]> Untitled Git - lemmy.git/blob - crates/db_views/src/comment_report_view.rs
bca8afdc007293e68f87d1655faac0ae73070982
[lemmy.git] / crates / db_views / src / comment_report_view.rs
1 use crate::structs::CommentReportView;
2 use diesel::{dsl::*, result::Error, *};
3 use lemmy_db_schema::{
4   aggregates::structs::CommentAggregates,
5   newtypes::{CommentReportId, CommunityId, PersonId},
6   schema::{
7     comment,
8     comment_aggregates,
9     comment_like,
10     comment_report,
11     community,
12     community_moderator,
13     community_person_ban,
14     person,
15     person_alias_1,
16     person_alias_2,
17     post,
18   },
19   source::{
20     comment::Comment,
21     comment_report::CommentReport,
22     community::{Community, CommunityPersonBan, CommunitySafe},
23     person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
24     post::Post,
25   },
26   traits::{MaybeOptional, ToSafe, ViewToVec},
27   utils::limit_and_offset,
28 };
29
30 type CommentReportViewTuple = (
31   CommentReport,
32   Comment,
33   Post,
34   CommunitySafe,
35   PersonSafe,
36   PersonSafeAlias1,
37   CommentAggregates,
38   Option<CommunityPersonBan>,
39   Option<i16>,
40   Option<PersonSafeAlias2>,
41 );
42
43 impl CommentReportView {
44   /// returns the CommentReportView for the provided report_id
45   ///
46   /// * `report_id` - the report id to obtain
47   pub fn read(
48     conn: &PgConnection,
49     report_id: CommentReportId,
50     my_person_id: PersonId,
51   ) -> Result<Self, Error> {
52     let (
53       comment_report,
54       comment,
55       post,
56       community,
57       creator,
58       comment_creator,
59       counts,
60       creator_banned_from_community,
61       comment_like,
62       resolver,
63     ) = comment_report::table
64       .find(report_id)
65       .inner_join(comment::table)
66       .inner_join(post::table.on(comment::post_id.eq(post::id)))
67       .inner_join(community::table.on(post::community_id.eq(community::id)))
68       .inner_join(person::table.on(comment_report::creator_id.eq(person::id)))
69       .inner_join(person_alias_1::table.on(comment::creator_id.eq(person_alias_1::id)))
70       .inner_join(
71         comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
72       )
73       .left_join(
74         community_person_ban::table.on(
75           community::id
76             .eq(community_person_ban::community_id)
77             .and(community_person_ban::person_id.eq(comment::creator_id))
78             .and(
79               community_person_ban::expires
80                 .is_null()
81                 .or(community_person_ban::expires.gt(now)),
82             ),
83         ),
84       )
85       .left_join(
86         comment_like::table.on(
87           comment::id
88             .eq(comment_like::comment_id)
89             .and(comment_like::person_id.eq(my_person_id)),
90         ),
91       )
92       .left_join(
93         person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())),
94       )
95       .select((
96         comment_report::all_columns,
97         comment::all_columns,
98         post::all_columns,
99         Community::safe_columns_tuple(),
100         Person::safe_columns_tuple(),
101         PersonAlias1::safe_columns_tuple(),
102         comment_aggregates::all_columns,
103         community_person_ban::all_columns.nullable(),
104         comment_like::score.nullable(),
105         PersonAlias2::safe_columns_tuple().nullable(),
106       ))
107       .first::<CommentReportViewTuple>(conn)?;
108
109     let my_vote = comment_like;
110
111     Ok(Self {
112       comment_report,
113       comment,
114       post,
115       community,
116       creator,
117       comment_creator,
118       counts,
119       creator_banned_from_community: creator_banned_from_community.is_some(),
120       my_vote,
121       resolver,
122     })
123   }
124
125   /// Returns the current unresolved post report count for the communities you mod
126   pub fn get_report_count(
127     conn: &PgConnection,
128     my_person_id: PersonId,
129     admin: bool,
130     community_id: Option<CommunityId>,
131   ) -> Result<i64, Error> {
132     use diesel::dsl::*;
133
134     let mut query = comment_report::table
135       .inner_join(comment::table)
136       .inner_join(post::table.on(comment::post_id.eq(post::id)))
137       .filter(comment_report::resolved.eq(false))
138       .into_boxed();
139
140     if let Some(community_id) = community_id {
141       query = query.filter(post::community_id.eq(community_id))
142     }
143
144     // If its not an admin, get only the ones you mod
145     if !admin {
146       query
147         .inner_join(
148           community_moderator::table.on(
149             community_moderator::community_id
150               .eq(post::community_id)
151               .and(community_moderator::person_id.eq(my_person_id)),
152           ),
153         )
154         .select(count(comment_report::id))
155         .first::<i64>(conn)
156     } else {
157       query.select(count(comment_report::id)).first::<i64>(conn)
158     }
159   }
160 }
161
162 pub struct CommentReportQueryBuilder<'a> {
163   conn: &'a PgConnection,
164   my_person_id: PersonId,
165   admin: bool,
166   community_id: Option<CommunityId>,
167   page: Option<i64>,
168   limit: Option<i64>,
169   unresolved_only: Option<bool>,
170 }
171
172 impl<'a> CommentReportQueryBuilder<'a> {
173   pub fn create(conn: &'a PgConnection, my_person_id: PersonId, admin: bool) -> Self {
174     CommentReportQueryBuilder {
175       conn,
176       my_person_id,
177       admin,
178       community_id: None,
179       page: None,
180       limit: None,
181       unresolved_only: Some(true),
182     }
183   }
184
185   pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
186     self.community_id = community_id.get_optional();
187     self
188   }
189
190   pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
191     self.page = page.get_optional();
192     self
193   }
194
195   pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
196     self.limit = limit.get_optional();
197     self
198   }
199
200   pub fn unresolved_only<T: MaybeOptional<bool>>(mut self, unresolved_only: T) -> Self {
201     self.unresolved_only = unresolved_only.get_optional();
202     self
203   }
204
205   pub fn list(self) -> Result<Vec<CommentReportView>, Error> {
206     let mut query = comment_report::table
207       .inner_join(comment::table)
208       .inner_join(post::table.on(comment::post_id.eq(post::id)))
209       .inner_join(community::table.on(post::community_id.eq(community::id)))
210       .inner_join(person::table.on(comment_report::creator_id.eq(person::id)))
211       .inner_join(person_alias_1::table.on(comment::creator_id.eq(person_alias_1::id)))
212       .inner_join(
213         comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
214       )
215       .left_join(
216         community_person_ban::table.on(
217           community::id
218             .eq(community_person_ban::community_id)
219             .and(community_person_ban::person_id.eq(comment::creator_id))
220             .and(
221               community_person_ban::expires
222                 .is_null()
223                 .or(community_person_ban::expires.gt(now)),
224             ),
225         ),
226       )
227       .left_join(
228         comment_like::table.on(
229           comment::id
230             .eq(comment_like::comment_id)
231             .and(comment_like::person_id.eq(self.my_person_id)),
232         ),
233       )
234       .left_join(
235         person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())),
236       )
237       .select((
238         comment_report::all_columns,
239         comment::all_columns,
240         post::all_columns,
241         Community::safe_columns_tuple(),
242         Person::safe_columns_tuple(),
243         PersonAlias1::safe_columns_tuple(),
244         comment_aggregates::all_columns,
245         community_person_ban::all_columns.nullable(),
246         comment_like::score.nullable(),
247         PersonAlias2::safe_columns_tuple().nullable(),
248       ))
249       .into_boxed();
250
251     if let Some(community_id) = self.community_id {
252       query = query.filter(post::community_id.eq(community_id));
253     }
254
255     if self.unresolved_only.unwrap_or(false) {
256       query = query.filter(comment_report::resolved.eq(false));
257     }
258
259     let (limit, offset) = limit_and_offset(self.page, self.limit);
260
261     query = query
262       .order_by(comment_report::published.desc())
263       .limit(limit)
264       .offset(offset);
265
266     // If its not an admin, get only the ones you mod
267     let res = if !self.admin {
268       query
269         .inner_join(
270           community_moderator::table.on(
271             community_moderator::community_id
272               .eq(post::community_id)
273               .and(community_moderator::person_id.eq(self.my_person_id)),
274           ),
275         )
276         .load::<CommentReportViewTuple>(self.conn)?
277     } else {
278       query.load::<CommentReportViewTuple>(self.conn)?
279     };
280
281     Ok(CommentReportView::from_tuple_to_vec(res))
282   }
283 }
284
285 impl ViewToVec for CommentReportView {
286   type DbTuple = CommentReportViewTuple;
287   fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
288     items
289       .iter()
290       .map(|a| Self {
291         comment_report: a.0.to_owned(),
292         comment: a.1.to_owned(),
293         post: a.2.to_owned(),
294         community: a.3.to_owned(),
295         creator: a.4.to_owned(),
296         comment_creator: a.5.to_owned(),
297         counts: a.6.to_owned(),
298         creator_banned_from_community: a.7.is_some(),
299         my_vote: a.8,
300         resolver: a.9.to_owned(),
301       })
302       .collect::<Vec<Self>>()
303   }
304 }
305
306 #[cfg(test)]
307 mod tests {
308   use crate::comment_report_view::{CommentReportQueryBuilder, CommentReportView};
309   use lemmy_db_schema::{
310     aggregates::structs::CommentAggregates,
311     source::{comment::*, comment_report::*, community::*, person::*, post::*},
312     traits::{Crud, Joinable, Reportable},
313     utils::establish_unpooled_connection,
314   };
315   use serial_test::serial;
316
317   #[test]
318   #[serial]
319   fn test_crud() {
320     let conn = establish_unpooled_connection();
321
322     let new_person = PersonForm {
323       name: "timmy_crv".into(),
324       ..PersonForm::default()
325     };
326
327     let inserted_timmy = Person::create(&conn, &new_person).unwrap();
328
329     let new_person_2 = PersonForm {
330       name: "sara_crv".into(),
331       ..PersonForm::default()
332     };
333
334     let inserted_sara = Person::create(&conn, &new_person_2).unwrap();
335
336     // Add a third person, since new ppl can only report something once.
337     let new_person_3 = PersonForm {
338       name: "jessica_crv".into(),
339       ..PersonForm::default()
340     };
341
342     let inserted_jessica = Person::create(&conn, &new_person_3).unwrap();
343
344     let new_community = CommunityForm {
345       name: "test community crv".to_string(),
346       title: "nada".to_owned(),
347       ..CommunityForm::default()
348     };
349
350     let inserted_community = Community::create(&conn, &new_community).unwrap();
351
352     // Make timmy a mod
353     let timmy_moderator_form = CommunityModeratorForm {
354       community_id: inserted_community.id,
355       person_id: inserted_timmy.id,
356     };
357
358     let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap();
359
360     let new_post = PostForm {
361       name: "A test post crv".into(),
362       creator_id: inserted_timmy.id,
363       community_id: inserted_community.id,
364       ..PostForm::default()
365     };
366
367     let inserted_post = Post::create(&conn, &new_post).unwrap();
368
369     let comment_form = CommentForm {
370       content: "A test comment 32".into(),
371       creator_id: inserted_timmy.id,
372       post_id: inserted_post.id,
373       ..CommentForm::default()
374     };
375
376     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
377
378     // sara reports
379     let sara_report_form = CommentReportForm {
380       creator_id: inserted_sara.id,
381       comment_id: inserted_comment.id,
382       original_comment_text: "this was it at time of creation".into(),
383       reason: "from sara".into(),
384     };
385
386     let inserted_sara_report = CommentReport::report(&conn, &sara_report_form).unwrap();
387
388     // jessica reports
389     let jessica_report_form = CommentReportForm {
390       creator_id: inserted_jessica.id,
391       comment_id: inserted_comment.id,
392       original_comment_text: "this was it at time of creation".into(),
393       reason: "from jessica".into(),
394     };
395
396     let inserted_jessica_report = CommentReport::report(&conn, &jessica_report_form).unwrap();
397
398     let agg = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
399
400     let read_jessica_report_view =
401       CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
402     let expected_jessica_report_view = CommentReportView {
403       comment_report: inserted_jessica_report.to_owned(),
404       comment: inserted_comment.to_owned(),
405       post: inserted_post,
406       community: CommunitySafe {
407         id: inserted_community.id,
408         name: inserted_community.name,
409         icon: None,
410         removed: false,
411         deleted: false,
412         nsfw: false,
413         actor_id: inserted_community.actor_id.to_owned(),
414         local: true,
415         title: inserted_community.title,
416         description: None,
417         updated: None,
418         banner: None,
419         hidden: false,
420         posting_restricted_to_mods: false,
421         published: inserted_community.published,
422       },
423       creator: PersonSafe {
424         id: inserted_jessica.id,
425         name: inserted_jessica.name,
426         display_name: None,
427         published: inserted_jessica.published,
428         avatar: None,
429         actor_id: inserted_jessica.actor_id.to_owned(),
430         local: true,
431         banned: false,
432         deleted: false,
433         admin: false,
434         bot_account: false,
435         bio: None,
436         banner: None,
437         updated: None,
438         inbox_url: inserted_jessica.inbox_url.to_owned(),
439         shared_inbox_url: None,
440         matrix_user_id: None,
441         ban_expires: None,
442       },
443       comment_creator: PersonSafeAlias1 {
444         id: inserted_timmy.id,
445         name: inserted_timmy.name.to_owned(),
446         display_name: None,
447         published: inserted_timmy.published,
448         avatar: None,
449         actor_id: inserted_timmy.actor_id.to_owned(),
450         local: true,
451         banned: false,
452         deleted: false,
453         admin: false,
454         bot_account: false,
455         bio: None,
456         banner: None,
457         updated: None,
458         inbox_url: inserted_timmy.inbox_url.to_owned(),
459         shared_inbox_url: None,
460         matrix_user_id: None,
461         ban_expires: None,
462       },
463       creator_banned_from_community: false,
464       counts: CommentAggregates {
465         id: agg.id,
466         comment_id: inserted_comment.id,
467         score: 0,
468         upvotes: 0,
469         downvotes: 0,
470         published: agg.published,
471       },
472       my_vote: None,
473       resolver: None,
474     };
475
476     assert_eq!(read_jessica_report_view, expected_jessica_report_view);
477
478     let mut expected_sara_report_view = expected_jessica_report_view.clone();
479     expected_sara_report_view.comment_report = inserted_sara_report;
480     expected_sara_report_view.creator = PersonSafe {
481       id: inserted_sara.id,
482       name: inserted_sara.name,
483       display_name: None,
484       published: inserted_sara.published,
485       avatar: None,
486       actor_id: inserted_sara.actor_id.to_owned(),
487       local: true,
488       banned: false,
489       deleted: false,
490       admin: false,
491       bot_account: false,
492       bio: None,
493       banner: None,
494       updated: None,
495       inbox_url: inserted_sara.inbox_url.to_owned(),
496       shared_inbox_url: None,
497       matrix_user_id: None,
498       ban_expires: None,
499     };
500
501     // Do a batch read of timmys reports
502     let reports = CommentReportQueryBuilder::create(&conn, inserted_timmy.id, false)
503       .list()
504       .unwrap();
505
506     assert_eq!(
507       reports,
508       [
509         expected_jessica_report_view.to_owned(),
510         expected_sara_report_view.to_owned()
511       ]
512     );
513
514     // Make sure the counts are correct
515     let report_count =
516       CommentReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
517     assert_eq!(2, report_count);
518
519     // Try to resolve the report
520     CommentReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
521     let read_jessica_report_view_after_resolve =
522       CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
523
524     let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
525     expected_jessica_report_view_after_resolve
526       .comment_report
527       .resolved = true;
528     expected_jessica_report_view_after_resolve
529       .comment_report
530       .resolver_id = Some(inserted_timmy.id);
531     expected_jessica_report_view_after_resolve
532       .comment_report
533       .updated = read_jessica_report_view_after_resolve
534       .comment_report
535       .updated;
536     expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 {
537       id: inserted_timmy.id,
538       name: inserted_timmy.name.to_owned(),
539       display_name: None,
540       published: inserted_timmy.published,
541       avatar: None,
542       actor_id: inserted_timmy.actor_id.to_owned(),
543       local: true,
544       banned: false,
545       deleted: false,
546       admin: false,
547       bot_account: false,
548       bio: None,
549       banner: None,
550       updated: None,
551       inbox_url: inserted_timmy.inbox_url.to_owned(),
552       shared_inbox_url: None,
553       matrix_user_id: None,
554       ban_expires: None,
555     });
556
557     assert_eq!(
558       read_jessica_report_view_after_resolve,
559       expected_jessica_report_view_after_resolve
560     );
561
562     // Do a batch read of timmys reports
563     // It should only show saras, which is unresolved
564     let reports_after_resolve = CommentReportQueryBuilder::create(&conn, inserted_timmy.id, false)
565       .list()
566       .unwrap();
567     assert_eq!(reports_after_resolve[0], expected_sara_report_view);
568
569     // Make sure the counts are correct
570     let report_count_after_resolved =
571       CommentReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
572     assert_eq!(1, report_count_after_resolved);
573
574     Person::delete(&conn, inserted_timmy.id).unwrap();
575     Person::delete(&conn, inserted_sara.id).unwrap();
576     Person::delete(&conn, inserted_jessica.id).unwrap();
577     Community::delete(&conn, inserted_community.id).unwrap();
578   }
579 }