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