]> Untitled Git - lemmy.git/blob - crates/db_views/src/comment_report_view.rs
0b1821c1b6b6a9abcb09c908e34d49e9fbc44ff7
[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   #![allow(clippy::unwrap_used)]
273   #![allow(clippy::indexing_slicing)]
274
275   use crate::comment_report_view::{CommentReportQuery, CommentReportView};
276   use lemmy_db_schema::{
277     aggregates::structs::CommentAggregates,
278     source::{
279       comment::{Comment, CommentInsertForm},
280       comment_report::{CommentReport, CommentReportForm},
281       community::{Community, CommunityInsertForm, CommunityModerator, CommunityModeratorForm},
282       instance::Instance,
283       person::{Person, PersonInsertForm},
284       post::{Post, PostInsertForm},
285     },
286     traits::{Crud, Joinable, Reportable},
287     utils::build_db_pool_for_tests,
288   };
289   use serial_test::serial;
290
291   #[tokio::test]
292   #[serial]
293   async fn test_crud() {
294     let pool = &build_db_pool_for_tests().await;
295     let pool = &mut pool.into();
296
297     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
298       .await
299       .unwrap();
300
301     let new_person = PersonInsertForm::builder()
302       .name("timmy_crv".into())
303       .public_key("pubkey".to_string())
304       .instance_id(inserted_instance.id)
305       .build();
306
307     let inserted_timmy = Person::create(pool, &new_person).await.unwrap();
308
309     let new_person_2 = PersonInsertForm::builder()
310       .name("sara_crv".into())
311       .public_key("pubkey".to_string())
312       .instance_id(inserted_instance.id)
313       .build();
314
315     let inserted_sara = Person::create(pool, &new_person_2).await.unwrap();
316
317     // Add a third person, since new ppl can only report something once.
318     let new_person_3 = PersonInsertForm::builder()
319       .name("jessica_crv".into())
320       .public_key("pubkey".to_string())
321       .instance_id(inserted_instance.id)
322       .build();
323
324     let inserted_jessica = Person::create(pool, &new_person_3).await.unwrap();
325
326     let new_community = CommunityInsertForm::builder()
327       .name("test community crv".to_string())
328       .title("nada".to_owned())
329       .public_key("pubkey".to_string())
330       .instance_id(inserted_instance.id)
331       .build();
332
333     let inserted_community = Community::create(pool, &new_community).await.unwrap();
334
335     // Make timmy a mod
336     let timmy_moderator_form = CommunityModeratorForm {
337       community_id: inserted_community.id,
338       person_id: inserted_timmy.id,
339     };
340
341     let _inserted_moderator = CommunityModerator::join(pool, &timmy_moderator_form)
342       .await
343       .unwrap();
344
345     let new_post = PostInsertForm::builder()
346       .name("A test post crv".into())
347       .creator_id(inserted_timmy.id)
348       .community_id(inserted_community.id)
349       .build();
350
351     let inserted_post = Post::create(pool, &new_post).await.unwrap();
352
353     let comment_form = CommentInsertForm::builder()
354       .content("A test comment 32".into())
355       .creator_id(inserted_timmy.id)
356       .post_id(inserted_post.id)
357       .build();
358
359     let inserted_comment = Comment::create(pool, &comment_form, None).await.unwrap();
360
361     // sara reports
362     let sara_report_form = CommentReportForm {
363       creator_id: inserted_sara.id,
364       comment_id: inserted_comment.id,
365       original_comment_text: "this was it at time of creation".into(),
366       reason: "from sara".into(),
367     };
368
369     let inserted_sara_report = CommentReport::report(pool, &sara_report_form)
370       .await
371       .unwrap();
372
373     // jessica reports
374     let jessica_report_form = CommentReportForm {
375       creator_id: inserted_jessica.id,
376       comment_id: inserted_comment.id,
377       original_comment_text: "this was it at time of creation".into(),
378       reason: "from jessica".into(),
379     };
380
381     let inserted_jessica_report = CommentReport::report(pool, &jessica_report_form)
382       .await
383       .unwrap();
384
385     let agg = CommentAggregates::read(pool, inserted_comment.id)
386       .await
387       .unwrap();
388
389     let read_jessica_report_view =
390       CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id)
391         .await
392         .unwrap();
393     let expected_jessica_report_view = CommentReportView {
394       comment_report: inserted_jessica_report.clone(),
395       comment: inserted_comment.clone(),
396       post: inserted_post,
397       community: Community {
398         id: inserted_community.id,
399         name: inserted_community.name,
400         icon: None,
401         removed: false,
402         deleted: false,
403         nsfw: false,
404         actor_id: inserted_community.actor_id.clone(),
405         local: true,
406         title: inserted_community.title,
407         description: None,
408         updated: None,
409         banner: None,
410         hidden: false,
411         posting_restricted_to_mods: false,
412         published: inserted_community.published,
413         private_key: inserted_community.private_key,
414         public_key: inserted_community.public_key,
415         last_refreshed_at: inserted_community.last_refreshed_at,
416         followers_url: inserted_community.followers_url,
417         inbox_url: inserted_community.inbox_url,
418         shared_inbox_url: inserted_community.shared_inbox_url,
419         moderators_url: inserted_community.moderators_url,
420         featured_url: inserted_community.featured_url,
421         instance_id: inserted_instance.id,
422       },
423       creator: Person {
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.clone(),
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.clone(),
439         shared_inbox_url: None,
440         matrix_user_id: None,
441         ban_expires: None,
442         instance_id: inserted_instance.id,
443         private_key: inserted_jessica.private_key,
444         public_key: inserted_jessica.public_key,
445         last_refreshed_at: inserted_jessica.last_refreshed_at,
446       },
447       comment_creator: Person {
448         id: inserted_timmy.id,
449         name: inserted_timmy.name.clone(),
450         display_name: None,
451         published: inserted_timmy.published,
452         avatar: None,
453         actor_id: inserted_timmy.actor_id.clone(),
454         local: true,
455         banned: false,
456         deleted: false,
457         admin: false,
458         bot_account: false,
459         bio: None,
460         banner: None,
461         updated: None,
462         inbox_url: inserted_timmy.inbox_url.clone(),
463         shared_inbox_url: None,
464         matrix_user_id: None,
465         ban_expires: None,
466         instance_id: inserted_instance.id,
467         private_key: inserted_timmy.private_key.clone(),
468         public_key: inserted_timmy.public_key.clone(),
469         last_refreshed_at: inserted_timmy.last_refreshed_at,
470       },
471       creator_banned_from_community: false,
472       counts: CommentAggregates {
473         id: agg.id,
474         comment_id: inserted_comment.id,
475         score: 0,
476         upvotes: 0,
477         downvotes: 0,
478         published: agg.published,
479         child_count: 0,
480         hot_rank: 1728,
481         controversy_rank: 0.0,
482       },
483       my_vote: None,
484       resolver: None,
485     };
486
487     assert_eq!(read_jessica_report_view, expected_jessica_report_view);
488
489     let mut expected_sara_report_view = expected_jessica_report_view.clone();
490     expected_sara_report_view.comment_report = inserted_sara_report;
491     expected_sara_report_view.creator = Person {
492       id: inserted_sara.id,
493       name: inserted_sara.name,
494       display_name: None,
495       published: inserted_sara.published,
496       avatar: None,
497       actor_id: inserted_sara.actor_id.clone(),
498       local: true,
499       banned: false,
500       deleted: false,
501       admin: false,
502       bot_account: false,
503       bio: None,
504       banner: None,
505       updated: None,
506       inbox_url: inserted_sara.inbox_url.clone(),
507       shared_inbox_url: None,
508       matrix_user_id: None,
509       ban_expires: None,
510       instance_id: inserted_instance.id,
511       private_key: inserted_sara.private_key,
512       public_key: inserted_sara.public_key,
513       last_refreshed_at: inserted_sara.last_refreshed_at,
514     };
515
516     // Do a batch read of timmys reports
517     let reports = CommentReportQuery::default()
518       .list(pool, &inserted_timmy)
519       .await
520       .unwrap();
521
522     assert_eq!(
523       reports,
524       [
525         expected_jessica_report_view.clone(),
526         expected_sara_report_view.clone()
527       ]
528     );
529
530     // Make sure the counts are correct
531     let report_count = CommentReportView::get_report_count(pool, inserted_timmy.id, false, None)
532       .await
533       .unwrap();
534     assert_eq!(2, report_count);
535
536     // Try to resolve the report
537     CommentReport::resolve(pool, inserted_jessica_report.id, inserted_timmy.id)
538       .await
539       .unwrap();
540     let read_jessica_report_view_after_resolve =
541       CommentReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id)
542         .await
543         .unwrap();
544
545     let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
546     expected_jessica_report_view_after_resolve
547       .comment_report
548       .resolved = true;
549     expected_jessica_report_view_after_resolve
550       .comment_report
551       .resolver_id = Some(inserted_timmy.id);
552     expected_jessica_report_view_after_resolve
553       .comment_report
554       .updated = read_jessica_report_view_after_resolve
555       .comment_report
556       .updated;
557     expected_jessica_report_view_after_resolve.resolver = Some(Person {
558       id: inserted_timmy.id,
559       name: inserted_timmy.name.clone(),
560       display_name: None,
561       published: inserted_timmy.published,
562       avatar: None,
563       actor_id: inserted_timmy.actor_id.clone(),
564       local: true,
565       banned: false,
566       deleted: false,
567       admin: false,
568       bot_account: false,
569       bio: None,
570       banner: None,
571       updated: None,
572       inbox_url: inserted_timmy.inbox_url.clone(),
573       private_key: inserted_timmy.private_key.clone(),
574       public_key: inserted_timmy.public_key.clone(),
575       last_refreshed_at: inserted_timmy.last_refreshed_at,
576       shared_inbox_url: None,
577       matrix_user_id: None,
578       ban_expires: None,
579       instance_id: inserted_instance.id,
580     });
581
582     assert_eq!(
583       read_jessica_report_view_after_resolve,
584       expected_jessica_report_view_after_resolve
585     );
586
587     // Do a batch read of timmys reports
588     // It should only show saras, which is unresolved
589     let reports_after_resolve = CommentReportQuery {
590       unresolved_only: (Some(true)),
591       ..Default::default()
592     }
593     .list(pool, &inserted_timmy)
594     .await
595     .unwrap();
596     assert_eq!(reports_after_resolve[0], expected_sara_report_view);
597     assert_eq!(reports_after_resolve.len(), 1);
598
599     // Make sure the counts are correct
600     let report_count_after_resolved =
601       CommentReportView::get_report_count(pool, inserted_timmy.id, false, None)
602         .await
603         .unwrap();
604     assert_eq!(1, report_count_after_resolved);
605
606     Person::delete(pool, inserted_timmy.id).await.unwrap();
607     Person::delete(pool, inserted_sara.id).await.unwrap();
608     Person::delete(pool, inserted_jessica.id).await.unwrap();
609     Community::delete(pool, inserted_community.id)
610       .await
611       .unwrap();
612     Instance::delete(pool, inserted_instance.id).await.unwrap();
613   }
614 }