]> Untitled Git - lemmy.git/blob - lemmy_db/src/source/comment.rs
dd4fb39dee27f9b5fce1adeaa789fe1dca8e1334
[lemmy.git] / lemmy_db / src / source / 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     source::{comment::*, community::*, post::*, user::*},
264     tests::establish_unpooled_connection,
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: Some(false),
284       published: None,
285       updated: None,
286       show_nsfw: false,
287       theme: "browser".into(),
288       default_sort_type: SortType::Hot as i16,
289       default_listing_type: ListingType::Subscribed as i16,
290       lang: "browser".into(),
291       show_avatars: true,
292       send_notifications_to_email: false,
293       actor_id: None,
294       bio: None,
295       local: true,
296       private_key: None,
297       public_key: None,
298       last_refreshed_at: None,
299     };
300
301     let inserted_user = User_::create(&conn, &new_user).unwrap();
302
303     let new_community = CommunityForm {
304       name: "test community".to_string(),
305       title: "nada".to_owned(),
306       description: None,
307       category_id: 1,
308       creator_id: inserted_user.id,
309       removed: None,
310       deleted: None,
311       updated: None,
312       nsfw: false,
313       actor_id: None,
314       local: true,
315       private_key: None,
316       public_key: None,
317       last_refreshed_at: None,
318       published: None,
319       banner: None,
320       icon: None,
321     };
322
323     let inserted_community = Community::create(&conn, &new_community).unwrap();
324
325     let new_post = PostForm {
326       name: "A test post".into(),
327       creator_id: inserted_user.id,
328       url: None,
329       body: None,
330       community_id: inserted_community.id,
331       removed: None,
332       deleted: None,
333       locked: None,
334       stickied: None,
335       updated: None,
336       nsfw: false,
337       embed_title: None,
338       embed_description: None,
339       embed_html: None,
340       thumbnail_url: None,
341       ap_id: None,
342       local: true,
343       published: None,
344     };
345
346     let inserted_post = Post::create(&conn, &new_post).unwrap();
347
348     let comment_form = CommentForm {
349       content: "A test comment".into(),
350       creator_id: inserted_user.id,
351       post_id: inserted_post.id,
352       removed: None,
353       deleted: None,
354       read: None,
355       parent_id: None,
356       published: None,
357       updated: None,
358       ap_id: None,
359       local: true,
360     };
361
362     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
363
364     let expected_comment = Comment {
365       id: inserted_comment.id,
366       content: "A test comment".into(),
367       creator_id: inserted_user.id,
368       post_id: inserted_post.id,
369       removed: false,
370       deleted: false,
371       read: false,
372       parent_id: None,
373       published: inserted_comment.published,
374       updated: None,
375       ap_id: inserted_comment.ap_id.to_owned(),
376       local: true,
377     };
378
379     let child_comment_form = CommentForm {
380       content: "A child comment".into(),
381       creator_id: inserted_user.id,
382       post_id: inserted_post.id,
383       parent_id: Some(inserted_comment.id),
384       removed: None,
385       deleted: None,
386       read: None,
387       published: None,
388       updated: None,
389       ap_id: None,
390       local: true,
391     };
392
393     let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
394
395     // Comment Like
396     let comment_like_form = CommentLikeForm {
397       comment_id: inserted_comment.id,
398       post_id: inserted_post.id,
399       user_id: inserted_user.id,
400       score: 1,
401     };
402
403     let inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
404
405     let expected_comment_like = CommentLike {
406       id: inserted_comment_like.id,
407       comment_id: inserted_comment.id,
408       post_id: inserted_post.id,
409       user_id: inserted_user.id,
410       published: inserted_comment_like.published,
411       score: 1,
412     };
413
414     // Comment Saved
415     let comment_saved_form = CommentSavedForm {
416       comment_id: inserted_comment.id,
417       user_id: inserted_user.id,
418     };
419
420     let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap();
421
422     let expected_comment_saved = CommentSaved {
423       id: inserted_comment_saved.id,
424       comment_id: inserted_comment.id,
425       user_id: inserted_user.id,
426       published: inserted_comment_saved.published,
427     };
428
429     let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
430     let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
431     let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
432     let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
433     let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
434     Comment::delete(&conn, inserted_child_comment.id).unwrap();
435     Post::delete(&conn, inserted_post.id).unwrap();
436     Community::delete(&conn, inserted_community.id).unwrap();
437     User_::delete(&conn, inserted_user.id).unwrap();
438
439     assert_eq!(expected_comment, read_comment);
440     assert_eq!(expected_comment, inserted_comment);
441     assert_eq!(expected_comment, updated_comment);
442     assert_eq!(expected_comment_like, inserted_comment_like);
443     assert_eq!(expected_comment_saved, inserted_comment_saved);
444     assert_eq!(
445       expected_comment.id,
446       inserted_child_comment.parent_id.unwrap()
447     );
448     assert_eq!(1, like_removed);
449     assert_eq!(1, saved_removed);
450     assert_eq!(1, num_deleted);
451   }
452 }