]> Untitled Git - lemmy.git/blob - lemmy_db/src/source/comment.rs
Merge branch 'main' into move_views_to_diesel
[lemmy.git] / lemmy_db / src / source / comment.rs
1 use super::post::Post;
2 use crate::{
3   naive_now,
4   schema::{comment, comment_alias_1, comment_like, comment_saved},
5   ApubObject,
6   Crud,
7   Likeable,
8   Saveable,
9 };
10 use diesel::{dsl::*, result::Error, *};
11 use serde::Serialize;
12 use url::{ParseError, Url};
13
14 // WITH RECURSIVE MyTree AS (
15 //     SELECT * FROM comment WHERE parent_id IS NULL
16 //     UNION ALL
17 //     SELECT m.* FROM comment AS m JOIN MyTree AS t ON m.parent_id = t.id
18 // )
19 // SELECT * FROM MyTree;
20
21 #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
22 #[belongs_to(Post)]
23 #[table_name = "comment"]
24 pub struct Comment {
25   pub id: i32,
26   pub creator_id: i32,
27   pub post_id: i32,
28   pub parent_id: Option<i32>,
29   pub content: String,
30   pub removed: bool,
31   pub read: bool, // Whether the recipient has read the comment or not
32   pub published: chrono::NaiveDateTime,
33   pub updated: Option<chrono::NaiveDateTime>,
34   pub deleted: bool,
35   pub ap_id: String,
36   pub local: bool,
37 }
38
39 #[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)]
40 #[belongs_to(Post)]
41 #[table_name = "comment_alias_1"]
42 pub struct CommentAlias1 {
43   pub id: i32,
44   pub creator_id: i32,
45   pub post_id: i32,
46   pub parent_id: Option<i32>,
47   pub content: String,
48   pub removed: bool,
49   pub read: bool, // Whether the recipient has read the comment or not
50   pub published: chrono::NaiveDateTime,
51   pub updated: Option<chrono::NaiveDateTime>,
52   pub deleted: bool,
53   pub ap_id: String,
54   pub local: bool,
55 }
56
57 #[derive(Insertable, AsChangeset, Clone)]
58 #[table_name = "comment"]
59 pub struct CommentForm {
60   pub creator_id: i32,
61   pub post_id: i32,
62   pub parent_id: Option<i32>,
63   pub content: String,
64   pub removed: Option<bool>,
65   pub read: Option<bool>,
66   pub published: Option<chrono::NaiveDateTime>,
67   pub updated: Option<chrono::NaiveDateTime>,
68   pub deleted: Option<bool>,
69   pub ap_id: Option<String>,
70   pub local: bool,
71 }
72
73 impl CommentForm {
74   pub fn get_ap_id(&self) -> Result<Url, ParseError> {
75     Url::parse(&self.ap_id.as_ref().unwrap_or(&"not_a_url".to_string()))
76   }
77 }
78
79 impl Crud<CommentForm> for Comment {
80   fn read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
81     use crate::schema::comment::dsl::*;
82     comment.find(comment_id).first::<Self>(conn)
83   }
84
85   fn delete(conn: &PgConnection, comment_id: i32) -> Result<usize, Error> {
86     use crate::schema::comment::dsl::*;
87     diesel::delete(comment.find(comment_id)).execute(conn)
88   }
89
90   fn create(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
91     use crate::schema::comment::dsl::*;
92     insert_into(comment)
93       .values(comment_form)
94       .get_result::<Self>(conn)
95   }
96
97   fn update(
98     conn: &PgConnection,
99     comment_id: i32,
100     comment_form: &CommentForm,
101   ) -> Result<Self, Error> {
102     use crate::schema::comment::dsl::*;
103     diesel::update(comment.find(comment_id))
104       .set(comment_form)
105       .get_result::<Self>(conn)
106   }
107 }
108
109 impl ApubObject<CommentForm> for Comment {
110   fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
111     use crate::schema::comment::dsl::*;
112     comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
113   }
114
115   fn upsert(conn: &PgConnection, comment_form: &CommentForm) -> Result<Self, Error> {
116     use crate::schema::comment::dsl::*;
117     insert_into(comment)
118       .values(comment_form)
119       .on_conflict(ap_id)
120       .do_update()
121       .set(comment_form)
122       .get_result::<Self>(conn)
123   }
124 }
125
126 impl Comment {
127   pub fn update_ap_id(
128     conn: &PgConnection,
129     comment_id: i32,
130     apub_id: String,
131   ) -> Result<Self, Error> {
132     use crate::schema::comment::dsl::*;
133
134     diesel::update(comment.find(comment_id))
135       .set(ap_id.eq(apub_id))
136       .get_result::<Self>(conn)
137   }
138
139   pub fn permadelete_for_creator(
140     conn: &PgConnection,
141     for_creator_id: i32,
142   ) -> Result<Vec<Self>, Error> {
143     use crate::schema::comment::dsl::*;
144     diesel::update(comment.filter(creator_id.eq(for_creator_id)))
145       .set((
146         content.eq("*Permananently Deleted*"),
147         deleted.eq(true),
148         updated.eq(naive_now()),
149       ))
150       .get_results::<Self>(conn)
151   }
152
153   pub fn update_deleted(
154     conn: &PgConnection,
155     comment_id: i32,
156     new_deleted: bool,
157   ) -> Result<Self, Error> {
158     use crate::schema::comment::dsl::*;
159     diesel::update(comment.find(comment_id))
160       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
161       .get_result::<Self>(conn)
162   }
163
164   pub fn update_removed(
165     conn: &PgConnection,
166     comment_id: i32,
167     new_removed: bool,
168   ) -> Result<Self, Error> {
169     use crate::schema::comment::dsl::*;
170     diesel::update(comment.find(comment_id))
171       .set((removed.eq(new_removed), updated.eq(naive_now())))
172       .get_result::<Self>(conn)
173   }
174
175   pub fn update_removed_for_creator(
176     conn: &PgConnection,
177     for_creator_id: i32,
178     new_removed: bool,
179   ) -> Result<Vec<Self>, Error> {
180     use crate::schema::comment::dsl::*;
181     diesel::update(comment.filter(creator_id.eq(for_creator_id)))
182       .set((removed.eq(new_removed), updated.eq(naive_now())))
183       .get_results::<Self>(conn)
184   }
185
186   pub fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Self, Error> {
187     use crate::schema::comment::dsl::*;
188     diesel::update(comment.find(comment_id))
189       .set(read.eq(new_read))
190       .get_result::<Self>(conn)
191   }
192
193   pub fn update_content(
194     conn: &PgConnection,
195     comment_id: i32,
196     new_content: &str,
197   ) -> Result<Self, Error> {
198     use crate::schema::comment::dsl::*;
199     diesel::update(comment.find(comment_id))
200       .set((content.eq(new_content), updated.eq(naive_now())))
201       .get_result::<Self>(conn)
202   }
203 }
204
205 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]
206 #[belongs_to(Comment)]
207 #[table_name = "comment_like"]
208 pub struct CommentLike {
209   pub id: i32,
210   pub user_id: i32,
211   pub comment_id: i32,
212   pub post_id: i32, // TODO this is redundant
213   pub score: i16,
214   pub published: chrono::NaiveDateTime,
215 }
216
217 #[derive(Insertable, AsChangeset, Clone)]
218 #[table_name = "comment_like"]
219 pub struct CommentLikeForm {
220   pub user_id: i32,
221   pub comment_id: i32,
222   pub post_id: i32, // TODO this is redundant
223   pub score: i16,
224 }
225
226 impl Likeable<CommentLikeForm> for CommentLike {
227   fn like(conn: &PgConnection, comment_like_form: &CommentLikeForm) -> Result<Self, Error> {
228     use crate::schema::comment_like::dsl::*;
229     insert_into(comment_like)
230       .values(comment_like_form)
231       .on_conflict((comment_id, user_id))
232       .do_update()
233       .set(comment_like_form)
234       .get_result::<Self>(conn)
235   }
236   fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
237     use crate::schema::comment_like::dsl;
238     diesel::delete(
239       dsl::comment_like
240         .filter(dsl::comment_id.eq(comment_id))
241         .filter(dsl::user_id.eq(user_id)),
242     )
243     .execute(conn)
244   }
245 }
246
247 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
248 #[belongs_to(Comment)]
249 #[table_name = "comment_saved"]
250 pub struct CommentSaved {
251   pub id: i32,
252   pub comment_id: i32,
253   pub user_id: i32,
254   pub published: chrono::NaiveDateTime,
255 }
256
257 #[derive(Insertable, AsChangeset)]
258 #[table_name = "comment_saved"]
259 pub struct CommentSavedForm {
260   pub comment_id: i32,
261   pub user_id: i32,
262 }
263
264 impl Saveable<CommentSavedForm> for CommentSaved {
265   fn save(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result<Self, Error> {
266     use crate::schema::comment_saved::dsl::*;
267     insert_into(comment_saved)
268       .values(comment_saved_form)
269       .on_conflict((comment_id, user_id))
270       .do_update()
271       .set(comment_saved_form)
272       .get_result::<Self>(conn)
273   }
274   fn unsave(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result<usize, Error> {
275     use crate::schema::comment_saved::dsl::*;
276     diesel::delete(
277       comment_saved
278         .filter(comment_id.eq(comment_saved_form.comment_id))
279         .filter(user_id.eq(comment_saved_form.user_id)),
280     )
281     .execute(conn)
282   }
283 }
284
285 #[cfg(test)]
286 mod tests {
287   use crate::{
288     source::{comment::*, community::*, post::*, user::*},
289     tests::establish_unpooled_connection,
290     Crud,
291     ListingType,
292     SortType,
293   };
294
295   #[test]
296   fn test_crud() {
297     let conn = establish_unpooled_connection();
298
299     let new_user = UserForm {
300       name: "terry".into(),
301       preferred_username: None,
302       password_encrypted: "nope".into(),
303       email: None,
304       matrix_user_id: None,
305       avatar: None,
306       banner: None,
307       admin: false,
308       banned: Some(false),
309       published: None,
310       updated: None,
311       show_nsfw: false,
312       theme: "browser".into(),
313       default_sort_type: SortType::Hot as i16,
314       default_listing_type: ListingType::Subscribed as i16,
315       lang: "browser".into(),
316       show_avatars: true,
317       send_notifications_to_email: false,
318       actor_id: None,
319       bio: None,
320       local: true,
321       private_key: None,
322       public_key: None,
323       last_refreshed_at: None,
324     };
325
326     let inserted_user = User_::create(&conn, &new_user).unwrap();
327
328     let new_community = CommunityForm {
329       name: "test community".to_string(),
330       title: "nada".to_owned(),
331       description: None,
332       category_id: 1,
333       creator_id: inserted_user.id,
334       removed: None,
335       deleted: None,
336       updated: None,
337       nsfw: false,
338       actor_id: None,
339       local: true,
340       private_key: None,
341       public_key: None,
342       last_refreshed_at: None,
343       published: None,
344       banner: None,
345       icon: None,
346     };
347
348     let inserted_community = Community::create(&conn, &new_community).unwrap();
349
350     let new_post = PostForm {
351       name: "A test post".into(),
352       creator_id: inserted_user.id,
353       url: None,
354       body: None,
355       community_id: inserted_community.id,
356       removed: None,
357       deleted: None,
358       locked: None,
359       stickied: None,
360       updated: None,
361       nsfw: false,
362       embed_title: None,
363       embed_description: None,
364       embed_html: None,
365       thumbnail_url: None,
366       ap_id: None,
367       local: true,
368       published: None,
369     };
370
371     let inserted_post = Post::create(&conn, &new_post).unwrap();
372
373     let comment_form = CommentForm {
374       content: "A test comment".into(),
375       creator_id: inserted_user.id,
376       post_id: inserted_post.id,
377       removed: None,
378       deleted: None,
379       read: None,
380       parent_id: None,
381       published: None,
382       updated: None,
383       ap_id: None,
384       local: true,
385     };
386
387     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
388
389     let expected_comment = Comment {
390       id: inserted_comment.id,
391       content: "A test comment".into(),
392       creator_id: inserted_user.id,
393       post_id: inserted_post.id,
394       removed: false,
395       deleted: false,
396       read: false,
397       parent_id: None,
398       published: inserted_comment.published,
399       updated: None,
400       ap_id: inserted_comment.ap_id.to_owned(),
401       local: true,
402     };
403
404     let child_comment_form = CommentForm {
405       content: "A child comment".into(),
406       creator_id: inserted_user.id,
407       post_id: inserted_post.id,
408       parent_id: Some(inserted_comment.id),
409       removed: None,
410       deleted: None,
411       read: None,
412       published: None,
413       updated: None,
414       ap_id: None,
415       local: true,
416     };
417
418     let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
419
420     // Comment Like
421     let comment_like_form = CommentLikeForm {
422       comment_id: inserted_comment.id,
423       post_id: inserted_post.id,
424       user_id: inserted_user.id,
425       score: 1,
426     };
427
428     let inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
429
430     let expected_comment_like = CommentLike {
431       id: inserted_comment_like.id,
432       comment_id: inserted_comment.id,
433       post_id: inserted_post.id,
434       user_id: inserted_user.id,
435       published: inserted_comment_like.published,
436       score: 1,
437     };
438
439     // Comment Saved
440     let comment_saved_form = CommentSavedForm {
441       comment_id: inserted_comment.id,
442       user_id: inserted_user.id,
443     };
444
445     let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap();
446
447     let expected_comment_saved = CommentSaved {
448       id: inserted_comment_saved.id,
449       comment_id: inserted_comment.id,
450       user_id: inserted_user.id,
451       published: inserted_comment_saved.published,
452     };
453
454     let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
455     let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
456     let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
457     let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
458     let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
459     Comment::delete(&conn, inserted_child_comment.id).unwrap();
460     Post::delete(&conn, inserted_post.id).unwrap();
461     Community::delete(&conn, inserted_community.id).unwrap();
462     User_::delete(&conn, inserted_user.id).unwrap();
463
464     assert_eq!(expected_comment, read_comment);
465     assert_eq!(expected_comment, inserted_comment);
466     assert_eq!(expected_comment, updated_comment);
467     assert_eq!(expected_comment_like, inserted_comment_like);
468     assert_eq!(expected_comment_saved, inserted_comment_saved);
469     assert_eq!(
470       expected_comment.id,
471       inserted_child_comment.parent_id.unwrap()
472     );
473     assert_eq!(1, like_removed);
474     assert_eq!(1, saved_removed);
475     assert_eq!(1, num_deleted);
476   }
477 }