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