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