]> Untitled Git - lemmy.git/blob - crates/db_views/src/post_report_view.rs
Adding check for requests with no id or name, adding max limit. (#2265)
[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       ..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         hidden: false,
403         posting_restricted_to_mods: false,
404         published: inserted_community.published,
405       },
406       creator: PersonSafe {
407         id: inserted_jessica.id,
408         name: inserted_jessica.name,
409         display_name: None,
410         published: inserted_jessica.published,
411         avatar: None,
412         actor_id: inserted_jessica.actor_id.to_owned(),
413         local: true,
414         banned: false,
415         deleted: false,
416         admin: false,
417         bot_account: false,
418         bio: None,
419         banner: None,
420         updated: None,
421         inbox_url: inserted_jessica.inbox_url.to_owned(),
422         shared_inbox_url: None,
423         matrix_user_id: None,
424         ban_expires: 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         ban_expires: None,
445       },
446       creator_banned_from_community: false,
447       my_vote: None,
448       counts: PostAggregates {
449         id: agg.id,
450         post_id: inserted_post.id,
451         comments: 0,
452         score: 0,
453         upvotes: 0,
454         downvotes: 0,
455         stickied: false,
456         published: agg.published,
457         newest_comment_time_necro: inserted_post.published,
458         newest_comment_time: inserted_post.published,
459       },
460       resolver: None,
461     };
462
463     assert_eq!(read_jessica_report_view, expected_jessica_report_view);
464
465     let mut expected_sara_report_view = expected_jessica_report_view.clone();
466     expected_sara_report_view.post_report = inserted_sara_report;
467     expected_sara_report_view.my_vote = None;
468     expected_sara_report_view.creator = PersonSafe {
469       id: inserted_sara.id,
470       name: inserted_sara.name,
471       display_name: None,
472       published: inserted_sara.published,
473       avatar: None,
474       actor_id: inserted_sara.actor_id.to_owned(),
475       local: true,
476       banned: false,
477       deleted: false,
478       admin: false,
479       bot_account: false,
480       bio: None,
481       banner: None,
482       updated: None,
483       inbox_url: inserted_sara.inbox_url.to_owned(),
484       shared_inbox_url: None,
485       matrix_user_id: None,
486       ban_expires: None,
487     };
488
489     // Do a batch read of timmys reports
490     let reports = PostReportQueryBuilder::create(&conn, inserted_timmy.id, false)
491       .list()
492       .unwrap();
493
494     assert_eq!(
495       reports,
496       [
497         expected_jessica_report_view.to_owned(),
498         expected_sara_report_view.to_owned()
499       ]
500     );
501
502     // Make sure the counts are correct
503     let report_count =
504       PostReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
505     assert_eq!(2, report_count);
506
507     // Try to resolve the report
508     PostReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
509     let read_jessica_report_view_after_resolve =
510       PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
511
512     let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
513     expected_jessica_report_view_after_resolve
514       .post_report
515       .resolved = true;
516     expected_jessica_report_view_after_resolve
517       .post_report
518       .resolver_id = Some(inserted_timmy.id);
519     expected_jessica_report_view_after_resolve
520       .post_report
521       .updated = read_jessica_report_view_after_resolve.post_report.updated;
522     expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 {
523       id: inserted_timmy.id,
524       name: inserted_timmy.name.to_owned(),
525       display_name: None,
526       published: inserted_timmy.published,
527       avatar: None,
528       actor_id: inserted_timmy.actor_id.to_owned(),
529       local: true,
530       banned: false,
531       deleted: false,
532       admin: false,
533       bot_account: false,
534       bio: None,
535       banner: None,
536       updated: None,
537       inbox_url: inserted_timmy.inbox_url.to_owned(),
538       shared_inbox_url: None,
539       matrix_user_id: None,
540       ban_expires: None,
541     });
542
543     assert_eq!(
544       read_jessica_report_view_after_resolve,
545       expected_jessica_report_view_after_resolve
546     );
547
548     // Do a batch read of timmys reports
549     // It should only show saras, which is unresolved
550     let reports_after_resolve = PostReportQueryBuilder::create(&conn, inserted_timmy.id, false)
551       .list()
552       .unwrap();
553     assert_eq!(reports_after_resolve[0], expected_sara_report_view);
554
555     // Make sure the counts are correct
556     let report_count_after_resolved =
557       PostReportView::get_report_count(&conn, inserted_timmy.id, false, None).unwrap();
558     assert_eq!(1, report_count_after_resolved);
559
560     Person::delete(&conn, inserted_timmy.id).unwrap();
561     Person::delete(&conn, inserted_sara.id).unwrap();
562     Person::delete(&conn, inserted_jessica.id).unwrap();
563     Community::delete(&conn, inserted_community.id).unwrap();
564   }
565 }