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