]> Untitled Git - lemmy.git/blob - lemmy_db/src/source/comment.rs
Beginning to add new comment_view.
[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       .get_result::<Self>(conn)
232   }
233   fn remove(conn: &PgConnection, user_id: i32, comment_id: i32) -> Result<usize, Error> {
234     use crate::schema::comment_like::dsl;
235     diesel::delete(
236       dsl::comment_like
237         .filter(dsl::comment_id.eq(comment_id))
238         .filter(dsl::user_id.eq(user_id)),
239     )
240     .execute(conn)
241   }
242 }
243
244 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
245 #[belongs_to(Comment)]
246 #[table_name = "comment_saved"]
247 pub struct CommentSaved {
248   pub id: i32,
249   pub comment_id: i32,
250   pub user_id: i32,
251   pub published: chrono::NaiveDateTime,
252 }
253
254 #[derive(Insertable, AsChangeset)]
255 #[table_name = "comment_saved"]
256 pub struct CommentSavedForm {
257   pub comment_id: i32,
258   pub user_id: i32,
259 }
260
261 impl Saveable<CommentSavedForm> for CommentSaved {
262   fn save(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result<Self, Error> {
263     use crate::schema::comment_saved::dsl::*;
264     insert_into(comment_saved)
265       .values(comment_saved_form)
266       .get_result::<Self>(conn)
267   }
268   fn unsave(conn: &PgConnection, comment_saved_form: &CommentSavedForm) -> Result<usize, Error> {
269     use crate::schema::comment_saved::dsl::*;
270     diesel::delete(
271       comment_saved
272         .filter(comment_id.eq(comment_saved_form.comment_id))
273         .filter(user_id.eq(comment_saved_form.user_id)),
274     )
275     .execute(conn)
276   }
277 }
278
279 #[cfg(test)]
280 mod tests {
281   use crate::{
282     source::{comment::*, community::*, post::*, user::*},
283     tests::establish_unpooled_connection,
284     Crud,
285     ListingType,
286     SortType,
287   };
288
289   #[test]
290   fn test_crud() {
291     let conn = establish_unpooled_connection();
292
293     let new_user = UserForm {
294       name: "terry".into(),
295       preferred_username: None,
296       password_encrypted: "nope".into(),
297       email: None,
298       matrix_user_id: None,
299       avatar: None,
300       banner: None,
301       admin: false,
302       banned: Some(false),
303       published: None,
304       updated: None,
305       show_nsfw: false,
306       theme: "browser".into(),
307       default_sort_type: SortType::Hot as i16,
308       default_listing_type: ListingType::Subscribed as i16,
309       lang: "browser".into(),
310       show_avatars: true,
311       send_notifications_to_email: false,
312       actor_id: None,
313       bio: None,
314       local: true,
315       private_key: None,
316       public_key: None,
317       last_refreshed_at: None,
318     };
319
320     let inserted_user = User_::create(&conn, &new_user).unwrap();
321
322     let new_community = CommunityForm {
323       name: "test community".to_string(),
324       title: "nada".to_owned(),
325       description: None,
326       category_id: 1,
327       creator_id: inserted_user.id,
328       removed: None,
329       deleted: None,
330       updated: None,
331       nsfw: false,
332       actor_id: None,
333       local: true,
334       private_key: None,
335       public_key: None,
336       last_refreshed_at: None,
337       published: None,
338       banner: None,
339       icon: None,
340     };
341
342     let inserted_community = Community::create(&conn, &new_community).unwrap();
343
344     let new_post = PostForm {
345       name: "A test post".into(),
346       creator_id: inserted_user.id,
347       url: None,
348       body: None,
349       community_id: inserted_community.id,
350       removed: None,
351       deleted: None,
352       locked: None,
353       stickied: None,
354       updated: None,
355       nsfw: false,
356       embed_title: None,
357       embed_description: None,
358       embed_html: None,
359       thumbnail_url: None,
360       ap_id: None,
361       local: true,
362       published: None,
363     };
364
365     let inserted_post = Post::create(&conn, &new_post).unwrap();
366
367     let comment_form = CommentForm {
368       content: "A test comment".into(),
369       creator_id: inserted_user.id,
370       post_id: inserted_post.id,
371       removed: None,
372       deleted: None,
373       read: None,
374       parent_id: None,
375       published: None,
376       updated: None,
377       ap_id: None,
378       local: true,
379     };
380
381     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
382
383     let expected_comment = Comment {
384       id: inserted_comment.id,
385       content: "A test comment".into(),
386       creator_id: inserted_user.id,
387       post_id: inserted_post.id,
388       removed: false,
389       deleted: false,
390       read: false,
391       parent_id: None,
392       published: inserted_comment.published,
393       updated: None,
394       ap_id: inserted_comment.ap_id.to_owned(),
395       local: true,
396     };
397
398     let child_comment_form = CommentForm {
399       content: "A child comment".into(),
400       creator_id: inserted_user.id,
401       post_id: inserted_post.id,
402       parent_id: Some(inserted_comment.id),
403       removed: None,
404       deleted: None,
405       read: None,
406       published: None,
407       updated: None,
408       ap_id: None,
409       local: true,
410     };
411
412     let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
413
414     // Comment Like
415     let comment_like_form = CommentLikeForm {
416       comment_id: inserted_comment.id,
417       post_id: inserted_post.id,
418       user_id: inserted_user.id,
419       score: 1,
420     };
421
422     let inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
423
424     let expected_comment_like = CommentLike {
425       id: inserted_comment_like.id,
426       comment_id: inserted_comment.id,
427       post_id: inserted_post.id,
428       user_id: inserted_user.id,
429       published: inserted_comment_like.published,
430       score: 1,
431     };
432
433     // Comment Saved
434     let comment_saved_form = CommentSavedForm {
435       comment_id: inserted_comment.id,
436       user_id: inserted_user.id,
437     };
438
439     let inserted_comment_saved = CommentSaved::save(&conn, &comment_saved_form).unwrap();
440
441     let expected_comment_saved = CommentSaved {
442       id: inserted_comment_saved.id,
443       comment_id: inserted_comment.id,
444       user_id: inserted_user.id,
445       published: inserted_comment_saved.published,
446     };
447
448     let read_comment = Comment::read(&conn, inserted_comment.id).unwrap();
449     let updated_comment = Comment::update(&conn, inserted_comment.id, &comment_form).unwrap();
450     let like_removed = CommentLike::remove(&conn, inserted_user.id, inserted_comment.id).unwrap();
451     let saved_removed = CommentSaved::unsave(&conn, &comment_saved_form).unwrap();
452     let num_deleted = Comment::delete(&conn, inserted_comment.id).unwrap();
453     Comment::delete(&conn, inserted_child_comment.id).unwrap();
454     Post::delete(&conn, inserted_post.id).unwrap();
455     Community::delete(&conn, inserted_community.id).unwrap();
456     User_::delete(&conn, inserted_user.id).unwrap();
457
458     assert_eq!(expected_comment, read_comment);
459     assert_eq!(expected_comment, inserted_comment);
460     assert_eq!(expected_comment, updated_comment);
461     assert_eq!(expected_comment_like, inserted_comment_like);
462     assert_eq!(expected_comment_saved, inserted_comment_saved);
463     assert_eq!(
464       expected_comment.id,
465       inserted_child_comment.parent_id.unwrap()
466     );
467     assert_eq!(1, like_removed);
468     assert_eq!(1, saved_removed);
469     assert_eq!(1, num_deleted);
470   }
471 }