]> Untitled Git - lemmy.git/blobdiff - crates/api/src/comment.rs
Rewrite fetcher (#1792)
[lemmy.git] / crates / api / src / comment.rs
index 711e8e725f4f5bbc38200665b2725b465ed1f084..0af3324fae0b082159496a614a384ff858081944 100644 (file)
-use crate::{
+use crate::Perform;
+use actix_web::web::Data;
+use lemmy_api_common::{
+  blocking,
   check_community_ban,
   check_downvotes_enabled,
-  collect_moderated_communities,
-  get_post,
+  check_person_block,
+  comment::*,
   get_local_user_view_from_jwt,
-  get_local_user_view_from_jwt_opt,
-  is_mod_or_admin,
-  Perform,
-};
-use actix_web::web::Data;
-use lemmy_api_structs::{blocking, comment::*, send_local_notifs};
-use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
-use lemmy_db_queries::{
-  source::comment::Comment_,
-  Crud,
-  Likeable,
-  ListingType,
-  Reportable,
-  Saveable,
-  SortType,
 };
-use lemmy_db_schema::source::{comment::*, comment_report::*, moderator::*};
-use lemmy_db_views::{
-  comment_report_view::{CommentReportQueryBuilder, CommentReportView},
-  comment_view::{CommentQueryBuilder, CommentView},
+use lemmy_apub::{
+  activities::voting::{
+    undo_vote::UndoVote,
+    vote::{Vote, VoteType},
+  },
+  fetcher::post_or_comment::PostOrComment,
 };
-use lemmy_utils::{
-  utils::{remove_slurs, scrape_text_for_mentions},
-  ApiError,
-  ConnectionId,
-  LemmyError,
-};
-use lemmy_websocket::{
-  messages::{SendComment, SendModRoomMessage, SendUserRoomMessage},
-  LemmyContext,
-  UserOperation,
-};
-use std::str::FromStr;
-
-#[async_trait::async_trait(?Send)]
-impl Perform for CreateComment {
-  type Response = CommentResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommentResponse, LemmyError> {
-    let data: &CreateComment = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let content_slurs_removed = remove_slurs(&data.content.to_owned());
-
-    // Check for a community ban
-    let post_id = data.post_id;
-    let post = get_post(post_id, context.pool()).await?;
-
-    check_community_ban(local_user_view.person.id, post.community_id, context.pool()).await?;
-
-    // Check if post is locked, no new comments
-    if post.locked {
-      return Err(ApiError::err("locked").into());
-    }
-
-    // If there's a parent_id, check to make sure that comment is in that post
-    if let Some(parent_id) = data.parent_id {
-      // Make sure the parent comment exists
-      let parent =
-        match blocking(context.pool(), move |conn| Comment::read(&conn, parent_id)).await? {
-          Ok(comment) => comment,
-          Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
-        };
-      if parent.post_id != post_id {
-        return Err(ApiError::err("couldnt_create_comment").into());
-      }
-    }
-
-    let comment_form = CommentForm {
-      content: content_slurs_removed,
-      parent_id: data.parent_id.to_owned(),
-      post_id: data.post_id,
-      creator_id: local_user_view.person.id,
-      removed: None,
-      deleted: None,
-      read: None,
-      published: None,
-      updated: None,
-      ap_id: None,
-      local: true,
-    };
-
-    // Create the comment
-    let comment_form2 = comment_form.clone();
-    let inserted_comment = match blocking(context.pool(), move |conn| {
-      Comment::create(&conn, &comment_form2)
-    })
-    .await?
-    {
-      Ok(comment) => comment,
-      Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
-    };
-
-    // Necessary to update the ap_id
-    let inserted_comment_id = inserted_comment.id;
-    let updated_comment: Comment =
-      match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
-        let apub_id =
-          generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?;
-        Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?)
-      })
-      .await?
-      {
-        Ok(comment) => comment,
-        Err(_e) => return Err(ApiError::err("couldnt_create_comment").into()),
-      };
-
-    updated_comment.send_create(&local_user_view.person, context).await?;
-
-    // Scan the comment for user mentions, add those rows
-    let post_id = post.id;
-    let mentions = scrape_text_for_mentions(&comment_form.content);
-    let recipient_ids = send_local_notifs(
-      mentions,
-      updated_comment.clone(),
-      local_user_view.person.clone(),
-      post,
-      context.pool(),
-      true,
-    )
-    .await?;
-
-    // You like your own comment by default
-    let like_form = CommentLikeForm {
-      comment_id: inserted_comment.id,
-      post_id,
-      person_id: local_user_view.person.id,
-      score: 1,
-    };
-
-    let like = move |conn: &'_ _| CommentLike::like(&conn, &like_form);
-    if blocking(context.pool(), like).await?.is_err() {
-      return Err(ApiError::err("couldnt_like_comment").into());
-    }
-
-    updated_comment.send_like(&local_user_view.person, context).await?;
-
-    let person_id = local_user_view.person.id;
-    let mut comment_view = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, inserted_comment.id, Some(person_id))
-    })
-    .await??;
-
-    // If its a comment to yourself, mark it as read
-    let comment_id = comment_view.comment.id;
-    if local_user_view.person.id == comment_view.get_recipient_id() {
-      match blocking(context.pool(), move |conn| {
-        Comment::update_read(conn, comment_id, true)
-      })
-      .await?
-      {
-        Ok(comment) => comment,
-        Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
-      };
-      comment_view.comment.read = true;
-    }
-
-    let mut res = CommentResponse {
-      comment_view,
-      recipient_ids,
-      form_id: data.form_id.to_owned(),
-    };
-
-    context.chat_server().do_send(SendComment {
-      op: UserOperation::CreateComment,
-      comment: res.clone(),
-      websocket_id,
-    });
-
-    res.recipient_ids = Vec::new(); // Necessary to avoid doubles
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for EditComment {
-  type Response = CommentResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommentResponse, LemmyError> {
-    let data: &EditComment = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let comment_id = data.comment_id;
-    let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
-    })
-    .await??;
-
-    check_community_ban(local_user_view.person.id, orig_comment.community.id, context.pool()).await?;
-
-    // Verify that only the creator can edit
-    if local_user_view.person.id != orig_comment.creator.id {
-      return Err(ApiError::err("no_comment_edit_allowed").into());
-    }
-
-    // Do the update
-    let content_slurs_removed = remove_slurs(&data.content.to_owned());
-    let comment_id = data.comment_id;
-    let updated_comment = match blocking(context.pool(), move |conn| {
-      Comment::update_content(conn, comment_id, &content_slurs_removed)
-    })
-    .await?
-    {
-      Ok(comment) => comment,
-      Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
-    };
-
-    // Send the apub update
-    updated_comment.send_update(&local_user_view.person, context).await?;
-
-    // Do the mentions / recipients
-    let updated_comment_content = updated_comment.content.to_owned();
-    let mentions = scrape_text_for_mentions(&updated_comment_content);
-    let recipient_ids = send_local_notifs(
-      mentions,
-      updated_comment,
-      local_user_view.person.clone(),
-      orig_comment.post,
-      context.pool(),
-      false,
-    )
-    .await?;
-
-    let comment_id = data.comment_id;
-    let person_id = local_user_view.person.id;
-    let comment_view = blocking(context.pool(), move |conn| {
-      CommentView::read(conn, comment_id, Some(person_id))
-    })
-    .await??;
-
-    let res = CommentResponse {
-      comment_view,
-      recipient_ids,
-      form_id: data.form_id.to_owned(),
-    };
-
-    context.chat_server().do_send(SendComment {
-      op: UserOperation::EditComment,
-      comment: res.clone(),
-      websocket_id,
-    });
-
-    Ok(res)
-  }
-}
+use lemmy_db_queries::{source::comment::Comment_, Likeable, Saveable};
+use lemmy_db_schema::{source::comment::*, LocalUserId};
+use lemmy_db_views::{comment_view::CommentView, local_user_view::LocalUserView};
+use lemmy_utils::{ApiError, ConnectionId, LemmyError};
+use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperation};
+use std::convert::TryInto;
 
 #[async_trait::async_trait(?Send)]
