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