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