]> Untitled Git - lemmy.git/blob - lemmy_api/src/comment.rs
remove timing files added by accident
[lemmy.git] / lemmy_api / src / comment.rs
1 use crate::{
2   check_community_ban,
3   collect_moderated_communities,
4   get_post,
5   get_user_from_jwt,
6   get_user_from_jwt_opt,
7   is_mod_or_admin,
8   Perform,
9 };
10 use actix_web::web::Data;
11 use lemmy_apub::{ApubLikeableType, ApubObjectType};
12 use lemmy_db::{
13   source::comment::Comment_,
14   views::{
15     comment_report_view::{CommentReportQueryBuilder, CommentReportView},
16     comment_view::{CommentQueryBuilder, CommentView},
17     site_view::SiteView,
18   },
19   Crud,
20   Likeable,
21   ListingType,
22   Reportable,
23   Saveable,
24   SortType,
25 };
26 use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*, post::Post, user::*};
27 use lemmy_structs::{blocking, comment::*, send_local_notifs};
28 use lemmy_utils::{
29   apub::{make_apub_endpoint, EndpointType},
30   utils::{remove_slurs, scrape_text_for_mentions},
31   APIError,
32   ConnectionId,
33   LemmyError,
34 };
35 use lemmy_websocket::{
36   messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
37   LemmyContext,
38   UserOperation,
39 };
40 use std::str::FromStr;
41
42 #[async_trait::async_trait(?Send)]
43 impl Perform for CreateComment {
44   type Response = CommentResponse;
45
46   async fn perform(
47     &self,
48     context: &Data<LemmyContext>,
49     websocket_id: Option<ConnectionId>,
50   ) -> Result<CommentResponse, LemmyError> {
51     let data: &CreateComment = &self;
52     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
53
54     let content_slurs_removed = remove_slurs(&data.content.to_owned());
55
56     let comment_form = CommentForm {
57       content: content_slurs_removed,
58       parent_id: data.parent_id.to_owned(),
59       post_id: data.post_id,
60       creator_id: user.id,
61       removed: None,
62       deleted: None,
63       read: None,
64       published: None,
65       updated: None,
66       ap_id: None,
67       local: true,
68     };
69
70     // Check for a community ban
71     let post_id = data.post_id;
72     let post = get_post(post_id, context.pool()).await?;
73
74     check_community_ban(user.id, post.community_id, context.pool()).await?;
75
76     // Check if post is locked, no new comments
77     if post.locked {
78       return Err(APIError::err("locked").into());
79     }
80
81     // Create the comment
82     let comment_form2 = comment_form.clone();
83     let inserted_comment = match blocking(context.pool(), move |conn| {
84       Comment::create(&conn, &comment_form2)
85     })
86     .await?
87     {
88       Ok(comment) => comment,
89       Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
90     };
91
92     // Necessary to update the ap_id
93     let inserted_comment_id = inserted_comment.id;
94     let updated_comment: Comment = match blocking(context.pool(), move |conn| {
95       let apub_id =
96         make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
97       Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
98     })
99     .await?
100     {
101       Ok(comment) => comment,
102       Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
103     };
104
105     updated_comment.send_create(&user, context).await?;
106
107     // Scan the comment for user mentions, add those rows
108     let mentions = scrape_text_for_mentions(&comment_form.content);
109     let recipient_ids = send_local_notifs(
110       mentions,
111       updated_comment.clone(),
112       &user,
113       post,
114       context.pool(),
115       true,
116     )
117     .await?;
118
119     // You like your own comment by default
120     let like_form = CommentLikeForm {
121       comment_id: inserted_comment.id,
122       post_id: data.post_id,
123       user_id: user.id,
124       score: 1,
125     };
126
127     let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
128     if blocking(context.pool(), like).await?.is_err() {
129       return Err(APIError::err("couldnt_like_comment").into());
130     }
131
132     updated_comment.send_like(&user, context).await?;
133
134     let user_id = user.id;
135     let comment_view = blocking(context.pool(), move |conn| {
136       CommentView::read(&conn, inserted_comment.id, Some(user_id))
137     })
138     .await??;
139
140     let mut res = CommentResponse {
141       comment_view,
142       recipient_ids,
143       form_id: data.form_id.to_owned(),
144     };
145
146     context.chat_server().do_send(SendComment {
147       op: UserOperation::CreateComment,
148       comment: res.clone(),
149       websocket_id,
150     });
151
152     // strip out the recipient_ids, so that
153     // users don't get double notifs
154     res.recipient_ids = Vec::new();
155
156     Ok(res)
157   }
158 }
159
160 #[async_trait::async_trait(?Send)]
161 impl Perform for EditComment {
162   type Response = CommentResponse;
163
164   async fn perform(
165     &self,
166     context: &Data<LemmyContext>,
167     websocket_id: Option<ConnectionId>,
168   ) -> Result<CommentResponse, LemmyError> {
169     let data: &EditComment = &self;
170     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
171
172     let edit_id = data.edit_id;
173     let orig_comment = blocking(context.pool(), move |conn| {
174       CommentView::read(&conn, edit_id, None)
175     })
176     .await??;
177
178     check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
179
180     // Verify that only the creator can edit
181     if user.id != orig_comment.creator.id {
182       return Err(APIError::err("no_comment_edit_allowed").into());
183     }
184
185     // Do the update
186     let content_slurs_removed = remove_slurs(&data.content.to_owned());
187     let edit_id = data.edit_id;
188     let updated_comment = match blocking(context.pool(), move |conn| {
189       Comment::update_content(conn, edit_id, &content_slurs_removed)
190     })
191     .await?
192     {
193       Ok(comment) => comment,
194       Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
195     };
196
197     // Send the apub update
198     updated_comment.send_update(&user, context).await?;
199
200     // Do the mentions / recipients
201     let post_id = orig_comment.post.id;
202     let post = get_post(post_id, context.pool()).await?;
203
204     let updated_comment_content = updated_comment.content.to_owned();
205     let mentions = scrape_text_for_mentions(&updated_comment_content);
206     let recipient_ids = send_local_notifs(
207       mentions,
208       updated_comment,
209       &user,
210       post,
211       context.pool(),
212       false,
213     )
214     .await?;
215
216     let edit_id = data.edit_id;
217     let user_id = user.id;
218     let comment_view = blocking(context.pool(), move |conn| {
219       CommentView::read(conn, edit_id, Some(user_id))
220     })
221     .await??;
222
223     let mut res = CommentResponse {
224       comment_view,
225       recipient_ids,
226       form_id: data.form_id.to_owned(),
227     };
228
229     context.chat_server().do_send(SendComment {
230       op: UserOperation::EditComment,
231       comment: res.clone(),
232       websocket_id,
233     });
234
235     // strip out the recipient_ids, so that
236     // users don't get double notifs
237     res.recipient_ids = Vec::new();
238
239     Ok(res)
240   }
241 }
242
243 #[async_trait::async_trait(?Send)]
244 impl Perform for DeleteComment {
245   type Response = CommentResponse;
246
247   async fn perform(
248     &self,
249     context: &Data<LemmyContext>,
250     websocket_id: Option<ConnectionId>,
251   ) -> Result<CommentResponse, LemmyError> {
252     let data: &DeleteComment = &self;
253     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
254
255     let edit_id = data.edit_id;
256     let orig_comment = blocking(context.pool(), move |conn| {
257       CommentView::read(&conn, edit_id, None)
258     })
259     .await??;
260
261     check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
262
263     // Verify that only the creator can delete
264     if user.id != orig_comment.creator.id {
265       return Err(APIError::err("no_comment_edit_allowed").into());
266     }
267
268     // Do the delete
269     let deleted = data.deleted;
270     let updated_comment = match blocking(context.pool(), move |conn| {
271       Comment::update_deleted(conn, edit_id, deleted)
272     })
273     .await?
274     {
275       Ok(comment) => comment,
276       Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
277     };
278
279     // Send the apub message
280     if deleted {
281       updated_comment.send_delete(&user, context).await?;
282     } else {
283       updated_comment.send_undo_delete(&user, context).await?;
284     }
285
286     // Refetch it
287     let edit_id = data.edit_id;
288     let user_id = user.id;
289     let comment_view = blocking(context.pool(), move |conn| {
290       CommentView::read(conn, edit_id, Some(user_id))
291     })
292     .await??;
293
294     // Build the recipients
295     let post_id = comment_view.post.id;
296     let post = get_post(post_id, context.pool()).await?;
297     let mentions = vec![];
298     let recipient_ids = send_local_notifs(
299       mentions,
300       updated_comment,
301       &user,
302       post,
303       context.pool(),
304       false,
305     )
306     .await?;
307
308     let mut res = CommentResponse {
309       comment_view,
310       recipient_ids,
311       form_id: None,
312     };
313
314     context.chat_server().do_send(SendComment {
315       op: UserOperation::DeleteComment,
316       comment: res.clone(),
317       websocket_id,
318     });
319
320     // strip out the recipient_ids, so that
321     // users don't get double notifs
322     res.recipient_ids = Vec::new();
323
324     Ok(res)
325   }
326 }
327
328 #[async_trait::async_trait(?Send)]
329 impl Perform for RemoveComment {
330   type Response = CommentResponse;
331
332   async fn perform(
333     &self,
334     context: &Data<LemmyContext>,
335     websocket_id: Option<ConnectionId>,
336   ) -> Result<CommentResponse, LemmyError> {
337     let data: &RemoveComment = &self;
338     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
339
340     let edit_id = data.edit_id;
341     let orig_comment = blocking(context.pool(), move |conn| {
342       CommentView::read(&conn, edit_id, None)
343     })
344     .await??;
345
346     check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
347
348     // Verify that only a mod or admin can remove
349     is_mod_or_admin(context.pool(), user.id, orig_comment.community.id).await?;
350
351     // Do the remove
352     let removed = data.removed;
353     let updated_comment = match blocking(context.pool(), move |conn| {
354       Comment::update_removed(conn, edit_id, removed)
355     })
356     .await?
357     {
358       Ok(comment) => comment,
359       Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
360     };
361
362     // Mod tables
363     let form = ModRemoveCommentForm {
364       mod_user_id: user.id,
365       comment_id: data.edit_id,
366       removed: Some(removed),
367       reason: data.reason.to_owned(),
368     };
369     blocking(context.pool(), move |conn| {
370       ModRemoveComment::create(conn, &form)
371     })
372     .await??;
373
374     // Send the apub message
375     if removed {
376       updated_comment.send_remove(&user, context).await?;
377     } else {
378       updated_comment.send_undo_remove(&user, context).await?;
379     }
380
381     // Refetch it
382     let edit_id = data.edit_id;
383     let user_id = user.id;
384     let comment_view = blocking(context.pool(), move |conn| {
385       CommentView::read(conn, edit_id, Some(user_id))
386     })
387     .await??;
388
389     // Build the recipients
390     let post_id = comment_view.post.id;
391     let post = get_post(post_id, context.pool()).await?;
392     let mentions = vec![];
393     let recipient_ids = send_local_notifs(
394       mentions,
395       updated_comment,
396       &user,
397       post,
398       context.pool(),
399       false,
400     )
401     .await?;
402
403     let mut res = CommentResponse {
404       comment_view,
405       recipient_ids,
406       form_id: None,
407     };
408
409     context.chat_server().do_send(SendComment {
410       op: UserOperation::RemoveComment,
411       comment: res.clone(),
412       websocket_id,
413     });
414
415     // strip out the recipient_ids, so that
416     // users don't get double notifs
417     res.recipient_ids = Vec::new();
418
419     Ok(res)
420   }
421 }
422
423 #[async_trait::async_trait(?Send)]
424 impl Perform for MarkCommentAsRead {
425   type Response = CommentResponse;
426
427   async fn perform(
428     &self,
429     context: &Data<LemmyContext>,
430     _websocket_id: Option<ConnectionId>,
431   ) -> Result<CommentResponse, LemmyError> {
432     let data: &MarkCommentAsRead = &self;
433     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
434
435     let edit_id = data.edit_id;
436     let orig_comment = blocking(context.pool(), move |conn| {
437       CommentView::read(&conn, edit_id, None)
438     })
439     .await??;
440
441     check_community_ban(user.id, orig_comment.community.id, context.pool()).await?;
442
443     // Verify that only the recipient can mark as read
444     // Needs to fetch the parent comment / post to get the recipient
445     let parent_id = orig_comment.comment.parent_id;
446     match parent_id {
447       Some(pid) => {
448         let parent_comment = blocking(context.pool(), move |conn| {
449           CommentView::read(&conn, pid, None)
450         })
451         .await??;
452         if user.id != parent_comment.creator.id {
453           return Err(APIError::err("no_comment_edit_allowed").into());
454         }
455       }
456       None => {
457         let parent_post_id = orig_comment.post.id;
458         let parent_post =
459           blocking(context.pool(), move |conn| Post::read(conn, parent_post_id)).await??;
460         if user.id != parent_post.creator_id {
461           return Err(APIError::err("no_comment_edit_allowed").into());
462         }
463       }
464     }
465
466     // Do the mark as read
467     let read = data.read;
468     match blocking(context.pool(), move |conn| {
469       Comment::update_read(conn, edit_id, read)
470     })
471     .await?
472     {
473       Ok(comment) => comment,
474       Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
475     };
476
477     // Refetch it
478     let edit_id = data.edit_id;
479     let user_id = user.id;
480     let comment_view = blocking(context.pool(), move |conn| {
481       CommentView::read(conn, edit_id, Some(user_id))
482     })
483     .await??;
484
485     let res = CommentResponse {
486       comment_view,
487       recipient_ids: Vec::new(),
488       form_id: None,
489     };
490
491     Ok(res)
492   }
493 }
494
495 #[async_trait::async_trait(?Send)]
496 impl Perform for SaveComment {
497   type Response = CommentResponse;
498
499   async fn perform(
500     &self,
501     context: &Data<LemmyContext>,
502     _websocket_id: Option<ConnectionId>,
503   ) -> Result<CommentResponse, LemmyError> {
504     let data: &SaveComment = &self;
505     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
506
507     let comment_saved_form = CommentSavedForm {
508       comment_id: data.comment_id,
509       user_id: user.id,
510     };
511
512     if data.save {
513       let save_comment = move |conn: &'_ _| CommentSaved::save(conn, &comment_saved_form);
514       if blocking(context.pool(), save_comment).await?.is_err() {
515         return Err(APIError::err("couldnt_save_comment").into());
516       }
517     } else {
518       let unsave_comment = move |conn: &'_ _| CommentSaved::unsave(conn, &comment_saved_form);
519       if blocking(context.pool(), unsave_comment).await?.is_err() {
520         return Err(APIError::err("couldnt_save_comment").into());
521       }
522     }
523
524     let comment_id = data.comment_id;
525     let user_id = user.id;
526     let comment_view = blocking(context.pool(), move |conn| {
527       CommentView::read(conn, comment_id, Some(user_id))
528     })
529     .await??;
530
531     Ok(CommentResponse {
532       comment_view,
533       recipient_ids: Vec::new(),
534       form_id: None,
535     })
536   }
537 }
538
539 #[async_trait::async_trait(?Send)]
540 impl Perform for CreateCommentLike {
541   type Response = CommentResponse;
542
543   async fn perform(
544     &self,
545     context: &Data<LemmyContext>,
546     websocket_id: Option<ConnectionId>,
547   ) -> Result<CommentResponse, LemmyError> {
548     let data: &CreateCommentLike = &self;
549     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
550
551     let mut recipient_ids = Vec::new();
552
553     // Don't do a downvote if site has downvotes disabled
554     if data.score == -1 {
555       let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
556       if !site_view.site.enable_downvotes {
557         return Err(APIError::err("downvotes_disabled").into());
558       }
559     }
560
561     let comment_id = data.comment_id;
562     let orig_comment = blocking(context.pool(), move |conn| {
563       CommentView::read(&conn, comment_id, None)
564     })
565     .await??;
566
567     let post_id = orig_comment.post.id;
568     let post = get_post(post_id, context.pool()).await?;
569     check_community_ban(user.id, post.community_id, context.pool()).await?;
570
571     let comment_id = data.comment_id;
572     let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
573
574     // Add to recipient ids
575     match comment.parent_id {
576       Some(parent_id) => {
577         let parent_comment =
578           blocking(context.pool(), move |conn| Comment::read(conn, parent_id)).await??;
579         if parent_comment.creator_id != user.id {
580           let parent_user = blocking(context.pool(), move |conn| {
581             User_::read(conn, parent_comment.creator_id)
582           })
583           .await??;
584           recipient_ids.push(parent_user.id);
585         }
586       }
587       None => {
588         recipient_ids.push(post.creator_id);
589       }
590     }
591
592     let like_form = CommentLikeForm {
593       comment_id: data.comment_id,
594       post_id,
595       user_id: user.id,
596       score: data.score,
597     };
598
599     // Remove any likes first
600     let user_id = user.id;
601     blocking(context.pool(), move |conn| {
602       CommentLike::remove(conn, user_id, comment_id)
603     })
604     .await??;
605
606     // Only add the like if the score isnt 0
607     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
608     if do_add {
609       let like_form2 = like_form.clone();
610       let like = move |conn: &'_ _| CommentLike::like(conn, &like_form2);
611       if blocking(context.pool(), like).await?.is_err() {
612         return Err(APIError::err("couldnt_like_comment").into());
613       }
614
615       if like_form.score == 1 {
616         comment.send_like(&user, context).await?;
617       } else if like_form.score == -1 {
618         comment.send_dislike(&user, context).await?;
619       }
620     } else {
621       comment.send_undo_like(&user, context).await?;
622     }
623
624     // Have to refetch the comment to get the current state
625     let comment_id = data.comment_id;
626     let user_id = user.id;
627     let liked_comment = blocking(context.pool(), move |conn| {
628       CommentView::read(conn, comment_id, Some(user_id))
629     })
630     .await??;
631
632     let mut res = CommentResponse {
633       comment_view: liked_comment,
634       recipient_ids,
635       form_id: None,
636     };
637
638     context.chat_server().do_send(SendComment {
639       op: UserOperation::CreateCommentLike,
640       comment: res.clone(),
641       websocket_id,
642     });
643
644     // strip out the recipient_ids, so that
645     // users don't get double notifs
646     res.recipient_ids = Vec::new();
647
648     Ok(res)
649   }
650 }
651
652 #[async_trait::async_trait(?Send)]
653 impl Perform for GetComments {
654   type Response = GetCommentsResponse;
655
656   async fn perform(
657     &self,
658     context: &Data<LemmyContext>,
659     _websocket_id: Option<ConnectionId>,
660   ) -> Result<GetCommentsResponse, LemmyError> {
661     let data: &GetComments = &self;
662     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
663     let user_id = user.map(|u| u.id);
664
665     let type_ = ListingType::from_str(&data.type_)?;
666     let sort = SortType::from_str(&data.sort)?;
667
668     let community_id = data.community_id;
669     let community_name = data.community_name.to_owned();
670     let page = data.page;
671     let limit = data.limit;
672     let comments = blocking(context.pool(), move |conn| {
673       CommentQueryBuilder::create(conn)
674         .listing_type(type_)
675         .sort(&sort)
676         .community_id(community_id)
677         .community_name(community_name)
678         .my_user_id(user_id)
679         .page(page)
680         .limit(limit)
681         .list()
682     })
683     .await?;
684     let comments = match comments {
685       Ok(comments) => comments,
686       Err(_) => return Err(APIError::err("couldnt_get_comments").into()),
687     };
688
689     Ok(GetCommentsResponse { comments })
690   }
691 }
692
693 /// Creates a comment report and notifies the moderators of the community
694 #[async_trait::async_trait(?Send)]
695 impl Perform for CreateCommentReport {
696   type Response = CreateCommentReportResponse;
697
698   async fn perform(
699     &self,
700     context: &Data<LemmyContext>,
701     websocket_id: Option<ConnectionId>,
702   ) -> Result<CreateCommentReportResponse, LemmyError> {
703     let data: &CreateCommentReport = &self;
704     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
705
706     // check size of report and check for whitespace
707     let reason = data.reason.trim();
708     if reason.is_empty() {
709       return Err(APIError::err("report_reason_required").into());
710     }
711     if reason.len() > 1000 {
712       return Err(APIError::err("report_too_long").into());
713     }
714
715     let user_id = user.id;
716     let comment_id = data.comment_id;
717     let comment_view = blocking(context.pool(), move |conn| {
718       CommentView::read(&conn, comment_id, None)
719     })
720     .await??;
721
722     check_community_ban(user_id, comment_view.community.id, context.pool()).await?;
723
724     let report_form = CommentReportForm {
725       creator_id: user_id,
726       comment_id,
727       original_comment_text: comment_view.comment.content,
728       reason: data.reason.to_owned(),
729     };
730
731     let report = match blocking(context.pool(), move |conn| {
732       CommentReport::report(conn, &report_form)
733     })
734     .await?
735     {
736       Ok(report) => report,
737       Err(_e) => return Err(APIError::err("couldnt_create_report").into()),
738     };
739
740     let res = CreateCommentReportResponse { success: true };
741
742     context.chat_server().do_send(SendUserRoomMessage {
743       op: UserOperation::CreateCommentReport,
744       response: res.clone(),
745       recipient_id: user.id,
746       websocket_id,
747     });
748
749     context.chat_server().do_send(SendModRoomMessage {
750       op: UserOperation::CreateCommentReport,
751       response: report,
752       community_id: comment_view.community.id,
753       websocket_id,
754     });
755
756     Ok(res)
757   }
758 }
759
760 /// Resolves or unresolves a comment report and notifies the moderators of the community
761 #[async_trait::async_trait(?Send)]
762 impl Perform for ResolveCommentReport {
763   type Response = ResolveCommentReportResponse;
764
765   async fn perform(
766     &self,
767     context: &Data<LemmyContext>,
768     websocket_id: Option<ConnectionId>,
769   ) -> Result<ResolveCommentReportResponse, LemmyError> {
770     let data: &ResolveCommentReport = &self;
771     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
772
773     let report_id = data.report_id;
774     let report = blocking(context.pool(), move |conn| {
775       CommentReportView::read(&conn, report_id)
776     })
777     .await??;
778
779     let user_id = user.id;
780     is_mod_or_admin(context.pool(), user_id, report.community.id).await?;
781
782     let resolved = data.resolved;
783     let resolve_fun = move |conn: &'_ _| {
784       if resolved {
785         CommentReport::resolve(conn, report_id, user_id)
786       } else {
787         CommentReport::unresolve(conn, report_id, user_id)
788       }
789     };
790
791     if blocking(context.pool(), resolve_fun).await?.is_err() {
792       return Err(APIError::err("couldnt_resolve_report").into());
793     };
794
795     let report_id = data.report_id;
796     let res = ResolveCommentReportResponse {
797       report_id,
798       resolved,
799     };
800
801     context.chat_server().do_send(SendModRoomMessage {
802       op: UserOperation::ResolveCommentReport,
803       response: res.clone(),
804       community_id: report.community.id,
805       websocket_id,
806     });
807
808     Ok(res)
809   }
810 }
811
812 /// Lists comment reports for a community if an id is supplied
813 /// or returns all comment reports for communities a user moderates
814 #[async_trait::async_trait(?Send)]
815 impl Perform for ListCommentReports {
816   type Response = ListCommentReportsResponse;
817
818   async fn perform(
819     &self,
820     context: &Data<LemmyContext>,
821     websocket_id: Option<ConnectionId>,
822   ) -> Result<ListCommentReportsResponse, LemmyError> {
823     let data: &ListCommentReports = &self;
824     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
825
826     let user_id = user.id;
827     let community_id = data.community;
828     let community_ids =
829       collect_moderated_communities(user_id, community_id, context.pool()).await?;
830
831     let page = data.page;
832     let limit = data.limit;
833     let comments = blocking(context.pool(), move |conn| {
834       CommentReportQueryBuilder::create(conn)
835         .community_ids(community_ids)
836         .page(page)
837         .limit(limit)
838         .list()
839     })
840     .await??;
841
842     let res = ListCommentReportsResponse { comments };
843
844     context.chat_server().do_send(SendUserRoomMessage {
845       op: UserOperation::ListCommentReports,
846       response: res.clone(),
847       recipient_id: user.id,
848       websocket_id,
849     });
850
851     Ok(res)
852   }
853 }