]> Untitled Git - lemmy.git/blob - crates/db_views/src/post_report_view.rs
Fix *All* reports not showing. Fixes #2902 (#2903)
[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: &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: &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> {
165   #[builder(!default)]
166   pool: &'a DbPool,
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> PostReportQuery<'a> {
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
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       },
472       resolver: None,
473     };
474
475     assert_eq!(read_jessica_report_view, expected_jessica_report_view);
476
477     let mut expected_sara_report_view = expected_jessica_report_view.clone();
478     expected_sara_report_view.post_report = inserted_sara_report;
479     expected_sara_report_view.my_vote = None;
480     expected_sara_report_view.creator = Person {
481       id: inserted_sara.id,
482       name: inserted_sara.name,
483       display_name: None,
484       published: inserted_sara.published,
485       avatar: None,
486       actor_id: inserted_sara.actor_id.clone(),
487       local: true,
488       banned: false,
489       deleted: false,
490       admin: false,
491       bot_account: false,
492       bio: None,
493       banner: None,
494       updated: None,
495       inbox_url: inserted_sara.inbox_url.clone(),
496       shared_inbox_url: None,
497       matrix_user_id: None,
498       ban_expires: None,
499       instance_id: inserted_instance.id,
500       private_key: inserted_sara.private_key,
501       public_key: inserted_sara.public_key,
502       last_refreshed_at: inserted_sara.last_refreshed_at,
503     };
504
505     // Do a batch read of timmys reports
506     let reports = PostReportQuery::builder()
507       .pool(pool)
508       .my_person_id(inserted_timmy.id)
509       .admin(false)
510       .build()
511       .list()
512       .await
513       .unwrap();
514
515     assert_eq!(
516       reports,
517       [
518         expected_jessica_report_view.clone(),
519         expected_sara_report_view.clone()
520       ]
521     );
522
523     // Make sure the counts are correct
524     let report_count = PostReportView::get_report_count(pool, inserted_timmy.id, false, None)
525       .await
526       .unwrap();
527     assert_eq!(2, report_count);
528
529     // Try to resolve the report
530     PostReport::resolve(pool, inserted_jessica_report.id, inserted_timmy.id)
531       .await
532       .unwrap();
533     let read_jessica_report_view_after_resolve =
534       PostReportView::read(pool, inserted_jessica_report.id, inserted_timmy.id)
535         .await
536         .unwrap();
537
538     let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
539     expected_jessica_report_view_after_resolve
540       .post_report
541       .resolved = true;
542     expected_jessica_report_view_after_resolve
543       .post_report
544       .resolver_id = Some(inserted_timmy.id);
545     expected_jessica_report_view_after_resolve
546       .post_report
547       .updated = read_jessica_report_view_after_resolve.post_report.updated;
548     expected_jessica_report_view_after_resolve.resolver = Some(Person {
549       id: inserted_timmy.id,
550       name: inserted_timmy.name.clone(),
551       display_name: None,
552       published: inserted_timmy.published,
553       avatar: None,
554       actor_id: inserted_timmy.actor_id.clone(),
555       local: true,
556       banned: false,
557       deleted: false,
558       admin: false,
559       bot_account: false,
560       bio: None,
561       banner: None,
562       updated: None,
563       inbox_url: inserted_timmy.inbox_url.clone(),
564       shared_inbox_url: None,
565       matrix_user_id: None,
566       ban_expires: None,
567       instance_id: inserted_instance.id,
568       private_key: inserted_timmy.private_key.clone(),
569       public_key: inserted_timmy.public_key.clone(),
570       last_refreshed_at: inserted_timmy.last_refreshed_at,
571     });
572
573     assert_eq!(
574       read_jessica_report_view_after_resolve,
575       expected_jessica_report_view_after_resolve
576     );
577
578     // Do a batch read of timmys reports
579     // It should only show saras, which is unresolved
580     let reports_after_resolve = PostReportQuery::builder()
581       .pool(pool)
582       .my_person_id(inserted_timmy.id)
583       .admin(false)
584       .unresolved_only(Some(true))
585       .build()
586       .list()
587       .await
588       .unwrap();
589     assert_eq!(reports_after_resolve[0], expected_sara_report_view);
590
591     // Make sure the counts are correct
592     let report_count_after_resolved =
593       PostReportView::get_report_count(pool, inserted_timmy.id, false, None)
594         .await
595         .unwrap();
596     assert_eq!(1, report_count_after_resolved);
597
598     Person::delete(pool, inserted_timmy.id).await.unwrap();
599     Person::delete(pool, inserted_sara.id).await.unwrap();
600     Person::delete(pool, inserted_jessica.id).await.unwrap();
601     Community::delete(pool, inserted_community.id)
602       .await
603       .unwrap();
604     Instance::delete(pool, inserted_instance.id).await.unwrap();
605   }
606 }