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