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