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