]> Untitled Git - lemmy.git/blob - crates/db_views/src/post_report_view.rs
f27ba50aad520c82f12d6b2878a515b4e40db4e2
[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       // Test this join
135       .inner_join(
136         community_moderator::table.on(community_moderator::community_id.eq(post::community_id)),
137       )
138       .filter(post_report::resolved.eq(false))
139       .into_boxed();
140
141     // If its not an admin, get only the ones you mod
142     if !admin {
143       query = query.filter(community_moderator::person_id.eq(my_person_id));
144     }
145
146     if let Some(community_id) = community_id {
147       query = query.filter(post::community_id.eq(community_id))
148     }
149
150     query.select(count(post_report::id)).first::<i64>(conn)
151   }
152 }
153
154 pub struct PostReportQueryBuilder<'a> {
155   conn: &'a PgConnection,
156   my_person_id: PersonId,
157   admin: bool,
158   community_id: Option<CommunityId>,
159   page: Option<i64>,
160   limit: Option<i64>,
161   unresolved_only: Option<bool>,
162 }
163
164 impl<'a> PostReportQueryBuilder<'a> {
165   pub fn create(conn: &'a PgConnection, my_person_id: PersonId, admin: bool) -> Self {
166     PostReportQueryBuilder {
167       conn,
168       my_person_id,
169       admin,
170       community_id: None,
171       page: None,
172       limit: None,
173       unresolved_only: Some(true),
174     }
175   }
176
177   pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
178     self.community_id = community_id.get_optional();
179     self
180   }
181
182   pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
183     self.page = page.get_optional();
184     self
185   }
186
187   pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
188     self.limit = limit.get_optional();
189     self
190   }
191
192   pub fn unresolved_only<T: MaybeOptional<bool>>(mut self, unresolved_only: T) -> Self {
193     self.unresolved_only = unresolved_only.get_optional();
194     self
195   }
196
197   pub fn list(self) -> Result<Vec<PostReportView>, Error> {
198     let mut query = post_report::table
199       .inner_join(post::table)
200       .inner_join(community::table.on(post::community_id.eq(community::id)))
201       .inner_join(person::table.on(post_report::creator_id.eq(person::id)))
202       .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id)))
203       .inner_join(
204         community_moderator::table.on(community_moderator::community_id.eq(post::community_id)),
205       )
206       .left_join(
207         community_person_ban::table.on(
208           post::community_id
209             .eq(community_person_ban::community_id)
210             .and(community_person_ban::person_id.eq(post::creator_id)),
211         ),
212       )
213       .left_join(
214         post_like::table.on(
215           post::id
216             .eq(post_like::post_id)
217             .and(post_like::person_id.eq(self.my_person_id)),
218         ),
219       )
220       .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
221       .left_join(
222         person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())),
223       )
224       .select((
225         post_report::all_columns,
226         post::all_columns,
227         Community::safe_columns_tuple(),
228         Person::safe_columns_tuple(),
229         PersonAlias1::safe_columns_tuple(),
230         community_person_ban::all_columns.nullable(),
231         post_like::score.nullable(),
232         post_aggregates::all_columns,
233         PersonAlias2::safe_columns_tuple().nullable(),
234       ))
235       .into_boxed();
236
237     // If its not an admin, get only the ones you mod
238     if !self.admin {
239       query = query.filter(community_moderator::person_id.eq(self.my_person_id));
240     }
241
242     if let Some(community_id) = self.community_id {
243       query = query.filter(post::community_id.eq(community_id));
244     }
245
246     if self.unresolved_only.unwrap_or(false) {
247       query = query.filter(post_report::resolved.eq(false));
248     }
249
250     let (limit, offset) = limit_and_offset(self.page, self.limit);
251
252     let res = query
253       .order_by(post_report::published.asc())
254       .limit(limit)
255       .offset(offset)
256       .load::<PostReportViewTuple>(self.conn)?;
257
258     Ok(PostReportView::from_tuple_to_vec(res))
259   }
260 }
261
262 impl ViewToVec for PostReportView {
263   type DbTuple = PostReportViewTuple;
264   fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
265     items
266       .iter()
267       .map(|a| Self {
268         post_report: a.0.to_owned(),
269         post: a.1.to_owned(),
270         community: a.2.to_owned(),
271         creator: a.3.to_owned(),
272         post_creator: a.4.to_owned(),
273         creator_banned_from_community: a.5.is_some(),
274         my_vote: a.6,
275         counts: a.7.to_owned(),
276         resolver: a.8.to_owned(),
277       })
278       .collect::<Vec<Self>>()
279   }
280 }
281
282 #[cfg(test)]
283 mod tests {
284   use crate::post_report_view::{PostReportQueryBuilder, PostReportView};
285   use lemmy_db_schema::{
286     aggregates::post_aggregates::PostAggregates,
287     establish_unpooled_connection,
288     source::{
289       community::*,
290       person::*,
291       post::*,
292       post_report::{PostReport, PostReportForm},
293     },
294     traits::{Crud, Joinable, Reportable},
295   };
296   use serial_test::serial;
297
298   #[test]
299   #[serial]
300   fn test_crud() {
301     let conn = establish_unpooled_connection();
302
303     let new_person = PersonForm {
304       name: "timmy_prv".into(),
305       ..PersonForm::default()
306     };
307
308     let inserted_timmy = Person::create(&conn, &new_person).unwrap();
309
310     let new_person_2 = PersonForm {
311       name: "sara_prv".into(),
312       ..PersonForm::default()
313     };
314
315     let inserted_sara = Person::create(&conn, &new_person_2).unwrap();
316
317     // Add a third person, since new ppl can only report something once.
318     let new_person_3 = PersonForm {
319       name: "jessica_prv".into(),
320       ..PersonForm::default()
321     };
322
323     let inserted_jessica = Person::create(&conn, &new_person_3).unwrap();
324
325     let new_community = CommunityForm {
326       name: "test community prv".to_string(),
327       title: "nada".to_owned(),
328       ..CommunityForm::default()
329     };
330
331     let inserted_community = Community::create(&conn, &new_community).unwrap();
332
333     // Make timmy a mod
334     let timmy_moderator_form = CommunityModeratorForm {
335       community_id: inserted_community.id,
336       person_id: inserted_timmy.id,
337     };
338
339     let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap();
340
341     let new_post = PostForm {
342       name: "A test post crv".into(),
343       creator_id: inserted_timmy.id,
344       community_id: inserted_community.id,
345       ..PostForm::default()
346     };
347
348     let inserted_post = Post::create(&conn, &new_post).unwrap();
349
350     // sara reports
351     let sara_report_form = PostReportForm {
352       creator_id: inserted_sara.id,
353       post_id: inserted_post.id,
354       original_post_name: "Orig post".into(),
355       original_post_url: None,
356       original_post_body: None,
357       reason: "from sara".into(),
358     };
359
360     let inserted_sara_report = PostReport::report(&conn, &sara_report_form).unwrap();
361
362     // jessica reports
363     let jessica_report_form = PostReportForm {
364       creator_id: inserted_jessica.id,
365       post_id: inserted_post.id,
366       original_post_name: "Orig post".into(),
367       original_post_url: None,
368       original_post_body: None,
369       reason: "from jessica".into(),
370     };
371
372     let inserted_jessica_report = PostReport::report(&conn, &jessica_report_form).unwrap();
373
374     let agg = PostAggregates::read(&conn, inserted_post.id).unwrap();
375
376     let read_jessica_report_view =
377       PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
378     let expected_jessica_report_view = PostReportView {
379       post_report: inserted_jessica_report.to_owned(),
380       post: inserted_post.to_owned(),
381       community: CommunitySafe {
382         id: inserted_community.id,
383         name: inserted_community.name,
384         icon: None,
385         removed: false,
386         deleted: false,
387         nsfw: false,
388         actor_id: inserted_community.actor_id.to_owned(),
389         local: true,
390         title: inserted_community.title,
391         description: None,
392         updated: None,
393         banner: None,
394         published: inserted_community.published,
395       },
396       creator: PersonSafe {
397         id: inserted_jessica.id,
398         name: inserted_jessica.name,
399         display_name: None,
400         published: inserted_jessica.published,
401         avatar: None,
402         actor_id: inserted_jessica.actor_id.to_owned(),
403         local: true,
404         banned: false,
405         deleted: false,
406         admin: false,
407         bot_account: false,
408         bio: None,
409         banner: None,
410         updated: None,
411         inbox_url: inserted_jessica.inbox_url.to_owned(),
412         shared_inbox_url: None,
413         matrix_user_id: None,
414       },
415       post_creator: PersonSafeAlias1 {
416         id: inserted_timmy.id,
417         name: inserted_timmy.name.to_owned(),
418         display_name: None,
419         published: inserted_timmy.published,
420         avatar: None,
421         actor_id: inserted_timmy.actor_id.to_owned(),
422         local: true,
423         banned: false,
424         deleted: false,
425         admin: false,
426         bot_account: false,
427         bio: None,
428         banner: None,
429         updated: None,
430         inbox_url: inserted_timmy.inbox_url.to_owned(),
431         shared_inbox_url: None,
432         matrix_user_id: None,
433       },
434       creator_banned_from_community: false,
435       my_vote: None,
436       counts: PostAggregates {
437         id: agg.id,
438         post_id: inserted_post.id,
439         comments: 0,
440         score: 0,
441         upvotes: 0,
442         downvotes: 0,
443         stickied: false,
444         published: agg.published,
445         newest_comment_time_necro: inserted_post.published,
446         newest_comment_time: inserted_post.published,
447       },
448       resolver: None,
449     };
450
451     assert_eq!(read_jessica_report_view, expected_jessica_report_view);
452
453     let mut expected_sara_report_view = expected_jessica_report_view.clone();
454     expected_sara_report_view.post_report = inserted_sara_report;
455     expected_sara_report_view.my_vote = None;
456     expected_sara_report_view.creator = PersonSafe {
457       id: inserted_sara.id,
458       name: inserted_sara.name,
459       display_name: None,
460       published: inserted_sara.published,
461       avatar: None,
462       actor_id: inserted_sara.actor_id.to_owned(),
463       local: true,
464       banned: false,
465       deleted: false,
466       admin: false,
467       bot_account: false,
468       bio: None,
469       banner: None,
470       updated: None,
471       inbox_url: inserted_sara.inbox_url.to_owned(),
472       shared_inbox_url: None,
473       matrix_user_id: None,
474     };
475
476     // Do a batch read of timmys reports
477     let reports = PostReportQueryBuilder::create(&conn, inserted_timmy.id, false)
478       .list()
479       .unwrap();
480
481     assert_eq!(
482       reports,
483       [
484         expected_sara_report_view.to_owned(),
485         expected_jessica_report_view.to_owned()
486       ]
487     );
488
489     // Make sure the counts are correct
490     let report_count =
491       PostReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
492     assert_eq!(2, report_count);
493
494     // Try to resolve the report
495     PostReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
496     let read_jessica_report_view_after_resolve =
497       PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
498
499     let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
500     expected_jessica_report_view_after_resolve
501       .post_report
502       .resolved = true;
503     expected_jessica_report_view_after_resolve
504       .post_report
505       .resolver_id = Some(inserted_timmy.id);
506     expected_jessica_report_view_after_resolve
507       .post_report
508       .updated = read_jessica_report_view_after_resolve.post_report.updated;
509     expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 {
510       id: inserted_timmy.id,
511       name: inserted_timmy.name.to_owned(),
512       display_name: None,
513       published: inserted_timmy.published,
514       avatar: None,
515       actor_id: inserted_timmy.actor_id.to_owned(),
516       local: true,
517       banned: false,
518       deleted: false,
519       admin: false,
520       bot_account: false,
521       bio: None,
522       banner: None,
523       updated: None,
524       inbox_url: inserted_timmy.inbox_url.to_owned(),
525       shared_inbox_url: None,
526       matrix_user_id: None,
527     });
528
529     assert_eq!(
530       read_jessica_report_view_after_resolve,
531       expected_jessica_report_view_after_resolve
532     );
533
534     // Do a batch read of timmys reports
535     // It should only show saras, which is unresolved
536     let reports_after_resolve = PostReportQueryBuilder::create(&conn, inserted_timmy.id, false)
537       .list()
538       .unwrap();
539     assert_eq!(reports_after_resolve[0], expected_sara_report_view);
540
541     // Make sure the counts are correct
542     let report_count_after_resolved =
543       PostReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
544     assert_eq!(1, report_count_after_resolved);
545
546     Person::delete(&conn, inserted_timmy.id).unwrap();
547     Person::delete(&conn, inserted_sara.id).unwrap();
548     Person::delete(&conn, inserted_jessica.id).unwrap();
549     Community::delete(&conn, inserted_community.id).unwrap();
550   }
551 }