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