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