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