diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 83979212d..e8d84542a 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -33,13 +33,11 @@ use std::io::Cursor; use totp_rs::{Secret, TOTP}; pub mod comment; -pub mod comment_report; pub mod community; pub mod local_user; pub mod post; -pub mod post_report; pub mod private_message; -pub mod private_message_report; +pub mod reports; pub mod site; pub mod sitemap; diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/reports/comment_report/create.rs similarity index 97% rename from crates/api/src/comment_report/create.rs rename to crates/api/src/reports/comment_report/create.rs index 48066cfe6..a456ded36 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/reports/comment_report/create.rs @@ -2,8 +2,8 @@ use crate::check_report_reason; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ - comment::{CommentReportResponse, CreateCommentReport}, context::LemmyContext, + reports::comment::{CommentReportResponse, CreateCommentReport}, send_activity::{ActivityChannel, SendActivityData}, utils::{ check_comment_deleted_or_removed, diff --git a/crates/api/src/comment_report/list.rs b/crates/api/src/reports/comment_report/list.rs similarity index 94% rename from crates/api/src/comment_report/list.rs rename to crates/api/src/reports/comment_report/list.rs index d2f723819..f4a611698 100644 --- a/crates/api/src/comment_report/list.rs +++ b/crates/api/src/reports/comment_report/list.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ - comment::{ListCommentReports, ListCommentReportsResponse}, context::LemmyContext, + reports::comment::{ListCommentReports, ListCommentReportsResponse}, utils::check_community_mod_of_any_or_admin_action, }; use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView}; diff --git a/crates/api/src/comment_report/mod.rs b/crates/api/src/reports/comment_report/mod.rs similarity index 100% rename from crates/api/src/comment_report/mod.rs rename to crates/api/src/reports/comment_report/mod.rs diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/reports/comment_report/resolve.rs similarity index 95% rename from crates/api/src/comment_report/resolve.rs rename to crates/api/src/reports/comment_report/resolve.rs index 58d5041dc..5ab36054f 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/reports/comment_report/resolve.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ - comment::{CommentReportResponse, ResolveCommentReport}, context::LemmyContext, + reports::comment::{CommentReportResponse, ResolveCommentReport}, utils::check_community_mod_action, }; use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable}; diff --git a/crates/api/src/reports/mod.rs b/crates/api/src/reports/mod.rs new file mode 100644 index 000000000..f23d1d71f --- /dev/null +++ b/crates/api/src/reports/mod.rs @@ -0,0 +1,4 @@ +pub mod comment_report; +pub mod post_report; +pub mod private_message_report; +pub mod report_combined; diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/reports/post_report/create.rs similarity index 97% rename from crates/api/src/post_report/create.rs rename to crates/api/src/reports/post_report/create.rs index b9edf35c5..bc85bdbe7 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/reports/post_report/create.rs @@ -3,7 +3,7 @@ use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ context::LemmyContext, - post::{CreatePostReport, PostReportResponse}, + reports::post::{CreatePostReport, PostReportResponse}, send_activity::{ActivityChannel, SendActivityData}, utils::{ check_community_user_action, diff --git a/crates/api/src/post_report/list.rs b/crates/api/src/reports/post_report/list.rs similarity index 94% rename from crates/api/src/post_report/list.rs rename to crates/api/src/reports/post_report/list.rs index 7d1d50b0b..da3c0cd94 100644 --- a/crates/api/src/post_report/list.rs +++ b/crates/api/src/reports/post_report/list.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, - post::{ListPostReports, ListPostReportsResponse}, + reports::post::{ListPostReports, ListPostReportsResponse}, utils::check_community_mod_of_any_or_admin_action, }; use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView}; diff --git a/crates/api/src/post_report/mod.rs b/crates/api/src/reports/post_report/mod.rs similarity index 100% rename from crates/api/src/post_report/mod.rs rename to crates/api/src/reports/post_report/mod.rs diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/reports/post_report/resolve.rs similarity index 96% rename from crates/api/src/post_report/resolve.rs rename to crates/api/src/reports/post_report/resolve.rs index 652327513..26b182a45 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/reports/post_report/resolve.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - post::{PostReportResponse, ResolvePostReport}, + reports::post::{PostReportResponse, ResolvePostReport}, utils::check_community_mod_action, }; use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable}; diff --git a/crates/api/src/private_message_report/create.rs b/crates/api/src/reports/private_message_report/create.rs similarity index 96% rename from crates/api/src/private_message_report/create.rs rename to crates/api/src/reports/private_message_report/create.rs index de8ca390f..17b5dceeb 100644 --- a/crates/api/src/private_message_report/create.rs +++ b/crates/api/src/reports/private_message_report/create.rs @@ -2,7 +2,7 @@ use crate::check_report_reason; use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse}, + reports::private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse}, utils::send_new_report_email_to_admins, }; use lemmy_db_schema::{ diff --git a/crates/api/src/private_message_report/list.rs b/crates/api/src/reports/private_message_report/list.rs similarity index 90% rename from crates/api/src/private_message_report/list.rs rename to crates/api/src/reports/private_message_report/list.rs index 79ef53e1c..61dbd6b97 100644 --- a/crates/api/src/private_message_report/list.rs +++ b/crates/api/src/reports/private_message_report/list.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json, Query}; use lemmy_api_common::{ context::LemmyContext, - private_message::{ListPrivateMessageReports, ListPrivateMessageReportsResponse}, + reports::private_message::{ListPrivateMessageReports, ListPrivateMessageReportsResponse}, utils::is_admin, }; use lemmy_db_views::{ diff --git a/crates/api/src/private_message_report/mod.rs b/crates/api/src/reports/private_message_report/mod.rs similarity index 100% rename from crates/api/src/private_message_report/mod.rs rename to crates/api/src/reports/private_message_report/mod.rs diff --git a/crates/api/src/private_message_report/resolve.rs b/crates/api/src/reports/private_message_report/resolve.rs similarity index 93% rename from crates/api/src/private_message_report/resolve.rs rename to crates/api/src/reports/private_message_report/resolve.rs index 7d821a60c..3f812e4fe 100644 --- a/crates/api/src/private_message_report/resolve.rs +++ b/crates/api/src/reports/private_message_report/resolve.rs @@ -1,7 +1,7 @@ use actix_web::web::{Data, Json}; use lemmy_api_common::{ context::LemmyContext, - private_message::{PrivateMessageReportResponse, ResolvePrivateMessageReport}, + reports::private_message::{PrivateMessageReportResponse, ResolvePrivateMessageReport}, utils::is_admin, }; use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable}; diff --git a/crates/api/src/reports/report_combined/list.rs b/crates/api/src/reports/report_combined/list.rs new file mode 100644 index 000000000..7fab9919b --- /dev/null +++ b/crates/api/src/reports/report_combined/list.rs @@ -0,0 +1,35 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + reports::combined::{ListReports, ListReportsResponse}, + utils::check_community_mod_of_any_or_admin_action, +}; +use lemmy_db_views::{report_combined_view::ReportCombinedQuery, structs::LocalUserView}; +use lemmy_utils::error::LemmyResult; + +/// Lists reports for a community if an id is supplied +/// or returns all reports for communities a user moderates +#[tracing::instrument(skip(context))] +pub async fn list_reports( + data: Query, + context: Data, + local_user_view: LocalUserView, +) -> LemmyResult> { + let community_id = data.community_id; + let unresolved_only = data.unresolved_only.unwrap_or_default(); + + check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?; + + let page = data.page; + let limit = data.limit; + let reports = ReportCombinedQuery { + community_id, + unresolved_only, + page, + limit, + } + .list(&mut context.pool(), &local_user_view) + .await?; + + Ok(Json(ListReportsResponse { reports })) +} diff --git a/crates/api/src/reports/report_combined/mod.rs b/crates/api/src/reports/report_combined/mod.rs new file mode 100644 index 000000000..d17e233fb --- /dev/null +++ b/crates/api/src/reports/report_combined/mod.rs @@ -0,0 +1 @@ +pub mod list; diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs index e08365789..bae9c4de4 100644 --- a/crates/api_common/src/comment.rs +++ b/crates/api_common/src/comment.rs @@ -1,9 +1,9 @@ use lemmy_db_schema::{ - newtypes::{CommentId, CommentReportId, CommunityId, LanguageId, LocalUserId, PostId}, + newtypes::{CommentId, CommunityId, LanguageId, LocalUserId, PostId}, CommentSortType, ListingType, }; -use lemmy_db_views::structs::{CommentReportView, CommentView, VoteView}; +use lemmy_db_views::structs::{CommentView, VoteView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -146,60 +146,6 @@ pub struct GetCommentsResponse { pub comments: Vec, } -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Report a comment. -pub struct CreateCommentReport { - pub comment_id: CommentId, - pub reason: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The comment report response. -pub struct CommentReportResponse { - pub comment_report_view: CommentReportView, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Resolve a comment report (only doable by mods). -pub struct ResolveCommentReport { - pub report_id: CommentReportId, - pub resolved: bool, -} - -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// List comment reports. -pub struct ListCommentReports { - #[cfg_attr(feature = "full", ts(optional))] - pub comment_id: Option, - #[cfg_attr(feature = "full", ts(optional))] - pub page: Option, - #[cfg_attr(feature = "full", ts(optional))] - pub limit: Option, - /// Only shows the unresolved reports - #[cfg_attr(feature = "full", ts(optional))] - pub unresolved_only: Option, - /// 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, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The comment report list response. -pub struct ListCommentReportsResponse { - pub comment_reports: Vec, -} - #[skip_serializing_none] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(TS))] diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 6e09d904d..8af1dec25 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -11,6 +11,7 @@ pub mod oauth_provider; pub mod person; pub mod post; pub mod private_message; +pub mod reports; #[cfg(feature = "full")] pub mod request; #[cfg(feature = "full")] diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 405de3a92..310c5c03e 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -1,10 +1,10 @@ use lemmy_db_schema::{ - newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId}, + newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId}, ListingType, PostFeatureType, PostSortType, }; -use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView}; +use lemmy_db_views::structs::{PaginationCursor, PostView, VoteView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -247,61 +247,6 @@ pub struct SavePost { pub save: bool, } -#[derive(Debug, Serialize, Deserialize, Clone, Default)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Create a post report. -pub struct CreatePostReport { - pub post_id: PostId, - pub reason: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The post report response. -pub struct PostReportResponse { - pub post_report_view: PostReportView, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Resolve a post report (mods only). -pub struct ResolvePostReport { - pub report_id: PostReportId, - pub resolved: bool, -} - -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// List post reports. -pub struct ListPostReports { - #[cfg_attr(feature = "full", ts(optional))] - pub page: Option, - #[cfg_attr(feature = "full", ts(optional))] - pub limit: Option, - /// Only shows the unresolved reports - #[cfg_attr(feature = "full", ts(optional))] - pub unresolved_only: Option, - // TODO make into tagged enum at some point - /// 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, - #[cfg_attr(feature = "full", ts(optional))] - pub post_id: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The post reports response. -pub struct ListPostReportsResponse { - pub post_reports: Vec, -} - #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/api_common/src/private_message.rs b/crates/api_common/src/private_message.rs index 666fe3865..8bd417a8e 100644 --- a/crates/api_common/src/private_message.rs +++ b/crates/api_common/src/private_message.rs @@ -1,5 +1,5 @@ -use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId, PrivateMessageReportId}; -use lemmy_db_views::structs::{PrivateMessageReportView, PrivateMessageView}; +use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId}; +use lemmy_db_views::structs::PrivateMessageView; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -72,53 +72,3 @@ pub struct PrivateMessagesResponse { pub struct PrivateMessageResponse { pub private_message_view: PrivateMessageView, } - -#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Create a report for a private message. -pub struct CreatePrivateMessageReport { - pub private_message_id: PrivateMessageId, - pub reason: String, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// A private message report response. -pub struct PrivateMessageReportResponse { - pub private_message_report_view: PrivateMessageReportView, -} - -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// Resolve a private message report. -pub struct ResolvePrivateMessageReport { - pub report_id: PrivateMessageReportId, - pub resolved: bool, -} - -#[skip_serializing_none] -#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// List private message reports. -// TODO , perhaps GetReports should be a tagged enum list too. -pub struct ListPrivateMessageReports { - #[cfg_attr(feature = "full", ts(optional))] - pub page: Option, - #[cfg_attr(feature = "full", ts(optional))] - pub limit: Option, - /// Only shows the unresolved reports - #[cfg_attr(feature = "full", ts(optional))] - pub unresolved_only: Option, -} - -#[derive(Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -/// The response for list private message reports. -pub struct ListPrivateMessageReportsResponse { - pub private_message_reports: Vec, -} diff --git a/crates/api_common/src/reports/combined.rs b/crates/api_common/src/reports/combined.rs new file mode 100644 index 000000000..17d6a0505 --- /dev/null +++ b/crates/api_common/src/reports/combined.rs @@ -0,0 +1,32 @@ +use lemmy_db_schema::newtypes::CommunityId; +use lemmy_db_views::structs::ReportCombinedView; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List reports. +pub struct ListReports { + #[cfg_attr(feature = "full", ts(optional))] + pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub limit: Option, + /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] + pub unresolved_only: Option, + /// 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, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The post reports response. +pub struct ListReportsResponse { + pub reports: Vec, +} diff --git a/crates/api_common/src/reports/comment.rs b/crates/api_common/src/reports/comment.rs new file mode 100644 index 000000000..4324079e5 --- /dev/null +++ b/crates/api_common/src/reports/comment.rs @@ -0,0 +1,60 @@ +use lemmy_db_schema::newtypes::{CommentId, CommentReportId, CommunityId}; +use lemmy_db_views::structs::CommentReportView; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Report a comment. +pub struct CreateCommentReport { + pub comment_id: CommentId, + pub reason: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The comment report response. +pub struct CommentReportResponse { + pub comment_report_view: CommentReportView, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Resolve a comment report (only doable by mods). +pub struct ResolveCommentReport { + pub report_id: CommentReportId, + pub resolved: bool, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List comment reports. +pub struct ListCommentReports { + #[cfg_attr(feature = "full", ts(optional))] + pub comment_id: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub limit: Option, + /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] + pub unresolved_only: Option, + /// 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, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The comment report list response. +pub struct ListCommentReportsResponse { + pub comment_reports: Vec, +} diff --git a/crates/api_common/src/reports/mod.rs b/crates/api_common/src/reports/mod.rs new file mode 100644 index 000000000..6584de1bc --- /dev/null +++ b/crates/api_common/src/reports/mod.rs @@ -0,0 +1,4 @@ +pub mod combined; +pub mod comment; +pub mod post; +pub mod private_message; diff --git a/crates/api_common/src/reports/post.rs b/crates/api_common/src/reports/post.rs new file mode 100644 index 000000000..25094a22a --- /dev/null +++ b/crates/api_common/src/reports/post.rs @@ -0,0 +1,61 @@ +use lemmy_db_schema::newtypes::{CommunityId, PostId, PostReportId}; +use lemmy_db_views::structs::PostReportView; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Create a post report. +pub struct CreatePostReport { + pub post_id: PostId, + pub reason: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The post report response. +pub struct PostReportResponse { + pub post_report_view: PostReportView, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Resolve a post report (mods only). +pub struct ResolvePostReport { + pub report_id: PostReportId, + pub resolved: bool, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List post reports. +pub struct ListPostReports { + #[cfg_attr(feature = "full", ts(optional))] + pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub limit: Option, + /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] + pub unresolved_only: Option, + // TODO make into tagged enum at some point + /// 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, + #[cfg_attr(feature = "full", ts(optional))] + pub post_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The post reports response. +pub struct ListPostReportsResponse { + pub post_reports: Vec, +} diff --git a/crates/api_common/src/reports/private_message.rs b/crates/api_common/src/reports/private_message.rs new file mode 100644 index 000000000..0eb8100f0 --- /dev/null +++ b/crates/api_common/src/reports/private_message.rs @@ -0,0 +1,56 @@ +use lemmy_db_schema::newtypes::{PrivateMessageId, PrivateMessageReportId}; +use lemmy_db_views::structs::PrivateMessageReportView; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Create a report for a private message. +pub struct CreatePrivateMessageReport { + pub private_message_id: PrivateMessageId, + pub reason: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A private message report response. +pub struct PrivateMessageReportResponse { + pub private_message_report_view: PrivateMessageReportView, +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Resolve a private message report. +pub struct ResolvePrivateMessageReport { + pub report_id: PrivateMessageReportId, + pub resolved: bool, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// List private message reports. +// TODO , perhaps GetReports should be a tagged enum list too. +pub struct ListPrivateMessageReports { + #[cfg_attr(feature = "full", ts(optional))] + pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub limit: Option, + /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] + pub unresolved_only: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The response for list private message reports. +pub struct ListPrivateMessageReportsResponse { + pub private_message_reports: Vec, +} diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 1d3177b15..a64ade092 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -862,6 +862,7 @@ diesel::table! { published -> Timestamptz, post_report_id -> Nullable, comment_report_id -> Nullable, + private_message_report_id -> Nullable, } } @@ -1017,6 +1018,7 @@ diesel::joinable!(registration_application -> local_user (local_user_id)); diesel::joinable!(registration_application -> person (admin_id)); diesel::joinable!(report_combined -> comment_report (comment_report_id)); diesel::joinable!(report_combined -> post_report (post_report_id)); +diesel::joinable!(report_combined -> private_message_report (private_message_report_id)); diesel::joinable!(site -> instance (instance_id)); diesel::joinable!(site_aggregates -> site (site_id)); diesel::joinable!(site_language -> language (language_id)); diff --git a/crates/db_views/src/report_combined_view.rs b/crates/db_views/src/report_combined_view.rs index d34838c83..04bcc0f38 100644 --- a/crates/db_views/src/report_combined_view.rs +++ b/crates/db_views/src/report_combined_view.rs @@ -1,11 +1,12 @@ use crate::structs::{ + CommentReportView, LocalUserView, - PostOrCommentReportViewTemp, PostReportView, + PrivateMessageReportView, ReportCombinedView, + ReportCombinedViewInternal, }; use diesel::{ - pg::Pg, result::Error, BoolExpressionMethods, ExpressionMethods, @@ -16,8 +17,11 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, - newtypes::{CommunityId, PersonId, PostReportId}, + newtypes::{CommunityId, PersonId}, schema::{ + comment, + comment_actions, + comment_aggregates, comment_report, community, community_actions, @@ -28,25 +32,17 @@ use lemmy_db_schema::{ post_actions, post_aggregates, post_report, + private_message, + private_message_report, report_combined, }, source::community::CommunityFollower, - utils::{ - actions, - actions_alias, - functions::coalesce, - get_conn, - limit_and_offset, - DbConn, - DbPool, - ListFn, - Queries, - ReadFn, - }, + utils::{actions, actions_alias, functions::coalesce, get_conn, limit_and_offset, DbPool}, }; use lemmy_utils::error::LemmyResult; -impl ReportCombinedView { +// TODO fix +impl ReportCombinedViewInternal { /// returns the current unresolved report count for the communities you mod pub async fn get_report_count( pool: &mut DbPool<'_>, @@ -96,140 +92,239 @@ pub struct ReportCombinedQuery { pub unresolved_only: bool, } +// TODO need to add private message impl ReportCombinedQuery { pub async fn list( self, pool: &mut DbPool<'_>, user: &LocalUserView, - ) -> LemmyResult> { + ) -> LemmyResult> { let options = self; + let my_person_id = user.local_user.person_id; + let item_creator = aliases::person1.field(person::id); let conn = &mut get_conn(pool).await?; + + // 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: + // - post_report.creator_id + // - comment_report.creator_id let mut query = report_combined::table .left_join(post_report::table) .left_join(comment_report::table) - // .inner_join(post::table) - // .inner_join(community::table.on(post::community_id.eq(community::id))) - .left_join( + .left_join(private_message_report::table) + // The report creator + .inner_join( person::table.on( post_report::creator_id .eq(person::id) - .or(comment_report::creator_id.eq(person::id)), + .or(comment_report::creator_id.eq(person::id)) + .or(private_message_report::creator_id.eq(person::id)), ), ) - // .inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id)))) - // .left_join(actions_alias( - // creator_community_actions, - // post::creator_id, - // post::community_id, - // )) - // .left_join(actions( - // community_actions::table, - // Some(my_person_id), - // post::community_id, - // )) - // .left_join( - // local_user::table.on( - // post::creator_id - // .eq(local_user::person_id) - // .and(local_user::admin.eq(true)), - // ), - // ) - // .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - // .left_join(actions( - // person_actions::table, - // Some(my_person_id), - // post::creator_id, - // )) - // .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) - // .left_join( - // aliases::person2 - // .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), - // ) + // The comment + .left_join(comment::table.on(comment_report::comment_id.eq(comment::id))) + // The private message + .left_join( + private_message::table + .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)), + ), + ) + // The item creator + // You can now use aliases::person1.field(person::id) / item_creator for all the item actions + .inner_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)), + ), + ) + // The community + .left_join(community::table.on(post::community_id.eq(community::id))) + .left_join(actions_alias( + creator_community_actions, + item_creator, + post::community_id, + )) + .left_join( + local_user::table.on( + item_creator + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ) + .left_join(actions( + community_actions::table, + Some(my_person_id), + post::community_id, + )) + .left_join(actions(post_actions::table, Some(my_person_id), post::id)) + .left_join(actions( + person_actions::table, + Some(my_person_id), + item_creator, + )) + .left_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) + .left_join( + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), + ) + .left_join(aliases::person2.on(item_creator.eq(aliases::person2.field(person::id)))) + .left_join(actions( + comment_actions::table, + Some(my_person_id), + comment_report::comment_id, + )) .select(( + // Post-specific post_report::all_columns.nullable(), + post::all_columns.nullable(), + post_aggregates::all_columns.nullable(), + coalesce( + post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), + post_aggregates::comments, + ) + .nullable(), + post_actions::saved.nullable().is_not_null(), + post_actions::read.nullable().is_not_null(), + post_actions::hidden.nullable().is_not_null(), + post_actions::like_score.nullable(), + // Comment-specific comment_report::all_columns.nullable(), - // post::all_columns, - // community::all_columns, - person::all_columns.nullable(), - // aliases::person1.fields(person::all_columns), - // creator_community_actions - // .field(community_actions::received_ban) - // .nullable() - // .is_not_null(), - // creator_community_actions - // .field(community_actions::became_moderator) - // .nullable() - // .is_not_null(), - // local_user::admin.nullable().is_not_null(), - // CommunityFollower::select_subscribed_type(), - // post_actions::saved.nullable().is_not_null(), - // post_actions::read.nullable().is_not_null(), - // post_actions::hidden.nullable().is_not_null(), - // person_actions::blocked.nullable().is_not_null(), - // post_actions::like_score.nullable(), - // coalesce( - // post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), - // post_aggregates::comments, - // ), - // post_aggregates::all_columns, - // aliases::person2.fields(person::all_columns.nullable()), + comment::all_columns.nullable(), + comment_aggregates::all_columns.nullable(), + comment_actions::saved.nullable().is_not_null(), + comment_actions::like_score.nullable(), + // Private-message-specific + private_message_report::all_columns.nullable(), + private_message::all_columns.nullable(), + // Shared + person::all_columns, + aliases::person1.fields(person::all_columns), + community::all_columns.nullable(), + CommunityFollower::select_subscribed_type(), + aliases::person2.fields(person::all_columns.nullable()), + local_user::admin.nullable().is_not_null(), + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null(), + person_actions::blocked.nullable().is_not_null(), )) .into_boxed(); - // if let Some(community_id) = options.community_id { - // query = query.filter(post::community_id.eq(community_id)); - // } - - // if let Some(post_id) = options.post_id { - // query = query.filter(post::id.eq(post_id)); - // } + if let Some(community_id) = options.community_id { + query = query.filter(community::id.eq(community_id)); + } // If viewing all reports, order by newest, but if viewing unresolved only, show the oldest // first (FIFO) - // if options.unresolved_only { - // query = query - // .filter(post_report::resolved.eq(false)) - // .order_by(post_report::published.asc()); - // } else { - // query = query.order_by(post_report::published.desc()); - // } + if options.unresolved_only { + query = query + .filter(post_report::resolved.eq(false)) + .or_filter(comment_report::resolved.eq(false)) + .or_filter(private_message_report::resolved.eq(false)) + .order_by(report_combined::published.asc()); + } else { + query = query.order_by(report_combined::published.desc()); + } // If its not an admin, get only the ones you mod - // if !user.local_user.admin { - // query = query.filter(community_actions::became_moderator.is_not_null()); - // } + if !user.local_user.admin { + query = query.filter(community_actions::became_moderator.is_not_null()); + } let (limit, offset) = limit_and_offset(options.page, options.limit)?; query = query.limit(limit).offset(offset); - let res = query.load::(conn).await?; - let out = res - .iter() - .filter_map(map_to_post_or_comment_view_tmp) - .collect(); + let res = query.load::(conn).await?; + + // Map the query results to the enum + let out = res.into_iter().filter_map(map_to_enum).collect(); Ok(out) } } -fn map_to_post_or_comment_view_tmp( - view: &ReportCombinedView, -) -> Option { - // If it has post_report, you know the other fields are defined - if let (Some(post_report), Some(post_creator)) = (view.post_report.clone(), view.creator.clone()) - { - Some(PostOrCommentReportViewTemp::Post { +/// Maps the combined DB row to an enum +fn map_to_enum(view: ReportCombinedViewInternal) -> Option { + // Use for a short alias + let v = view; + + if let (Some(post_report), Some(post), Some(community), Some(unread_comments), Some(counts)) = ( + v.post_report, + v.post.clone(), + v.community.clone(), + v.post_unread_comments, + v.post_counts, + ) { + Some(ReportCombinedView::Post(PostReportView { post_report, - post_creator, - }) - } else if let (Some(comment_report), Some(comment_creator)) = - (view.comment_report.clone(), view.creator.clone()) - { - Some(PostOrCommentReportViewTemp::Comment { + post, + community, + unread_comments, + counts, + creator: v.report_creator, + post_creator: v.item_creator, + creator_banned_from_community: v.item_creator_banned_from_community, + creator_is_moderator: v.item_creator_is_moderator, + creator_is_admin: v.item_creator_is_admin, + creator_blocked: v.item_creator_blocked, + subscribed: v.subscribed, + saved: v.post_saved, + read: v.post_read, + hidden: v.post_hidden, + my_vote: v.my_post_vote, + resolver: v.resolver, + })) + } else if let (Some(comment_report), Some(comment), Some(counts), Some(post), Some(community)) = ( + v.comment_report, + v.comment, + v.comment_counts, + v.post.clone(), + v.community.clone(), + ) { + Some(ReportCombinedView::Comment(CommentReportView { comment_report, - comment_creator, - }) + comment, + counts, + post, + community, + creator: v.report_creator, + comment_creator: v.item_creator, + creator_banned_from_community: v.item_creator_banned_from_community, + creator_is_moderator: v.item_creator_is_moderator, + creator_is_admin: v.item_creator_is_admin, + creator_blocked: v.item_creator_blocked, + subscribed: v.subscribed, + saved: v.comment_saved, + my_vote: v.my_comment_vote, + resolver: v.resolver, + })) + } else if let (Some(private_message_report), Some(private_message)) = + (v.private_message_report, v.private_message) + { + Some(ReportCombinedView::PrivateMessage( + PrivateMessageReportView { + private_message_report, + private_message, + creator: v.report_creator, + private_message_creator: v.item_creator, + resolver: v.resolver, + }, + )) } else { None } diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index e51aec67c..cbd879157 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -237,56 +237,47 @@ pub struct LocalImageView { pub local_image: LocalImage, pub person: Person, } -#[skip_serializing_none] + #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", derive(Queryable))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] -// TODO TS shouldn't be necessary here, since this shouldn't be used externally -#[cfg_attr(feature = "full", ts(export))] /// A combined report view -pub struct ReportCombinedView { +pub struct ReportCombinedViewInternal { // Post-specific pub post_report: Option, - // pub post_creator: Person, - // pub unread_comments: i64, - // pub post_counts: PostAggregates, - // #[cfg_attr(feature = "full", ts(optional))] - // pub resolver: Option, + pub post: Option, + pub post_counts: Option, + pub post_unread_comments: Option, + pub post_saved: bool, + pub post_read: bool, + pub post_hidden: bool, + pub my_post_vote: Option, // Comment-specific pub comment_report: Option, - // pub comment_creator: Person, - // pub comment: Comment, - // pub comment_counts: CommentAggregates, - // Shared - // pub post: Post, - // pub community: Community, - // pub creator: Person, - // pub creator_banned_from_community: bool, - // pub creator_is_moderator: bool, - // pub creator_is_admin: bool, - // pub subscribed: SubscribedType, - // pub saved: bool, - // pub read: bool, - // pub hidden: bool, - // pub creator_blocked: bool, - // #[cfg_attr(feature = "full", ts(optional))] - // pub my_vote: Option, - // --- - pub creator: Option, + pub comment: Option, + pub comment_counts: Option, + pub comment_saved: bool, + pub my_comment_vote: Option, + // Private-message-specific + pub private_message_report: Option, + pub private_message: Option, + // // Shared + pub report_creator: Person, + pub item_creator: Person, + pub community: Option, + pub subscribed: SubscribedType, + pub resolver: Option, + pub item_creator_is_admin: bool, + pub item_creator_banned_from_community: bool, + pub item_creator_is_moderator: bool, + pub item_creator_blocked: bool, } -pub enum PostOrCommentReportView { +#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub enum ReportCombinedView { Post(PostReportView), Comment(CommentReportView), -} - -pub enum PostOrCommentReportViewTemp { - Post { - post_report: PostReport, - post_creator: Person, - }, - Comment { - comment_report: CommentReport, - comment_creator: Person, - }, + PrivateMessage(PrivateMessageReportView), } diff --git a/migrations/2024-11-26-115042_add_combined_tables/down.sql b/migrations/2024-11-26-115042_add_combined_tables/down.sql index 02c747794..b27ba9bc4 100644 --- a/migrations/2024-11-26-115042_add_combined_tables/down.sql +++ b/migrations/2024-11-26-115042_add_combined_tables/down.sql @@ -1 +1,2 @@ DROP TABLE report_combined; + diff --git a/migrations/2024-11-26-115042_add_combined_tables/up.sql b/migrations/2024-11-26-115042_add_combined_tables/up.sql index a7a0a7414..2068355bf 100644 --- a/migrations/2024-11-26-115042_add_combined_tables/up.sql +++ b/migrations/2024-11-26-115042_add_combined_tables/up.sql @@ -1,14 +1,13 @@ - CREATE TABLE report_combined ( id serial PRIMARY KEY, - published timestamptz not null, + published timestamptz NOT NULL, post_report_id int REFERENCES post_report ON UPDATE CASCADE ON DELETE CASCADE, comment_report_id int REFERENCES comment_report ON UPDATE CASCADE ON DELETE CASCADE, - UNIQUE (post_report_id, comment_report_id) + private_message_report_id int REFERENCES private_message_report ON UPDATE CASCADE ON DELETE CASCADE, + UNIQUE (post_report_id, comment_report_id, private_message_report_id) ); -CREATE INDEX idx_report_combined_published on report_combined (published desc); +CREATE INDEX idx_report_combined_published ON report_combined (published DESC); -- TODO do history update -- TODO do triggers in replaceable schema - diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 2f431419c..e4fcb7a8f 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -6,11 +6,6 @@ use lemmy_api::{ list_comment_likes::list_comment_likes, save::save_comment, }, - comment_report::{ - create::create_comment_report, - list::list_comment_reports, - resolve::resolve_comment_report, - }, community::{ add_mod::add_mod_to_community, ban::ban_from_community, @@ -64,16 +59,24 @@ use lemmy_api::{ mark_read::mark_post_as_read, save::save_post, }, - post_report::{ - create::create_post_report, - list::list_post_reports, - resolve::resolve_post_report, - }, private_message::mark_read::mark_pm_as_read, - private_message_report::{ - create::create_pm_report, - list::list_pm_reports, - resolve::resolve_pm_report, + reports::{ + comment_report::{ + create::create_comment_report, + list::list_comment_reports, + resolve::resolve_comment_report, + }, + post_report::{ + create::create_post_report, + list::list_post_reports, + resolve::resolve_post_report, + }, + private_message_report::{ + create::create_pm_report, + list::list_pm_reports, + resolve::resolve_pm_report, + }, + report_combined::list::list_reports, }, site::{ block::block_instance, @@ -248,6 +251,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/like", web::post().to(like_post)) .route("/like/list", web::get().to(list_post_likes)) .route("/save", web::put().to(save_post)) + // TODO should these be moved into the new report heading? .route("/report", web::post().to(create_post_report)) .route("/report/resolve", web::put().to(resolve_post_report)) .route("/report/list", web::get().to(list_post_reports)) @@ -274,10 +278,16 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/like/list", web::get().to(list_comment_likes)) .route("/save", web::put().to(save_comment)) .route("/list", web::get().to(list_comments)) + // TODO should these be moved into the new report heading? .route("/report", web::post().to(create_comment_report)) .route("/report/resolve", web::put().to(resolve_comment_report)) .route("/report/list", web::get().to(list_comment_reports)), ) + .service( + web::scope("report") + .wrap(rate_limit.message()) + .route("/list", web::get().to(list_reports)), + ) // Private Message .service( web::scope("/private_message") @@ -287,6 +297,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("", web::put().to(update_private_message)) .route("/delete", web::post().to(delete_private_message)) .route("/mark_as_read", web::post().to(mark_pm_as_read)) + // TODO should these be moved into the new report heading? .route("/report", web::post().to(create_pm_report)) .route("/report/resolve", web::put().to(resolve_pm_report)) .route("/report/list", web::get().to(list_pm_reports)),