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