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