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