Repository: LemmyNet/lemmy (language: rust)

Issue:
[API] `post_id` field in `ListCommentReportsRequest`
### Requirements

- [X] Is this a feature request? For questions or discussions use https://lemmy.ml/c/lemmy_support
- [X] Did you check to see if this issue already exists?
- [X] Is this only a feature request? Do not put multiple feature requests in one issue.
- [X] Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.
- [X] Do you agree to follow the rules in our [Code of Conduct](https://join-lemmy.org/docs/code_of_conduct.html)?

### Is your proposal related to a problem?

The `comment/report/list` endpoint already has a `community_id` field for filtering. It would be nice if I could filter by `post_id` too. This would allow a client to, for example, display reports inline in a comment chain. 

### Describe the solution you'd like.

Add a `post_id` field to `ListCommentReportsRequest`.

### Describe alternatives you've considered.

-

### Additional context

_No response_


Discussion / hints:
I'm currently in the process of removing that endpoint in favor of a combined one #5231 . I'll need to add a test to make sure what you're saying works correctly tho.
Great, thanks 👍 

Fix patch:
diff --git a/crates/api/src/reports/report_combined/list.rs b/crates/api/src/reports/report_combined/list.rs
index 12548d1891..62df915023 100644
--- a/crates/api/src/reports/report_combined/list.rs
+++ b/crates/api/src/reports/report_combined/list.rs
@@ -15,9 +15,6 @@ pub async fn list_reports(
   context: Data<LemmyContext>,
   local_user_view: LocalUserView,
 ) -> LemmyResult<Json<ListReportsResponse>> {
-  let community_id = data.community_id;
-  let unresolved_only = data.unresolved_only;
-
   check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
 
   // parse pagination token
@@ -29,8 +26,10 @@ pub async fn list_reports(
   let page_back = data.page_back;
 
   let reports = ReportCombinedQuery {
-    community_id,
-    unresolved_only,
+    community_id: data.community_id,
+    post_id: data.post_id,
+    type_: data.type_,
+    unresolved_only: data.unresolved_only,
     page_after,
     page_back,
   }
diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs
index dd1c0a68a3..fde2102d5b 100644
--- a/crates/api_common/src/lib.rs
+++ b/crates/api_common/src/lib.rs
@@ -35,7 +35,7 @@ use std::{cmp::min, time::Duration};
 #[derive(Debug, Serialize, Deserialize, Clone)]
 #[cfg_attr(feature = "full", derive(ts_rs::TS))]
 #[cfg_attr(feature = "full", ts(export))]
-/// Saves settings for your user.
+/// A response that completes successfully.
 pub struct SuccessResponse {
   pub success: bool,
 }
diff --git a/crates/api_common/src/reports/combined.rs b/crates/api_common/src/reports/combined.rs
index 69d9288305..cd87c8a3d3 100644
--- a/crates/api_common/src/reports/combined.rs
+++ b/crates/api_common/src/reports/combined.rs
@@ -1,4 +1,7 @@
-use lemmy_db_schema::newtypes::CommunityId;
+use lemmy_db_schema::{
+  newtypes::{CommunityId, PostId},
+  ReportType,
+};
 use lemmy_db_views::structs::{ReportCombinedPaginationCursor, ReportCombinedView};
 use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
@@ -14,6 +17,12 @@ pub struct ListReports {
   /// Only shows the unresolved reports
   #[cfg_attr(feature = "full", ts(optional))]
   pub unresolved_only: Option<bool>,
+  /// Filter the type of report.
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub type_: Option<ReportType>,
+  /// Filter by the post id. Can return either comment or post reports.
+  #[cfg_attr(feature = "full", ts(optional))]
+  pub post_id: Option<PostId>,
   /// if no community is given, it returns reports for all communities moderated by the auth user
   #[cfg_attr(feature = "full", ts(optional))]
   pub community_id: Option<CommunityId>,
diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs
index 9c0f0ea0fa..fd270e6d52 100644
--- a/crates/db_schema/src/lib.rs
+++ b/crates/db_schema/src/lib.rs
@@ -229,13 +229,25 @@ pub enum InboxDataType {
 #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
 #[cfg_attr(feature = "full", derive(TS))]
 #[cfg_attr(feature = "full", ts(export))]
-/// A list of possible types for the various modlog actions.
+/// A list of possible types for a person's content.
 pub enum PersonContentType {
   All,
   Comments,
   Posts,
 }
 
+#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
+#[cfg_attr(feature = "full", derive(TS))]
+#[cfg_attr(feature = "full", ts(export))]
+/// A list of possible types for reports.
+pub enum ReportType {
+  All,
+  Posts,
+  Comments,
+  PrivateMessages,
+  Communities,
+}
+
 #[derive(
   EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash,
 )]
diff --git a/crates/db_views/src/report_combined_view.rs b/crates/db_views/src/report_combined_view.rs
index 335fccb81d..201e02728b 100644
--- a/crates/db_views/src/report_combined_view.rs
+++ b/crates/db_views/src/report_combined_view.rs
@@ -22,7 +22,7 @@ use diesel_async::RunQueryDsl;
 use i_love_jesus::PaginatedQueryBuilder;
 use lemmy_db_schema::{
   aliases::{self, creator_community_actions},
-  newtypes::CommunityId,
+  newtypes::{CommunityId, PostId},
   schema::{
     comment,
     comment_actions,
@@ -49,6 +49,7 @@ use lemmy_db_schema::{
   },
   traits::InternalToCombinedView,
   utils::{actions, actions_alias, functions::coalesce, get_conn, DbPool, ReverseTimestampKey},
+  ReportType,
 };
 use lemmy_utils::error::LemmyResult;
 
@@ -148,6 +149,8 @@ pub struct PaginationCursorData(ReportCombined);
 
 #[derive(Default)]
 pub struct ReportCombinedQuery {
+  pub type_: Option<ReportType>,
+  pub post_id: Option<PostId>,
   pub community_id: Option<CommunityId>,
   pub unresolved_only: Option<bool>,
   pub page_after: Option<PaginationCursorData>,
@@ -167,6 +170,33 @@ impl ReportCombinedQuery {
 
     let conn = &mut get_conn(pool).await?;
 
+    let report_creator_join = post_report::creator_id
+      .eq(report_creator)
+      .or(comment_report::creator_id.eq(report_creator))
+      .or(private_message_report::creator_id.eq(report_creator))
+      .or(community_report::creator_id.eq(report_creator));
+
+    let item_creator_join = post::creator_id
+      .eq(item_creator)
+      .or(comment::creator_id.eq(item_creator))
+      .or(private_message::creator_id.eq(item_creator));
+
+    let resolver_join = private_message_report::resolver_id
+      .eq(resolver)
+      .or(post_report::resolver_id.eq(resolver))
+      .or(comment_report::resolver_id.eq(resolver))
+      .or(community_report::resolver_id.eq(resolver));
+
+    let post_join = post_report::post_id
+      .eq(post::id)
+      .or(comment::post_id.eq(post::id));
+
+    let community_join = community::table.on(
+      community_report::community_id
+        .eq(community::id)
+        .or(post::community_id.eq(community::id)),
+    );
+
     // Notes: since the post_report_id and comment_report_id are optional columns,
     // many joins must use an OR condition.
     // For example, the report creator must be the person table joined to either:
@@ -178,15 +208,7 @@ impl ReportCombinedQuery {
       .left_join(private_message_report::table)
       .left_join(community_report::table)
       // The report creator
-      .inner_join(
-        person::table.on(
-          post_report::creator_id
-            .eq(report_creator)
-            .or(comment_report::creator_id.eq(report_creator))
-            .or(private_message_report::creator_id.eq(report_creator))
-            .or(community_report::creator_id.eq(report_creator)),
-        ),
-      )
+      .inner_join(person::table.on(report_creator_join))
       // The comment
       .left_join(comment::table.on(comment_report::comment_id.eq(comment::id)))
       // The private message
@@ -195,30 +217,13 @@ impl ReportCombinedQuery {
           .on(private_message_report::private_message_id.eq(private_message::id)),
       )
       // The post
-      .left_join(
-        post::table.on(
-          post_report::post_id
-            .eq(post::id)
-            .or(comment::post_id.eq(post::id)),
-        ),
-      )
+      .left_join(post::table.on(post_join))
       // The item creator (`item_creator` is the id of this person)
-      .left_join(
-        aliases::person1.on(
-          post::creator_id
-            .eq(item_creator)
-            .or(comment::creator_id.eq(item_creator))
-            .or(private_message::creator_id.eq(item_creator)),
-        ),
-      )
+      .left_join(aliases::person1.on(item_creator_join))
+      // The resolver
+      .left_join(aliases::person2.on(resolver_join))
       // The community
-      .left_join(
-        community::table.on(
-          post::community_id
-            .eq(community::id)
-            .or(community_report::community_id.eq(community::id)),
-        ),
-      )
+      .left_join(community_join)
       .left_join(actions_alias(
         creator_community_actions,
         item_creator,
@@ -250,16 +255,6 @@ impl ReportCombinedQuery {
         community_aggregates::table
           .on(community_report::community_id.eq(community_aggregates::community_id)),
       )
-      // The resolver
-      .left_join(
-        aliases::person2.on(
-          private_message_report::resolver_id
-            .eq(resolver)
-            .or(post_report::resolver_id.eq(resolver))
-            .or(comment_report::resolver_id.eq(resolver))
-            .or(community_report::resolver_id.eq(resolver)),
-        ),
-      )
       .left_join(actions(
         comment_actions::table,
         Some(my_person_id),
@@ -318,6 +313,10 @@ impl ReportCombinedQuery {
       );
     }
 
+    if let Some(post_id) = self.post_id {
+      query = query.filter(post::id.eq(post_id));
+    }
+
     // If its not an admin, get only the ones you mod
     if !user.local_user.admin {
       query = query.filter(
@@ -337,6 +336,18 @@ impl ReportCombinedQuery {
       query = query.after(page_after);
     }
 
+    if let Some(type_) = self.type_ {
+      query = match type_ {
+        ReportType::All => query,
+        ReportType::Posts => query.filter(report_combined::post_report_id.is_not_null()),
+        ReportType::Comments => query.filter(report_combined::comment_report_id.is_not_null()),
+        ReportType::PrivateMessages => {
+          query.filter(report_combined::private_message_report_id.is_not_null())
+        }
+        ReportType::Communities => query.filter(report_combined::community_report_id.is_not_null()),
+      }
+    }
+
     // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest
     // first (FIFO)
     if self.unresolved_only.unwrap_or_default() {
@@ -508,6 +519,7 @@ mod tests {
     },
     traits::{Crud, Joinable, Reportable},
     utils::{build_db_pool_for_tests, DbPool},
+    ReportType,
   };
   use lemmy_utils::error::LemmyResult;
   use pretty_assertions::assert_eq;
@@ -677,7 +689,7 @@ mod tests {
     let reports = ReportCombinedQuery::default()
       .list(pool, &data.admin_view)
       .await?;
-    assert_eq!(4, reports.len());
+    assert_length!(4, reports);
 
     // Make sure the report types are correct
     if let ReportCombinedView::Community(v) = &reports[3] {
@@ -709,22 +721,41 @@ mod tests {
       ReportCombinedViewInternal::get_report_count(pool, &data.admin_view, None).await?;
     assert_eq!(4, report_count_admin);
 
+    // Make sure the type_ filter is working
+    let reports_by_type = ReportCombinedQuery {
+      type_: Some(ReportType::Posts),
+      ..Default::default()
+    }
+    .list(pool, &data.admin_view)
+    .await?;
+    assert_length!(1, reports_by_type);
+
+    // Filter by the post id
+    // Should be 2, for the post, and the comment on that post
+    let reports_by_post_id = ReportCombinedQuery {
+      post_id: Some(data.post.id),
+      ..Default::default()
+    }
+    .list(pool, &data.admin_view)
+    .await?;
+    assert_length!(2, reports_by_post_id);
+
     // Timmy should only see 2 reports, since they're not an admin,
     // but they do mod the community
-    let reports = ReportCombinedQuery::default()
+    let timmys_reports = ReportCombinedQuery::default()
       .list(pool, &data.timmy_view)
       .await?;
-    assert_eq!(2, reports.len());
+    assert_length!(2, timmys_reports);
 
     // Make sure the report types are correct
-    if let ReportCombinedView::Post(v) = &reports[1] {
+    if let ReportCombinedView::Post(v) = &timmys_reports[1] {
       assert_eq!(data.post.id, v.post.id);
       assert_eq!(data.sara.id, v.creator.id);
       assert_eq!(data.timmy.id, v.post_creator.id);
     } else {
       panic!("wrong type");
     }
-    if let ReportCombinedView::Comment(v) = &reports[0] {
+    if let ReportCombinedView::Comment(v) = &timmys_reports[0] {
       assert_eq!(data.comment.id, v.comment.id);
       assert_eq!(data.post.id, v.post.id);
       assert_eq!(data.timmy.id, v.comment_creator.id);
@@ -1050,6 +1081,16 @@ mod tests {
       ReportCombinedViewInternal::get_report_count(pool, &data.timmy_view, None).await?;
     assert_eq!(1, report_count_after_resolved);
 
+    // Filter by post id, which should still include the comments.
+    let reports_post_id_filter = ReportCombinedQuery {
+      post_id: Some(data.post.id),
+      ..Default::default()
+    }
+    .list(pool, &data.timmy_view)
+    .await?;
+
+    assert_length!(2, reports_post_id_filter);
+
     cleanup(data, pool).await?;
 
     Ok(())


Test patch: