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