-impl Perform for DeleteComment {
+impl Perform for MarkCommentAsRead {
   type Response = CommentResponse;
 
   async fn perform(
     &self,
     context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
+    _websocket_id: Option<ConnectionId>,
   ) -> Result<CommentResponse, LemmyError> {
-    let data: &DeleteComment = &self;
+    let data: &MarkCommentAsRead = self;
     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
 
     let comment_id = data.comment_id;
     let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
+      CommentView::read(conn, comment_id, None)
     })
     .await??;
 
-    check_community_ban(local_user_view.person.id, orig_comment.community.id, context.pool()).await?;
-
-    // Verify that only the creator can delete
-    if local_user_view.person.id != orig_comment.creator.id {
-      return Err(ApiError::err("no_comment_edit_allowed").into());
-    }
-
-    // Do the delete
-    let deleted = data.deleted;
-    let updated_comment = match blocking(context.pool(), move |conn| {
-      Comment::update_deleted(conn, comment_id, deleted)
-    })
-    .await?
-    {
-      Ok(comment) => comment,
-      Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
-    };
-
-    // Send the apub message
-    if deleted {
-      updated_comment.send_delete(&local_user_view.person, context).await?;
-    } else {
-      updated_comment.send_undo_delete(&local_user_view.person, context).await?;
-    }
-
-    // Refetch it
-    let comment_id = data.comment_id;
-    let person_id = local_user_view.person.id;
-    let comment_view = blocking(context.pool(), move |conn| {
-      CommentView::read(conn, comment_id, Some(person_id))
-    })
-    .await??;
-
-    // Build the recipients
-    let comment_view_2 = comment_view.clone();
-    let mentions = vec![];
-    let recipient_ids = send_local_notifs(
-      mentions,
-      updated_comment,
-      local_user_view.person.clone(),
-      comment_view_2.post,
+    check_community_ban(
+      local_user_view.person.id,
+      orig_comment.community.id,
       context.pool(),
-      false,
     )
     .await?;
 
-    let res = CommentResponse {
-      comment_view,
-      recipient_ids,
-      form_id: None, // TODO a comment delete might clear forms?
-    };
-
-    context.chat_server().do_send(SendComment {
-      op: UserOperation::DeleteComment,
-      comment: res.clone(),
-      websocket_id,
-    });
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for RemoveComment {
-  type Response = CommentResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CommentResponse, LemmyError> {
-    let data: &RemoveComment = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let comment_id = data.comment_id;
-    let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
-    })
-    .await??;
-
-    check_community_ban(local_user_view.person.id, orig_comment.community.id, context.pool()).await?;
-
-    // Verify that only a mod or admin can remove
-    is_mod_or_admin(context.pool(), local_user_view.person.id, orig_comment.community.id).await?;
-
-    // Do the remove
-    let removed = data.removed;
-    let updated_comment = match blocking(context.pool(), move |conn| {
-      Comment::update_removed(conn, comment_id, removed)
-    })
-    .await?
-    {
-      Ok(comment) => comment,
-      Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
-    };
-
-    // Mod tables
-    let form = ModRemoveCommentForm {
-      mod_person_id: local_user_view.person.id,
-      comment_id: data.comment_id,
-      removed: Some(removed),
-      reason: data.reason.to_owned(),
-    };
-    blocking(context.pool(), move |conn| {
-      ModRemoveComment::create(conn, &form)
-    })
-    .await??;
-
-    // Send the apub message
-    if removed {
-      updated_comment.send_remove(&local_user_view.person, context).await?;
-    } else {
-      updated_comment.send_undo_remove(&local_user_view.person, context).await?;
-    }
-
-    // Refetch it
-    let comment_id = data.comment_id;
-    let person_id = local_user_view.person.id;
-    let comment_view = blocking(context.pool(), move |conn| {
-      CommentView::read(conn, comment_id, Some(person_id))
-    })
-    .await??;
-
-    // Build the recipients
-    let comment_view_2 = comment_view.clone();
-
-    let mentions = vec![];
-    let recipient_ids = send_local_notifs(
-      mentions,
-      updated_comment,
-      local_user_view.person.clone(),
-      comment_view_2.post,
-      context.pool(),
-      false,
-    )
-    .await?;
-
-    let res = CommentResponse {
-      comment_view,
-      recipient_ids,
-      form_id: None, // TODO maybe this might clear other forms
-    };
-
-    context.chat_server().do_send(SendComment {
-      op: UserOperation::RemoveComment,
-      comment: res.clone(),
-      websocket_id,
-    });
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for MarkCommentAsRead {
-  type Response = CommentResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<CommentResponse, LemmyError> {
-    let data: &MarkCommentAsRead = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let comment_id = data.comment_id;
-    let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
-    })
-    .await??;
-
-    check_community_ban(local_user_view.person.id, orig_comment.community.id, context.pool()).await?;
-
     // Verify that only the recipient can mark as read
     if local_user_view.person.id != orig_comment.get_recipient_id() {
       return Err(ApiError::err("no_comment_edit_allowed").into());
@@ -457,14 +54,11 @@ impl Perform for MarkCommentAsRead {
 
     // Do the mark as read
     let read = data.read;
-    match blocking(context.pool(), move |conn| {
+    blocking(context.pool(), move |conn| {
       Comment::update_read(conn, comment_id, read)
     })
     .await?
-    {
-      Ok(comment) => comment,
-      Err(_e) => return Err(ApiError::err("couldnt_update_comment").into()),
-    };
+    .map_err(|_| ApiError::err("couldnt_update_comment"))?;
 
     // Refetch it
     let comment_id = data.comment_id;
@@ -493,7 +87,7 @@ impl Perform for SaveComment {
     context: &Data<LemmyContext>,
     _websocket_id: Option<ConnectionId>,
   ) -> Result<CommentResponse, LemmyError> {
-    let data: &SaveComment = &self;
+    let data: &SaveComment = self;
     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
 
     let comment_saved_form = CommentSavedForm {
@@ -537,24 +131,43 @@ impl Perform for CreateCommentLike {
     context: &Data<LemmyContext>,
     websocket_id: Option<ConnectionId>,
   ) -> Result<CommentResponse, LemmyError> {
-    let data: &CreateCommentLike = &self;
+    let data: &CreateCommentLike = self;
     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
 
-    let mut recipient_ids = Vec::new();
+    let mut recipient_ids = Vec::<LocalUserId>::new();
 
     // Don't do a downvote if site has downvotes disabled
     check_downvotes_enabled(data.score, context.pool()).await?;
 
     let comment_id = data.comment_id;
     let orig_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
+      CommentView::read(conn, comment_id, None)
     })
     .await??;
 
-    check_community_ban(local_user_view.person.id, orig_comment.community.id, context.pool()).await?;
+    check_community_ban(
+      local_user_view.person.id,
+      orig_comment.community.id,
+      context.pool(),
+    )
+    .await?;
+
+    check_person_block(
+      local_user_view.person.id,
+      orig_comment.get_recipient_id(),
+      context.pool(),
+    )
+    .await?;
 
     // Add parent user to recipients
-    recipient_ids.push(orig_comment.get_recipient_id());
+    let recipient_id = orig_comment.get_recipient_id();
+    if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
+      LocalUserView::read_person(conn, recipient_id)
+    })
+    .await?
+    {
+      recipient_ids.push(local_recipient.local_user.id);
+    }
 
     let like_form = CommentLikeForm {
       comment_id: data.comment_id,
@@ -572,6 +185,7 @@ impl Perform for CreateCommentLike {
 
     // Only add the like if the score isnt 0
     let comment = orig_comment.comment;
+    let object = PostOrComment::Comment(Box::new(comment));
     let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1);
     if do_add {
       let like_form2 = like_form.clone();
@@ -580,238 +194,35 @@ impl Perform for CreateCommentLike {
         return Err(ApiError::err("couldnt_like_comment").into());
       }
 
-      if like_form.score == 1 {
-        comment.send_like(&local_user_view.person, context).await?;
-      } else if like_form.score == -1 {
-        comment.send_dislike(&local_user_view.person, context).await?;
-      }
+      Vote::send(
+        &object,
+        &local_user_view.person,
+        orig_comment.community.id,
+        like_form.score.try_into()?,
+        context,
+      )
+      .await?;
     } else {
-      comment.send_undo_like(&local_user_view.person, context).await?;
+      // API doesn't distinguish between Undo/Like and Undo/Dislike
+      UndoVote::send(
+        &object,
+        &local_user_view.person,
+        orig_comment.community.id,
+        VoteType::Like,
+        context,
+      )
+      .await?;
     }
 
-    // Have to refetch the comment to get the current state
-    let comment_id = data.comment_id;
-    let person_id = local_user_view.person.id;
-    let liked_comment = blocking(context.pool(), move |conn| {
-      CommentView::read(conn, comment_id, Some(person_id))
-    })
-    .await??;
-
-    let res = CommentResponse {
-      comment_view: liked_comment,
-      recipient_ids,
-      form_id: None,
-    };
-
-    context.chat_server().do_send(SendComment {
-      op: UserOperation::CreateCommentLike,
-      comment: res.clone(),
+    send_comment_ws_message(
+      data.comment_id,
+      UserOperation::CreateCommentLike,
       websocket_id,
-    });
-
-    Ok(res)
-  }
-}
-
-#[async_trait::async_trait(?Send)]
-impl Perform for GetComments {
-  type Response = GetCommentsResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    _websocket_id: Option<ConnectionId>,
-  ) -> Result<GetCommentsResponse, LemmyError> {
-    let data: &GetComments = &self;
-    let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
-    let person_id = local_user_view.map(|u| u.person.id);
-
-    let type_ = ListingType::from_str(&data.type_)?;
-    let sort = SortType::from_str(&data.sort)?;
-
-    let community_id = data.community_id;
-    let community_name = data.community_name.to_owned();
-    let page = data.page;
-    let limit = data.limit;
-    let comments = blocking(context.pool(), move |conn| {
-      CommentQueryBuilder::create(conn)
-        .listing_type(type_)
-        .sort(&sort)
-        .community_id(community_id)
-        .community_name(community_name)
-        .my_person_id(person_id)
-        .page(page)
-        .limit(limit)
-        .list()
-    })
-    .await?;
-    let comments = match comments {
-      Ok(comments) => comments,
-      Err(_) => return Err(ApiError::err("couldnt_get_comments").into()),
-    };
-
-    Ok(GetCommentsResponse { comments })
-  }
-}
-
-/// Creates a comment report and notifies the moderators of the community
-#[async_trait::async_trait(?Send)]
-impl Perform for CreateCommentReport {
-  type Response = CreateCommentReportResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<CreateCommentReportResponse, LemmyError> {
-    let data: &CreateCommentReport = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    // check size of report and check for whitespace
-    let reason = data.reason.trim();
-    if reason.is_empty() {
-      return Err(ApiError::err("report_reason_required").into());
-    }
-    if reason.chars().count() > 1000 {
-      return Err(ApiError::err("report_too_long").into());
-    }
-
-    let person_id = local_user_view.person.id;
-    let comment_id = data.comment_id;
-    let comment_view = blocking(context.pool(), move |conn| {
-      CommentView::read(&conn, comment_id, None)
-    })
-    .await??;
-
-    check_community_ban(person_id, comment_view.community.id, context.pool()).await?;
-
-    let report_form = CommentReportForm {
-      creator_id: person_id,
-      comment_id,
-      original_comment_text: comment_view.comment.content,
-      reason: data.reason.to_owned(),
-    };
-
-    let report = match blocking(context.pool(), move |conn| {
-      CommentReport::report(conn, &report_form)
-    })
-    .await?
-    {
-      Ok(report) => report,
-      Err(_e) => return Err(ApiError::err("couldnt_create_report").into()),
-    };
-
-    let res = CreateCommentReportResponse { success: true };
-
-    context.chat_server().do_send(SendUserRoomMessage {
-      op: UserOperation::CreateCommentReport,
-      response: res.clone(),
-      recipient_id: local_user_view.person.id,
-      websocket_id,
-    });
-
-    context.chat_server().do_send(SendModRoomMessage {
-      op: UserOperation::CreateCommentReport,
-      response: report,
-      community_id: comment_view.community.id,
-      websocket_id,
-    });
-
-    Ok(res)
-  }
-}
-
-/// Resolves or unresolves a comment report and notifies the moderators of the community
-#[async_trait::async_trait(?Send)]
-impl Perform for ResolveCommentReport {
-  type Response = ResolveCommentReportResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<ResolveCommentReportResponse, LemmyError> {
-    let data: &ResolveCommentReport = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let report_id = data.report_id;
-    let report = blocking(context.pool(), move |conn| {
-      CommentReportView::read(&conn, report_id)
-    })
-    .await??;
-
-    let person_id = local_user_view.person.id;
-    is_mod_or_admin(context.pool(), person_id, report.community.id).await?;
-
-    let resolved = data.resolved;
-    let resolve_fun = move |conn: &'_ _| {
-      if resolved {
-        CommentReport::resolve(conn, report_id, person_id)
-      } else {
-        CommentReport::unresolve(conn, report_id, person_id)
-      }
-    };
-
-    if blocking(context.pool(), resolve_fun).await?.is_err() {
-      return Err(ApiError::err("couldnt_resolve_report").into());
-    };
-
-    let report_id = data.report_id;
-    let res = ResolveCommentReportResponse {
-      report_id,
-      resolved,
-    };
-
-    context.chat_server().do_send(SendModRoomMessage {
-      op: UserOperation::ResolveCommentReport,
-      response: res.clone(),
-      community_id: report.community.id,
-      websocket_id,
-    });
-
-    Ok(res)
-  }
-}
-
-/// Lists comment reports for a community if an id is supplied
-/// or returns all comment reports for communities a user moderates
-#[async_trait::async_trait(?Send)]
-impl Perform for ListCommentReports {
-  type Response = ListCommentReportsResponse;
-
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<ListCommentReportsResponse, LemmyError> {
-    let data: &ListCommentReports = &self;
-    let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
-
-    let person_id = local_user_view.person.id;
-    let community_id = data.community;
-    let community_ids =
-      collect_moderated_communities(person_id, community_id, context.pool()).await?;
-
-    let page = data.page;
-    let limit = data.limit;
-    let comments = blocking(context.pool(), move |conn| {
-      CommentReportQueryBuilder::create(conn)
-        .community_ids(community_ids)
-        .page(page)
-        .limit(limit)
-        .list()
-    })
-    .await??;
-
-    let res = ListCommentReportsResponse { comments };
-
-    context.chat_server().do_send(SendUserRoomMessage {
-      op: UserOperation::ListCommentReports,
-      response: res.clone(),
-      recipient_id: local_user_view.person.id,
-      websocket_id,
-    });
-
-    Ok(res)
+      None,
+      Some(local_user_view.person.id),
+      recipient_ids,
+      context,
+    )
+    .await
   }
 }