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