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