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