From b18c744f637a1bfa51c13fcd6348b8136860c5f8 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 28 Sep 2021 06:36:17 -0400 Subject: [PATCH] Clean up reporting (#1776) * First untested pass at reporting. * Adding unit tests for post and comment report views * Fix clippy * Adding counts, creator_banned, and unresolved_only * Adding my_vote to report views * Fixing unit tests. --- crates/api/src/comment_report.rs | 67 ++- crates/api/src/local_user.rs | 52 +- crates/api/src/post_report.rs | 70 ++- crates/api_common/src/comment.rs | 31 +- crates/api_common/src/lib.rs | 33 +- crates/api_common/src/person.rs | 8 +- crates/api_common/src/post.rs | 31 +- crates/db_queries/src/lib.rs | 13 +- .../db_queries/src/source/comment_report.rs | 6 +- crates/db_queries/src/source/post_report.rs | 8 +- crates/db_schema/src/lib.rs | 6 + crates/db_schema/src/source/comment_report.rs | 10 +- crates/db_schema/src/source/post_report.rs | 4 +- crates/db_views/src/comment_report_view.rs | 478 ++++++++++++++++-- crates/db_views/src/post_report_view.rs | 429 +++++++++++++++- 15 files changed, 976 insertions(+), 270 deletions(-) diff --git a/crates/api/src/comment_report.rs b/crates/api/src/comment_report.rs index 6d2e2280d..32a4a5d9b 100644 --- a/crates/api/src/comment_report.rs +++ b/crates/api/src/comment_report.rs @@ -3,7 +3,6 @@ use actix_web::web::Data; use lemmy_api_common::{ blocking, check_community_ban, - collect_moderated_communities, comment::*, get_local_user_view_from_jwt, is_mod_or_admin, @@ -15,22 +14,18 @@ use lemmy_db_views::{ comment_view::CommentView, }; use lemmy_utils::{ApiError, ConnectionId, LemmyError}; -use lemmy_websocket::{ - messages::{SendModRoomMessage, SendUserRoomMessage}, - LemmyContext, - UserOperation, -}; +use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; /// Creates a comment report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreateCommentReport { - type Response = CreateCommentReportResponse; + type Response = CommentReportResponse; async fn perform( &self, context: &Data, websocket_id: Option, - ) -> Result { + ) -> Result { let data: &CreateCommentReport = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; @@ -66,18 +61,18 @@ impl Perform for CreateCommentReport { .await? .map_err(|_| ApiError::err("couldnt_create_report"))?; - let res = CreateCommentReportResponse { success: true }; + let comment_report_view = blocking(context.pool(), move |conn| { + CommentReportView::read(conn, report.id, person_id) + }) + .await??; - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::CreateCommentReport, - response: res.clone(), - local_recipient_id: local_user_view.local_user.id, - websocket_id, - }); + let res = CommentReportResponse { + comment_report_view, + }; context.chat_server().do_send(SendModRoomMessage { op: UserOperation::CreateCommentReport, - response: report, + response: res.clone(), community_id: comment_view.community.id, websocket_id, }); @@ -89,20 +84,21 @@ impl Perform for CreateCommentReport { /// 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; + type Response = CommentReportResponse; async fn perform( &self, context: &Data, websocket_id: Option, - ) -> Result { + ) -> Result { let data: &ResolveCommentReport = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; let report_id = data.report_id; + let person_id = local_user_view.person.id; let report = blocking(context.pool(), move |conn| { - CommentReportView::read(conn, report_id) + CommentReportView::read(conn, report_id, person_id) }) .await??; @@ -123,9 +119,13 @@ impl Perform for ResolveCommentReport { }; let report_id = data.report_id; - let res = ResolveCommentReportResponse { - report_id, - resolved, + let comment_report_view = blocking(context.pool(), move |conn| { + CommentReportView::read(conn, report_id, person_id) + }) + .await??; + + let res = CommentReportResponse { + comment_report_view, }; context.chat_server().do_send(SendModRoomMessage { @@ -148,36 +148,29 @@ impl Perform for ListCommentReports { async fn perform( &self, context: &Data, - websocket_id: Option, + _websocket_id: Option, ) -> Result { let data: &ListCommentReports = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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 community_id = data.community_id; + let unresolved_only = data.unresolved_only; let page = data.page; let limit = data.limit; - let comments = blocking(context.pool(), move |conn| { - CommentReportQueryBuilder::create(conn) - .community_ids(community_ids) + let comment_reports = blocking(context.pool(), move |conn| { + CommentReportQueryBuilder::create(conn, person_id) + .community_id(community_id) + .unresolved_only(unresolved_only) .page(page) .limit(limit) .list() }) .await??; - let res = ListCommentReportsResponse { comments }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::ListCommentReports, - response: res.clone(), - local_recipient_id: local_user_view.local_user.id, - websocket_id, - }); + let res = ListCommentReportsResponse { comment_reports }; Ok(res) } diff --git a/crates/api/src/local_user.rs b/crates/api/src/local_user.rs index c69ea48d3..1d4f27b28 100644 --- a/crates/api/src/local_user.rs +++ b/crates/api/src/local_user.rs @@ -6,7 +6,6 @@ use captcha::{gen, Difficulty}; use chrono::Duration; use lemmy_api_common::{ blocking, - collect_moderated_communities, get_local_user_view_from_jwt, is_admin, password_length_check, @@ -67,7 +66,7 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{ - messages::{CaptchaItem, SendAllMessage, SendUserRoomMessage}, + messages::{CaptchaItem, SendAllMessage}, LemmyContext, UserOperation, }; @@ -816,52 +815,31 @@ impl Perform for GetReportCount { async fn perform( &self, context: &Data, - websocket_id: Option, + _websocket_id: Option, ) -> Result { let data: &GetReportCount = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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 community_id = data.community_id; - let res = { - if community_ids.is_empty() { - GetReportCountResponse { - community: None, - comment_reports: 0, - post_reports: 0, - } - } else { - let ids = community_ids.clone(); - let comment_reports = blocking(context.pool(), move |conn| { - CommentReportView::get_report_count(conn, &ids) - }) - .await??; + let comment_reports = blocking(context.pool(), move |conn| { + CommentReportView::get_report_count(conn, person_id, community_id) + }) + .await??; - let ids = community_ids.clone(); - let post_reports = blocking(context.pool(), move |conn| { - PostReportView::get_report_count(conn, &ids) - }) - .await??; + let post_reports = blocking(context.pool(), move |conn| { + PostReportView::get_report_count(conn, person_id, community_id) + }) + .await??; - GetReportCountResponse { - community: data.community, - comment_reports, - post_reports, - } - } + let res = GetReportCountResponse { + community_id, + comment_reports, + post_reports, }; - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::GetReportCount, - response: res.clone(), - local_recipient_id: local_user_view.local_user.id, - websocket_id, - }); - Ok(res) } } diff --git a/crates/api/src/post_report.rs b/crates/api/src/post_report.rs index e08a64b3f..25af96ba6 100644 --- a/crates/api/src/post_report.rs +++ b/crates/api/src/post_report.rs @@ -3,16 +3,14 @@ use actix_web::web::Data; use lemmy_api_common::{ blocking, check_community_ban, - collect_moderated_communities, get_local_user_view_from_jwt, is_mod_or_admin, post::{ CreatePostReport, - CreatePostReportResponse, ListPostReports, ListPostReportsResponse, + PostReportResponse, ResolvePostReport, - ResolvePostReportResponse, }, }; use lemmy_db_queries::Reportable; @@ -22,22 +20,18 @@ use lemmy_db_views::{ post_view::PostView, }; use lemmy_utils::{ApiError, ConnectionId, LemmyError}; -use lemmy_websocket::{ - messages::{SendModRoomMessage, SendUserRoomMessage}, - LemmyContext, - UserOperation, -}; +use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation}; /// Creates a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreatePostReport { - type Response = CreatePostReportResponse; + type Response = PostReportResponse; async fn perform( &self, context: &Data, websocket_id: Option, - ) -> Result { + ) -> Result { let data: &CreatePostReport = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; @@ -75,18 +69,16 @@ impl Perform for CreatePostReport { .await? .map_err(|_| ApiError::err("couldnt_create_report"))?; - let res = CreatePostReportResponse { success: true }; + let post_report_view = blocking(context.pool(), move |conn| { + PostReportView::read(conn, report.id, person_id) + }) + .await??; - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::CreatePostReport, - response: res.clone(), - local_recipient_id: local_user_view.local_user.id, - websocket_id, - }); + let res = PostReportResponse { post_report_view }; context.chat_server().do_send(SendModRoomMessage { op: UserOperation::CreatePostReport, - response: report, + response: res.clone(), community_id: post_view.community.id, websocket_id, }); @@ -98,20 +90,21 @@ impl Perform for CreatePostReport { /// Resolves or unresolves a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for ResolvePostReport { - type Response = ResolvePostReportResponse; + type Response = PostReportResponse; async fn perform( &self, context: &Data, websocket_id: Option, - ) -> Result { + ) -> Result { let data: &ResolvePostReport = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; let report_id = data.report_id; + let person_id = local_user_view.person.id; let report = blocking(context.pool(), move |conn| { - PostReportView::read(conn, report_id) + PostReportView::read(conn, report_id, person_id) }) .await??; @@ -127,15 +120,17 @@ impl Perform for ResolvePostReport { } }; - let res = ResolvePostReportResponse { - report_id, - resolved: true, - }; - if blocking(context.pool(), resolve_fun).await?.is_err() { return Err(ApiError::err("couldnt_resolve_report").into()); }; + let post_report_view = blocking(context.pool(), move |conn| { + PostReportView::read(conn, report_id, person_id) + }) + .await??; + + let res = PostReportResponse { post_report_view }; + context.chat_server().do_send(SendModRoomMessage { op: UserOperation::ResolvePostReport, response: res.clone(), @@ -156,36 +151,29 @@ impl Perform for ListPostReports { async fn perform( &self, context: &Data, - websocket_id: Option, + _websocket_id: Option, ) -> Result { let data: &ListPostReports = self; let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).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 community_id = data.community_id; + let unresolved_only = data.unresolved_only; let page = data.page; let limit = data.limit; - let posts = blocking(context.pool(), move |conn| { - PostReportQueryBuilder::create(conn) - .community_ids(community_ids) + let post_reports = blocking(context.pool(), move |conn| { + PostReportQueryBuilder::create(conn, person_id) + .community_id(community_id) + .unresolved_only(unresolved_only) .page(page) .limit(limit) .list() }) .await??; - let res = ListPostReportsResponse { posts }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::ListPostReports, - response: res.clone(), - local_recipient_id: local_user_view.local_user.id, - websocket_id, - }); + let res = ListPostReportsResponse { post_reports }; Ok(res) } diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs index ce5182cf3..555640580 100644 --- a/crates/api_common/src/comment.rs +++ b/crates/api_common/src/comment.rs @@ -1,4 +1,4 @@ -use lemmy_db_schema::{CommentId, CommunityId, LocalUserId, PostId}; +use lemmy_db_schema::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId}; use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView}; use serde::{Deserialize, Serialize}; @@ -79,42 +79,37 @@ pub struct GetCommentsResponse { pub comments: Vec, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize)] pub struct CreateCommentReport { pub comment_id: CommentId, pub reason: String, pub auth: String, } -#[derive(Serialize, Deserialize, Clone)] -pub struct CreateCommentReportResponse { - pub success: bool, +#[derive(Serialize, Clone)] +pub struct CommentReportResponse { + pub comment_report_view: CommentReportView, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize)] pub struct ResolveCommentReport { - pub report_id: i32, + pub report_id: CommentReportId, pub resolved: bool, pub auth: String, } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ResolveCommentReportResponse { - // TODO this should probably return the view - pub report_id: i32, - pub resolved: bool, -} - -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize)] pub struct ListCommentReports { pub page: Option, pub limit: Option, + /// Only shows the unresolved reports + pub unresolved_only: Option, /// if no community is given, it returns reports for all communities moderated by the auth user - pub community: Option, + pub community_id: Option, pub auth: String, } -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize)] pub struct ListCommentReportsResponse { - pub comments: Vec, + pub comment_reports: Vec, } diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index c38d4e620..19715c3a5 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -8,11 +8,7 @@ pub mod websocket; use crate::site::FederatedInstances; use diesel::PgConnection; use lemmy_db_queries::{ - source::{ - community::{CommunityModerator_, Community_}, - person_block::PersonBlock_, - site::Site_, - }, + source::{community::Community_, person_block::PersonBlock_, site::Site_}, Crud, DbPool, Readable, @@ -20,7 +16,7 @@ use lemmy_db_queries::{ use lemmy_db_schema::{ source::{ comment::Comment, - community::{Community, CommunityModerator}, + community::Community, person::Person, person_block::PersonBlock, person_mention::{PersonMention, PersonMentionForm}, @@ -384,31 +380,6 @@ pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), Le Ok(()) } -/// Returns a list of communities that the user moderates -/// or if a community_id is supplied validates the user is a moderator -/// of that community and returns the community id in a vec -/// -/// * `person_id` - the person id of the moderator -/// * `community_id` - optional community id to check for moderator privileges -/// * `pool` - the diesel db pool -pub async fn collect_moderated_communities( - person_id: PersonId, - community_id: Option, - pool: &DbPool, -) -> Result, LemmyError> { - if let Some(community_id) = community_id { - // if the user provides a community_id, just check for mod/admin privileges - is_mod_or_admin(pool, person_id, community_id).await?; - Ok(vec![community_id]) - } else { - let ids = blocking(pool, move |conn: &'_ _| { - CommunityModerator::get_person_moderated_communities(conn, person_id) - }) - .await??; - Ok(ids) - } -} - pub async fn build_federated_instances( pool: &DbPool, federation_config: &FederationConfig, diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index a63e55d7e..c71cf540b 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -253,15 +253,15 @@ pub struct PrivateMessageResponse { pub private_message_view: PrivateMessageView, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize)] pub struct GetReportCount { - pub community: Option, + pub community_id: Option, pub auth: String, } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Clone)] pub struct GetReportCountResponse { - pub community: Option, + pub community_id: Option, pub comment_reports: i64, pub post_reports: i64, } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index 5a83a0c50..3926ed8c7 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -1,4 +1,4 @@ -use lemmy_db_schema::{CommunityId, PostId}; +use lemmy_db_schema::{CommunityId, PostId, PostReportId}; use lemmy_db_views::{ comment_view::CommentView, post_report_view::PostReportView, @@ -112,42 +112,39 @@ pub struct SavePost { pub auth: String, } -#[derive(Serialize, Deserialize)] +#[derive(Deserialize)] pub struct CreatePostReport { pub post_id: PostId, pub reason: String, pub auth: String, } -#[derive(Serialize, Deserialize, Clone)] -pub struct CreatePostReportResponse { - pub success: bool, +#[derive(Serialize, Clone)] +pub struct PostReportResponse { + pub post_report_view: PostReportView, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize)] pub struct ResolvePostReport { - pub report_id: i32, + pub report_id: PostReportId, pub resolved: bool, pub auth: String, } -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ResolvePostReportResponse { - pub report_id: i32, - pub resolved: bool, -} - -#[derive(Serialize, Deserialize, Debug)] +#[derive(Deserialize)] pub struct ListPostReports { pub page: Option, pub limit: Option, - pub community: Option, + /// Only shows the unresolved reports + pub unresolved_only: Option, + /// if no community is given, it returns reports for all communities moderated by the auth user + pub community_id: Option, pub auth: String, } -#[derive(Serialize, Clone, Debug)] +#[derive(Serialize)] pub struct ListPostReportsResponse { - pub posts: Vec, + pub post_reports: Vec, } #[derive(Deserialize, Debug)] diff --git a/crates/db_queries/src/lib.rs b/crates/db_queries/src/lib.rs index dbd470e4d..a94fb4659 100644 --- a/crates/db_queries/src/lib.rs +++ b/crates/db_queries/src/lib.rs @@ -131,13 +131,22 @@ pub trait Readable { pub trait Reportable { type Form; + type IdType; fn report(conn: &PgConnection, form: &Self::Form) -> Result where Self: Sized; - fn resolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result + fn resolve( + conn: &PgConnection, + report_id: Self::IdType, + resolver_id: PersonId, + ) -> Result where Self: Sized; - fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result + fn unresolve( + conn: &PgConnection, + report_id: Self::IdType, + resolver_id: PersonId, + ) -> Result where Self: Sized; } diff --git a/crates/db_queries/src/source/comment_report.rs b/crates/db_queries/src/source/comment_report.rs index 738ec3e29..10b9d43e8 100644 --- a/crates/db_queries/src/source/comment_report.rs +++ b/crates/db_queries/src/source/comment_report.rs @@ -3,11 +3,13 @@ use diesel::{dsl::*, result::Error, *}; use lemmy_db_schema::{ naive_now, source::comment_report::{CommentReport, CommentReportForm}, + CommentReportId, PersonId, }; impl Reportable for CommentReport { type Form = CommentReportForm; + type IdType = CommentReportId; /// creates a comment report and returns it /// /// * `conn` - the postgres connection @@ -26,7 +28,7 @@ impl Reportable for CommentReport { /// * `by_resolver_id` - the id of the user resolving the report fn resolve( conn: &PgConnection, - report_id: i32, + report_id: Self::IdType, by_resolver_id: PersonId, ) -> Result { use lemmy_db_schema::schema::comment_report::dsl::*; @@ -46,7 +48,7 @@ impl Reportable for CommentReport { /// * `by_resolver_id` - the id of the user unresolving the report fn unresolve( conn: &PgConnection, - report_id: i32, + report_id: Self::IdType, by_resolver_id: PersonId, ) -> Result { use lemmy_db_schema::schema::comment_report::dsl::*; diff --git a/crates/db_queries/src/source/post_report.rs b/crates/db_queries/src/source/post_report.rs index 19cd5dfea..e787fed4c 100644 --- a/crates/db_queries/src/source/post_report.rs +++ b/crates/db_queries/src/source/post_report.rs @@ -1,9 +1,11 @@ use crate::Reportable; use diesel::{dsl::*, result::Error, *}; -use lemmy_db_schema::{naive_now, source::post_report::*, PersonId}; +use lemmy_db_schema::{naive_now, source::post_report::*, PersonId, PostReportId}; impl Reportable for PostReport { type Form = PostReportForm; + type IdType = PostReportId; + /// creates a post report and returns it /// /// * `conn` - the postgres connection @@ -22,7 +24,7 @@ impl Reportable for PostReport { /// * `by_resolver_id` - the id of the user resolving the report fn resolve( conn: &PgConnection, - report_id: i32, + report_id: Self::IdType, by_resolver_id: PersonId, ) -> Result { use lemmy_db_schema::schema::post_report::dsl::*; @@ -42,7 +44,7 @@ impl Reportable for PostReport { /// * `by_resolver_id` - the id of the user unresolving the report fn unresolve( conn: &PgConnection, - report_id: i32, + report_id: Self::IdType, by_resolver_id: PersonId, ) -> Result { use lemmy_db_schema::schema::post_report::dsl::*; diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index ba49a4cd8..cc29848de 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -73,6 +73,12 @@ pub struct PersonBlockId(i32); #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)] pub struct CommunityBlockId(i32); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)] +pub struct CommentReportId(i32); + +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)] +pub struct PostReportId(i32); + #[repr(transparent)] #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)] #[sql_type = "Text"] diff --git a/crates/db_schema/src/source/comment_report.rs b/crates/db_schema/src/source/comment_report.rs index 7b47bef20..57ca961de 100644 --- a/crates/db_schema/src/source/comment_report.rs +++ b/crates/db_schema/src/source/comment_report.rs @@ -1,4 +1,10 @@ -use crate::{schema::comment_report, source::comment::Comment, CommentId, PersonId}; +use crate::{ + schema::comment_report, + source::comment::Comment, + CommentId, + CommentReportId, + PersonId, +}; use serde::{Deserialize, Serialize}; #[derive( @@ -7,7 +13,7 @@ use serde::{Deserialize, Serialize}; #[belongs_to(Comment)] #[table_name = "comment_report"] pub struct CommentReport { - pub id: i32, + pub id: CommentReportId, pub creator_id: PersonId, pub comment_id: CommentId, pub original_comment_text: String, diff --git a/crates/db_schema/src/source/post_report.rs b/crates/db_schema/src/source/post_report.rs index d32d7e1e4..a146a4bef 100644 --- a/crates/db_schema/src/source/post_report.rs +++ b/crates/db_schema/src/source/post_report.rs @@ -1,4 +1,4 @@ -use crate::{schema::post_report, source::post::Post, DbUrl, PersonId, PostId}; +use crate::{schema::post_report, source::post::Post, DbUrl, PersonId, PostId, PostReportId}; use serde::{Deserialize, Serialize}; #[derive( @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; #[belongs_to(Post)] #[table_name = "post_report"] pub struct PostReport { - pub id: i32, + pub id: PostReportId, pub creator_id: PersonId, pub post_id: PostId, pub original_post_name: String, diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 2aa4bdfcf..497dec6d6 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -1,15 +1,35 @@ use diesel::{result::Error, *}; -use lemmy_db_queries::{limit_and_offset, MaybeOptional, ToSafe, ViewToVec}; +use lemmy_db_queries::{ + aggregates::comment_aggregates::CommentAggregates, + limit_and_offset, + MaybeOptional, + ToSafe, + ViewToVec, +}; use lemmy_db_schema::{ - schema::{comment, comment_report, community, person, person_alias_1, person_alias_2, post}, + schema::{ + comment, + comment_aggregates, + comment_like, + comment_report, + community, + community_moderator, + community_person_ban, + person, + person_alias_1, + person_alias_2, + post, + }, source::{ comment::Comment, comment_report::CommentReport, - community::{Community, CommunitySafe}, + community::{Community, CommunityPersonBan, CommunitySafe}, person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2}, post::Post, }, + CommentReportId, CommunityId, + PersonId, }; use serde::Serialize; @@ -21,6 +41,9 @@ pub struct CommentReportView { pub community: CommunitySafe, pub creator: PersonSafe, pub comment_creator: PersonSafeAlias1, + pub counts: CommentAggregates, + pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan + pub my_vote: Option, // Left join to CommentLike pub resolver: Option, } @@ -31,6 +54,9 @@ type CommentReportViewTuple = ( CommunitySafe, PersonSafe, PersonSafeAlias1, + CommentAggregates, + Option, + Option, Option, ); @@ -38,28 +64,68 @@ impl CommentReportView { /// returns the CommentReportView for the provided report_id /// /// * `report_id` - the report id to obtain - pub fn read(conn: &PgConnection, report_id: i32) -> Result { - let (comment_report, comment, post, community, creator, comment_creator, resolver) = - comment_report::table - .find(report_id) - .inner_join(comment::table) - .inner_join(post::table.on(comment::post_id.eq(post::id))) - .inner_join(community::table.on(post::community_id.eq(community::id))) - .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) - .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) - .left_join( - person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())), - ) - .select(( - comment_report::all_columns, - comment::all_columns, - post::all_columns, - Community::safe_columns_tuple(), - Person::safe_columns_tuple(), - PersonAlias1::safe_columns_tuple(), - PersonAlias2::safe_columns_tuple().nullable(), - )) - .first::(conn)?; + pub fn read( + conn: &PgConnection, + report_id: CommentReportId, + my_person_id: PersonId, + ) -> Result { + let ( + comment_report, + comment, + post, + community, + creator, + comment_creator, + counts, + creator_banned_from_community, + comment_like, + resolver, + ) = comment_report::table + .find(report_id) + .inner_join(comment::table) + .inner_join(post::table.on(comment::post_id.eq(post::id))) + .inner_join(community::table.on(post::community_id.eq(community::id))) + .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) + .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) + .inner_join( + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), + ) + .left_join( + community_person_ban::table.on( + community::id + .eq(community_person_ban::community_id) + .and(community_person_ban::person_id.eq(comment::creator_id)), + ), + ) + .left_join( + comment_like::table.on( + comment::id + .eq(comment_like::comment_id) + .and(comment_like::person_id.eq(my_person_id)), + ), + ) + .left_join( + person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())), + ) + .select(( + comment_report::all_columns, + comment::all_columns, + post::all_columns, + Community::safe_columns_tuple(), + Person::safe_columns_tuple(), + PersonAlias1::safe_columns_tuple(), + comment_aggregates::all_columns, + community_person_ban::all_columns.nullable(), + comment_like::score.nullable(), + PersonAlias2::safe_columns_tuple().nullable(), + )) + .first::(conn)?; + + let my_vote = if comment_like.is_none() { + None + } else { + comment_like + }; Ok(Self { comment_report, @@ -68,6 +134,9 @@ impl CommentReportView { community, creator, comment_creator, + counts, + creator_banned_from_community: creator_banned_from_community.is_some(), + my_vote, resolver, }) } @@ -76,46 +145,60 @@ impl CommentReportView { /// /// * `community_ids` - a Vec of community_ids to get a count for /// TODO this eq_any is a bad way to do this, would be better to join to communitymoderator + /// TODO FIX THIS NOW /// for a person id pub fn get_report_count( conn: &PgConnection, - community_ids: &[CommunityId], + my_person_id: PersonId, + community_id: Option, ) -> Result { use diesel::dsl::*; - comment_report::table + + let mut query = comment_report::table .inner_join(comment::table) .inner_join(post::table.on(comment::post_id.eq(post::id))) - .filter( - comment_report::resolved - .eq(false) - .and(post::community_id.eq_any(community_ids)), + // Test this join + .inner_join( + community_moderator::table.on( + community_moderator::community_id + .eq(post::community_id) + .and(community_moderator::person_id.eq(my_person_id)), + ), ) - .select(count(comment_report::id)) - .first::(conn) + .filter(comment_report::resolved.eq(false)) + .into_boxed(); + + if let Some(community_id) = community_id { + query = query.filter(post::community_id.eq(community_id)) + } + + query.select(count(comment_report::id)).first::(conn) } } pub struct CommentReportQueryBuilder<'a> { conn: &'a PgConnection, - community_ids: Option>, // TODO bad way to do this + my_person_id: PersonId, + community_id: Option, page: Option, limit: Option, - resolved: Option, + unresolved_only: Option, } impl<'a> CommentReportQueryBuilder<'a> { - pub fn create(conn: &'a PgConnection) -> Self { + pub fn create(conn: &'a PgConnection, my_person_id: PersonId) -> Self { CommentReportQueryBuilder { conn, - community_ids: None, + my_person_id, + community_id: None, page: None, limit: None, - resolved: Some(false), + unresolved_only: Some(true), } } - pub fn community_ids>>(mut self, community_ids: T) -> Self { - self.community_ids = community_ids.get_optional(); + pub fn community_id>(mut self, community_id: T) -> Self { + self.community_id = community_id.get_optional(); self } @@ -129,8 +212,8 @@ impl<'a> CommentReportQueryBuilder<'a> { self } - pub fn resolved>(mut self, resolved: T) -> Self { - self.resolved = resolved.get_optional(); + pub fn unresolved_only>(mut self, unresolved_only: T) -> Self { + self.unresolved_only = unresolved_only.get_optional(); self } @@ -141,6 +224,31 @@ impl<'a> CommentReportQueryBuilder<'a> { .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) + // Test this join + .inner_join( + community_moderator::table.on( + community_moderator::community_id + .eq(post::community_id) + .and(community_moderator::person_id.eq(self.my_person_id)), + ), + ) + .inner_join( + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), + ) + .left_join( + community_person_ban::table.on( + community::id + .eq(community_person_ban::community_id) + .and(community_person_ban::person_id.eq(comment::creator_id)), + ), + ) + .left_join( + comment_like::table.on( + comment::id + .eq(comment_like::comment_id) + .and(comment_like::person_id.eq(self.my_person_id)), + ), + ) .left_join( person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())), ) @@ -151,16 +259,19 @@ impl<'a> CommentReportQueryBuilder<'a> { Community::safe_columns_tuple(), Person::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(), + comment_aggregates::all_columns, + community_person_ban::all_columns.nullable(), + comment_like::score.nullable(), PersonAlias2::safe_columns_tuple().nullable(), )) .into_boxed(); - if let Some(comm_ids) = self.community_ids { - query = query.filter(post::community_id.eq_any(comm_ids)); + if let Some(community_id) = self.community_id { + query = query.filter(post::community_id.eq(community_id)); } - if let Some(resolved_flag) = self.resolved { - query = query.filter(comment_report::resolved.eq(resolved_flag)); + if self.unresolved_only.unwrap_or(false) { + query = query.filter(comment_report::resolved.eq(false)); } let (limit, offset) = limit_and_offset(self.page, self.limit); @@ -187,8 +298,281 @@ impl ViewToVec for CommentReportView { community: a.3.to_owned(), creator: a.4.to_owned(), comment_creator: a.5.to_owned(), - resolver: a.6.to_owned(), + counts: a.6.to_owned(), + creator_banned_from_community: a.7.is_some(), + my_vote: a.8, + resolver: a.9.to_owned(), }) .collect::>() } } + +#[cfg(test)] +mod tests { + use crate::comment_report_view::{CommentReportQueryBuilder, CommentReportView}; + use lemmy_db_queries::{ + aggregates::comment_aggregates::CommentAggregates, + establish_unpooled_connection, + Crud, + Joinable, + Reportable, + }; + use lemmy_db_schema::source::{comment::*, comment_report::*, community::*, person::*, post::*}; + use serial_test::serial; + + #[test] + #[serial] + fn test_crud() { + let conn = establish_unpooled_connection(); + + let new_person = PersonForm { + name: "timmy_crv".into(), + ..PersonForm::default() + }; + + let inserted_timmy = Person::create(&conn, &new_person).unwrap(); + + let new_person_2 = PersonForm { + name: "sara_crv".into(), + ..PersonForm::default() + }; + + let inserted_sara = Person::create(&conn, &new_person_2).unwrap(); + + // Add a third person, since new ppl can only report something once. + let new_person_3 = PersonForm { + name: "jessica_crv".into(), + ..PersonForm::default() + }; + + let inserted_jessica = Person::create(&conn, &new_person_3).unwrap(); + + let new_community = CommunityForm { + name: "test community crv".to_string(), + title: "nada".to_owned(), + ..CommunityForm::default() + }; + + let inserted_community = Community::create(&conn, &new_community).unwrap(); + + // Make timmy a mod + let timmy_moderator_form = CommunityModeratorForm { + community_id: inserted_community.id, + person_id: inserted_timmy.id, + }; + + let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap(); + + let new_post = PostForm { + name: "A test post crv".into(), + creator_id: inserted_timmy.id, + community_id: inserted_community.id, + ..PostForm::default() + }; + + let inserted_post = Post::create(&conn, &new_post).unwrap(); + + let comment_form = CommentForm { + content: "A test comment 32".into(), + creator_id: inserted_timmy.id, + post_id: inserted_post.id, + ..CommentForm::default() + }; + + let inserted_comment = Comment::create(&conn, &comment_form).unwrap(); + + // sara reports + let sara_report_form = CommentReportForm { + creator_id: inserted_sara.id, + comment_id: inserted_comment.id, + original_comment_text: "this was it at time of creation".into(), + reason: "from sara".into(), + }; + + let inserted_sara_report = CommentReport::report(&conn, &sara_report_form).unwrap(); + + // jessica reports + let jessica_report_form = CommentReportForm { + creator_id: inserted_jessica.id, + comment_id: inserted_comment.id, + original_comment_text: "this was it at time of creation".into(), + reason: "from jessica".into(), + }; + + let inserted_jessica_report = CommentReport::report(&conn, &jessica_report_form).unwrap(); + + let agg = CommentAggregates::read(&conn, inserted_comment.id).unwrap(); + + let read_jessica_report_view = + CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + let expected_jessica_report_view = CommentReportView { + comment_report: inserted_jessica_report.to_owned(), + comment: inserted_comment.to_owned(), + post: inserted_post, + community: CommunitySafe { + id: inserted_community.id, + name: inserted_community.name, + icon: None, + removed: false, + deleted: false, + nsfw: false, + actor_id: inserted_community.actor_id.to_owned(), + local: true, + title: inserted_community.title, + description: None, + updated: None, + banner: None, + published: inserted_community.published, + }, + creator: PersonSafe { + id: inserted_jessica.id, + name: inserted_jessica.name, + display_name: None, + published: inserted_jessica.published, + avatar: None, + actor_id: inserted_jessica.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_jessica.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }, + comment_creator: PersonSafeAlias1 { + id: inserted_timmy.id, + name: inserted_timmy.name.to_owned(), + display_name: None, + published: inserted_timmy.published, + avatar: None, + actor_id: inserted_timmy.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_timmy.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }, + creator_banned_from_community: false, + counts: CommentAggregates { + id: agg.id, + comment_id: inserted_comment.id, + score: 0, + upvotes: 0, + downvotes: 0, + published: agg.published, + }, + my_vote: None, + resolver: None, + }; + + assert_eq!(read_jessica_report_view, expected_jessica_report_view); + + let mut expected_sara_report_view = expected_jessica_report_view.clone(); + expected_sara_report_view.comment_report = inserted_sara_report; + expected_sara_report_view.creator = PersonSafe { + id: inserted_sara.id, + name: inserted_sara.name, + display_name: None, + published: inserted_sara.published, + avatar: None, + actor_id: inserted_sara.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_sara.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }; + + // Do a batch read of timmys reports + let reports = CommentReportQueryBuilder::create(&conn, inserted_timmy.id) + .list() + .unwrap(); + + assert_eq!( + reports, + [ + expected_sara_report_view.to_owned(), + expected_jessica_report_view.to_owned() + ] + ); + + // Make sure the counts are correct + let report_count = CommentReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap(); + assert_eq!(2, report_count); + + // Try to resolve the report + CommentReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + let read_jessica_report_view_after_resolve = + CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + + let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view; + expected_jessica_report_view_after_resolve + .comment_report + .resolved = true; + expected_jessica_report_view_after_resolve + .comment_report + .resolver_id = Some(inserted_timmy.id); + expected_jessica_report_view_after_resolve + .comment_report + .updated = read_jessica_report_view_after_resolve + .comment_report + .updated; + expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 { + id: inserted_timmy.id, + name: inserted_timmy.name.to_owned(), + display_name: None, + published: inserted_timmy.published, + avatar: None, + actor_id: inserted_timmy.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_timmy.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }); + + assert_eq!( + read_jessica_report_view_after_resolve, + expected_jessica_report_view_after_resolve + ); + + // Do a batch read of timmys reports + // It should only show saras, which is unresolved + let reports_after_resolve = CommentReportQueryBuilder::create(&conn, inserted_timmy.id) + .list() + .unwrap(); + assert_eq!(reports_after_resolve[0], expected_sara_report_view); + + // Make sure the counts are correct + let report_count_after_resolved = + CommentReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap(); + assert_eq!(1, report_count_after_resolved); + + Person::delete(&conn, inserted_timmy.id).unwrap(); + Person::delete(&conn, inserted_sara.id).unwrap(); + Person::delete(&conn, inserted_jessica.id).unwrap(); + Community::delete(&conn, inserted_community.id).unwrap(); + } +} diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index a3dc49e28..b7becdbc5 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -1,14 +1,33 @@ use diesel::{result::Error, *}; -use lemmy_db_queries::{limit_and_offset, MaybeOptional, ToSafe, ViewToVec}; +use lemmy_db_queries::{ + aggregates::post_aggregates::PostAggregates, + limit_and_offset, + MaybeOptional, + ToSafe, + ViewToVec, +}; use lemmy_db_schema::{ - schema::{community, person, person_alias_1, person_alias_2, post, post_report}, + schema::{ + community, + community_moderator, + community_person_ban, + person, + person_alias_1, + person_alias_2, + post, + post_aggregates, + post_like, + post_report, + }, source::{ - community::{Community, CommunitySafe}, + community::{Community, CommunityPersonBan, CommunitySafe}, person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2}, post::Post, post_report::PostReport, }, CommunityId, + PersonId, + PostReportId, }; use serde::Serialize; @@ -19,6 +38,9 @@ pub struct PostReportView { pub community: CommunitySafe, pub creator: PersonSafe, pub post_creator: PersonSafeAlias1, + pub creator_banned_from_community: bool, + pub my_vote: Option, + pub counts: PostAggregates, pub resolver: Option, } @@ -28,6 +50,9 @@ type PostReportViewTuple = ( CommunitySafe, PersonSafe, PersonSafeAlias1, + Option, + Option, + PostAggregates, Option, ); @@ -35,13 +60,42 @@ impl PostReportView { /// returns the PostReportView for the provided report_id /// /// * `report_id` - the report id to obtain - pub fn read(conn: &PgConnection, report_id: i32) -> Result { - let (post_report, post, community, creator, post_creator, resolver) = post_report::table + pub fn read( + conn: &PgConnection, + report_id: PostReportId, + my_person_id: PersonId, + ) -> Result { + let ( + post_report, + post, + community, + creator, + post_creator, + creator_banned_from_community, + post_like, + counts, + resolver, + ) = post_report::table .find(report_id) .inner_join(post::table) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) + .left_join( + community_person_ban::table.on( + post::community_id + .eq(community_person_ban::community_id) + .and(community_person_ban::person_id.eq(post::creator_id)), + ), + ) + .left_join( + post_like::table.on( + post::id + .eq(post_like::post_id) + .and(post_like::person_id.eq(my_person_id)), + ), + ) + .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) .left_join( person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())), ) @@ -51,16 +105,24 @@ impl PostReportView { Community::safe_columns_tuple(), Person::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(), + community_person_ban::all_columns.nullable(), + post_like::score.nullable(), + post_aggregates::all_columns, PersonAlias2::safe_columns_tuple().nullable(), )) .first::(conn)?; + let my_vote = if post_like.is_none() { None } else { post_like }; + Ok(Self { post_report, post, community, creator, post_creator, + creator_banned_from_community: creator_banned_from_community.is_some(), + my_vote, + counts, resolver, }) } @@ -72,42 +134,54 @@ impl PostReportView { /// for a person id pub fn get_report_count( conn: &PgConnection, - community_ids: &[CommunityId], + my_person_id: PersonId, + community_id: Option, ) -> Result { use diesel::dsl::*; - post_report::table + let mut query = post_report::table .inner_join(post::table) - .filter( - post_report::resolved - .eq(false) - .and(post::community_id.eq_any(community_ids)), + // Test this join + .inner_join( + community_moderator::table.on( + community_moderator::community_id + .eq(post::community_id) + .and(community_moderator::person_id.eq(my_person_id)), + ), ) - .select(count(post_report::id)) - .first::(conn) + .filter(post_report::resolved.eq(false)) + .into_boxed(); + + if let Some(community_id) = community_id { + query = query.filter(post::community_id.eq(community_id)) + } + + query.select(count(post_report::id)).first::(conn) } } pub struct PostReportQueryBuilder<'a> { conn: &'a PgConnection, - community_ids: Option>, // TODO bad way to do this + my_person_id: PersonId, + community_id: Option, page: Option, limit: Option, - resolved: Option, + unresolved_only: Option, } impl<'a> PostReportQueryBuilder<'a> { - pub fn create(conn: &'a PgConnection) -> Self { + pub fn create(conn: &'a PgConnection, my_person_id: PersonId) -> Self { PostReportQueryBuilder { conn, - community_ids: None, + my_person_id, + community_id: None, page: None, limit: None, - resolved: Some(false), + unresolved_only: Some(true), } } - pub fn community_ids>>(mut self, community_ids: T) -> Self { - self.community_ids = community_ids.get_optional(); + pub fn community_id>(mut self, community_id: T) -> Self { + self.community_id = community_id.get_optional(); self } @@ -121,8 +195,8 @@ impl<'a> PostReportQueryBuilder<'a> { self } - pub fn resolved>(mut self, resolved: T) -> Self { - self.resolved = resolved.get_optional(); + pub fn unresolved_only>(mut self, unresolved_only: T) -> Self { + self.unresolved_only = unresolved_only.get_optional(); self } @@ -132,6 +206,29 @@ impl<'a> PostReportQueryBuilder<'a> { .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) + // Test this join + .inner_join( + community_moderator::table.on( + community_moderator::community_id + .eq(post::community_id) + .and(community_moderator::person_id.eq(self.my_person_id)), + ), + ) + .left_join( + community_person_ban::table.on( + post::community_id + .eq(community_person_ban::community_id) + .and(community_person_ban::person_id.eq(post::creator_id)), + ), + ) + .left_join( + post_like::table.on( + post::id + .eq(post_like::post_id) + .and(post_like::person_id.eq(self.my_person_id)), + ), + ) + .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) .left_join( person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())), ) @@ -141,16 +238,19 @@ impl<'a> PostReportQueryBuilder<'a> { Community::safe_columns_tuple(), Person::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(), + community_person_ban::all_columns.nullable(), + post_like::score.nullable(), + post_aggregates::all_columns, PersonAlias2::safe_columns_tuple().nullable(), )) .into_boxed(); - if let Some(comm_ids) = self.community_ids { - query = query.filter(post::community_id.eq_any(comm_ids)); + if let Some(community_id) = self.community_id { + query = query.filter(post::community_id.eq(community_id)); } - if let Some(resolved_flag) = self.resolved { - query = query.filter(post_report::resolved.eq(resolved_flag)); + if self.unresolved_only.unwrap_or(false) { + query = query.filter(post_report::resolved.eq(false)); } let (limit, offset) = limit_and_offset(self.page, self.limit); @@ -176,8 +276,283 @@ impl ViewToVec for PostReportView { community: a.2.to_owned(), creator: a.3.to_owned(), post_creator: a.4.to_owned(), - resolver: a.5.to_owned(), + creator_banned_from_community: a.5.is_some(), + my_vote: a.6, + counts: a.7.to_owned(), + resolver: a.8.to_owned(), }) .collect::>() } } + +#[cfg(test)] +mod tests { + use crate::post_report_view::{PostReportQueryBuilder, PostReportView}; + use lemmy_db_queries::{ + aggregates::post_aggregates::PostAggregates, + establish_unpooled_connection, + Crud, + Joinable, + Reportable, + }; + use lemmy_db_schema::source::{ + community::*, + person::*, + post::*, + post_report::{PostReport, PostReportForm}, + }; + use serial_test::serial; + + #[test] + #[serial] + fn test_crud() { + let conn = establish_unpooled_connection(); + + let new_person = PersonForm { + name: "timmy_prv".into(), + ..PersonForm::default() + }; + + let inserted_timmy = Person::create(&conn, &new_person).unwrap(); + + let new_person_2 = PersonForm { + name: "sara_prv".into(), + ..PersonForm::default() + }; + + let inserted_sara = Person::create(&conn, &new_person_2).unwrap(); + + // Add a third person, since new ppl can only report something once. + let new_person_3 = PersonForm { + name: "jessica_prv".into(), + ..PersonForm::default() + }; + + let inserted_jessica = Person::create(&conn, &new_person_3).unwrap(); + + let new_community = CommunityForm { + name: "test community prv".to_string(), + title: "nada".to_owned(), + ..CommunityForm::default() + }; + + let inserted_community = Community::create(&conn, &new_community).unwrap(); + + // Make timmy a mod + let timmy_moderator_form = CommunityModeratorForm { + community_id: inserted_community.id, + person_id: inserted_timmy.id, + }; + + let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap(); + + let new_post = PostForm { + name: "A test post crv".into(), + creator_id: inserted_timmy.id, + community_id: inserted_community.id, + ..PostForm::default() + }; + + let inserted_post = Post::create(&conn, &new_post).unwrap(); + + // sara reports + let sara_report_form = PostReportForm { + creator_id: inserted_sara.id, + post_id: inserted_post.id, + original_post_name: "Orig post".into(), + original_post_url: None, + original_post_body: None, + reason: "from sara".into(), + }; + + let inserted_sara_report = PostReport::report(&conn, &sara_report_form).unwrap(); + + // jessica reports + let jessica_report_form = PostReportForm { + creator_id: inserted_jessica.id, + post_id: inserted_post.id, + original_post_name: "Orig post".into(), + original_post_url: None, + original_post_body: None, + reason: "from jessica".into(), + }; + + let inserted_jessica_report = PostReport::report(&conn, &jessica_report_form).unwrap(); + + let agg = PostAggregates::read(&conn, inserted_post.id).unwrap(); + + let read_jessica_report_view = + PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + let expected_jessica_report_view = PostReportView { + post_report: inserted_jessica_report.to_owned(), + post: inserted_post.to_owned(), + community: CommunitySafe { + id: inserted_community.id, + name: inserted_community.name, + icon: None, + removed: false, + deleted: false, + nsfw: false, + actor_id: inserted_community.actor_id.to_owned(), + local: true, + title: inserted_community.title, + description: None, + updated: None, + banner: None, + published: inserted_community.published, + }, + creator: PersonSafe { + id: inserted_jessica.id, + name: inserted_jessica.name, + display_name: None, + published: inserted_jessica.published, + avatar: None, + actor_id: inserted_jessica.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_jessica.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }, + post_creator: PersonSafeAlias1 { + id: inserted_timmy.id, + name: inserted_timmy.name.to_owned(), + display_name: None, + published: inserted_timmy.published, + avatar: None, + actor_id: inserted_timmy.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_timmy.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }, + creator_banned_from_community: false, + my_vote: None, + counts: PostAggregates { + id: agg.id, + post_id: inserted_post.id, + comments: 0, + score: 0, + upvotes: 0, + downvotes: 0, + stickied: false, + published: agg.published, + newest_comment_time_necro: inserted_post.published, + newest_comment_time: inserted_post.published, + }, + resolver: None, + }; + + assert_eq!(read_jessica_report_view, expected_jessica_report_view); + + let mut expected_sara_report_view = expected_jessica_report_view.clone(); + expected_sara_report_view.post_report = inserted_sara_report; + expected_sara_report_view.my_vote = None; + expected_sara_report_view.creator = PersonSafe { + id: inserted_sara.id, + name: inserted_sara.name, + display_name: None, + published: inserted_sara.published, + avatar: None, + actor_id: inserted_sara.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_sara.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }; + + // Do a batch read of timmys reports + let reports = PostReportQueryBuilder::create(&conn, inserted_timmy.id) + .list() + .unwrap(); + + assert_eq!( + reports, + [ + expected_sara_report_view.to_owned(), + expected_jessica_report_view.to_owned() + ] + ); + + // Make sure the counts are correct + let report_count = PostReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap(); + assert_eq!(2, report_count); + + // Try to resolve the report + PostReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + let read_jessica_report_view_after_resolve = + PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap(); + + let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view; + expected_jessica_report_view_after_resolve + .post_report + .resolved = true; + expected_jessica_report_view_after_resolve + .post_report + .resolver_id = Some(inserted_timmy.id); + expected_jessica_report_view_after_resolve + .post_report + .updated = read_jessica_report_view_after_resolve.post_report.updated; + expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 { + id: inserted_timmy.id, + name: inserted_timmy.name.to_owned(), + display_name: None, + published: inserted_timmy.published, + avatar: None, + actor_id: inserted_timmy.actor_id.to_owned(), + local: true, + banned: false, + deleted: false, + admin: false, + bot_account: false, + bio: None, + banner: None, + updated: None, + inbox_url: inserted_timmy.inbox_url.to_owned(), + shared_inbox_url: None, + matrix_user_id: None, + }); + + assert_eq!( + read_jessica_report_view_after_resolve, + expected_jessica_report_view_after_resolve + ); + + // Do a batch read of timmys reports + // It should only show saras, which is unresolved + let reports_after_resolve = PostReportQueryBuilder::create(&conn, inserted_timmy.id) + .list() + .unwrap(); + assert_eq!(reports_after_resolve[0], expected_sara_report_view); + + // Make sure the counts are correct + let report_count_after_resolved = + PostReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap(); + assert_eq!(1, report_count_after_resolved); + + Person::delete(&conn, inserted_timmy.id).unwrap(); + Person::delete(&conn, inserted_sara.id).unwrap(); + Person::delete(&conn, inserted_jessica.id).unwrap(); + Community::delete(&conn, inserted_community.id).unwrap(); + } +}