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