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