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