diff --git a/lemmy_api/src/comment.rs b/lemmy_api/src/comment.rs index 5a78ba914..e9ed0a445 100644 --- a/lemmy_api/src/comment.rs +++ b/lemmy_api/src/comment.rs @@ -5,11 +5,13 @@ use crate::{ get_user_from_jwt_opt, is_mod_or_admin, Perform, + collect_moderated_communities, }; use actix_web::web::Data; use lemmy_apub::{ApubLikeableType, ApubObjectType}; use lemmy_db::{ comment::*, + comment_report::*, comment_view::*, moderator::*, post::*, @@ -20,6 +22,7 @@ use lemmy_db::{ ListingType, Saveable, SortType, + Reportable, }; use lemmy_structs::{blocking, comment::*, send_local_notifs}; use lemmy_utils::{ @@ -29,7 +32,7 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; -use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; +use lemmy_websocket::{messages::{SendComment, SendUserRoomMessage}, LemmyContext, UserOperation}; use std::str::FromStr; #[async_trait::async_trait(?Send)] @@ -682,3 +685,153 @@ impl Perform for GetComments { Ok(GetCommentsResponse { comments }) } } + +#[async_trait::async_trait(?Send)] +impl Perform for CreateCommentReport { + type Response = CreateCommentReportResponse; + + async fn perform( + &self, + context: &Data, + _websocket_id: Option, + ) -> Result { + let data: &CreateCommentReport = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + // check size of report and check for whitespace + let reason = data.reason.trim(); + if reason.is_empty() { + return Err(APIError::err("report_reason_required").into()); + } + if reason.len() > 1000 { + return Err(APIError::err("report_too_long").into()); + } + + let user_id = user.id; + let comment_id = data.comment_id; + let comment = blocking(context.pool(), move |conn| { + CommentView::read(&conn, comment_id, None) + }).await??; + + check_community_ban(user_id, comment.community_id, context.pool()).await?; + + let report_form = CommentReportForm { + creator_id: user_id, + comment_id, + comment_text: comment.content, + reason: data.reason.to_owned(), + }; + + let _report = match blocking(context.pool(), move |conn| { + CommentReport::report(conn, &report_form) + }).await? { + Ok(report) => report, + Err(_e) => return Err(APIError::err("couldnt_create_report").into()) + }; + + // to build on this, the user should get a success response, however + // mods should get a different response with more details + let res = CreateCommentReportResponse { success: true }; + + // TODO this needs to use a SendModRoomMessage + // context.chat_server().do_send(SendUserRoomMessage { + // op: UserOperation::CreateReport, + // response: res.clone(), + // recipient_id: user.id, + // websocket_id, + // }); + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for ResolveCommentReport { + type Response = ResolveCommentReportResponse; + + async fn perform( + &self, + context: &Data, + _websocket_id: Option, + ) -> Result { + let data: &ResolveCommentReport = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + let report_id = data.report_id; + let report = blocking(context.pool(), move |conn| { + CommentReportView::read(&conn, report_id) + }).await??; + + let user_id = user.id; + is_mod_or_admin(context.pool(), user_id, report.community_id).await?; + + let resolved = data.resolved; + let resolve_fun = move |conn: &'_ _| { + if resolved { + CommentReport::resolve(conn, report_id.clone(), user_id) + } else { + CommentReport::unresolve(conn, report_id.clone()) + } + }; + + if blocking(context.pool(),resolve_fun).await?.is_err() { + return Err(APIError::err("couldnt_resolve_report").into()) + }; + + let report_id = data.report_id; + let res = ResolveCommentReportResponse { + report_id, + resolved, + }; + + // TODO this needs to use a SendModRoomMessage + // context.chat_server().do_send(SendUserRoomMessage { + // op: UserOperation::ResolveCommentReport, + // response: res.clone(), + // recipient_id: user.id, + // websocket_id, + // }); + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for ListCommentReports { + type Response = ListCommentReportsResponse; + + async fn perform( + &self, + context: &Data, + websocket_id: Option, + ) -> Result { + let data: &ListCommentReports = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + let user_id = user.id; + let community_id = data.community; + let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + + let page = data.page; + let limit = data.limit; + let comments = blocking(context.pool(), move |conn| { + CommentReportQueryBuilder::create(conn) + .community_ids(community_ids) + .page(page) + .limit(limit) + .list() + }) + .await??; + + let res = ListCommentReportsResponse { comments }; + + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::ListCommentReports, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); + + Ok(res) + } +} \ No newline at end of file diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs index 5920918bf..38cf80435 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -1,14 +1,14 @@ use crate::claims::Claims; use actix_web::{web, web::Data}; use lemmy_db::{ - community::Community, + community::{Community, CommunityModerator}, community_view::CommunityUserBanView, post::Post, user::User_, Crud, DbPool, }; -use lemmy_structs::{blocking, comment::*, community::*, post::*, report::*, site::*, user::*}; +use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*}; use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError}; use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; use serde::Deserialize; @@ -19,7 +19,6 @@ pub mod claims; pub mod comment; pub mod community; pub mod post; -pub mod report; pub mod site; pub mod user; pub mod version; @@ -101,6 +100,23 @@ pub(in crate) async fn check_community_ban( } } +pub(in crate) async fn collect_moderated_communities( + user_id: i32, + 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, user_id, community_id).await?; + Ok(vec![community_id]) + } else { + let ids = blocking(pool, move |conn: &'_ _| { + CommunityModerator::get_user_moderated_communities(conn, user_id) + }).await??; + Ok(ids) + } +} + pub(in crate) fn check_optional_url(item: &Option>) -> Result<(), LemmyError> { if let Some(Some(item)) = &item { if Url::parse(item).is_err() { @@ -182,6 +198,9 @@ pub async fn match_websocket_operation( UserOperation::SaveUserSettings => { do_websocket_operation::(context, id, op, data).await } + UserOperation::GetReportCount => { + do_websocket_operation::(context, id, op, data).await + } // Private Message ops UserOperation::CreatePrivateMessage => { @@ -267,6 +286,15 @@ pub async fn match_websocket_operation( do_websocket_operation::(context, id, op, data).await } UserOperation::SavePost => do_websocket_operation::(context, id, op, data).await, + UserOperation::CreatePostReport => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::ListPostReports => { + do_websocket_operation::(context, id, op, data).await + } + UserOperation::ResolvePostReport => { + do_websocket_operation::(context, id, op, data).await + } // Comment ops UserOperation::CreateComment => { @@ -293,19 +321,14 @@ pub async fn match_websocket_operation( UserOperation::CreateCommentLike => { do_websocket_operation::(context, id, op, data).await } - - // report ops - UserOperation::CreateReport => { - do_websocket_operation::(context, id, op, data).await - } - UserOperation::ListReports => { - do_websocket_operation::(context, id, op, data).await + UserOperation::CreateCommentReport => { + do_websocket_operation::(context, id, op, data).await } - UserOperation::ResolveReport => { - do_websocket_operation::(context, id, op, data).await + UserOperation::ListCommentReports => { + do_websocket_operation::(context, id, op, data).await } - UserOperation::GetReportCount => { - do_websocket_operation::(context, id, op, data).await + UserOperation::ResolveCommentReport => { + do_websocket_operation::(context, id, op, data).await } } } diff --git a/lemmy_api/src/post.rs b/lemmy_api/src/post.rs index 755b98af2..0bf76b4d4 100644 --- a/lemmy_api/src/post.rs +++ b/lemmy_api/src/post.rs @@ -5,6 +5,7 @@ use crate::{ get_user_from_jwt_opt, is_mod_or_admin, Perform, + collect_moderated_communities, }; use actix_web::web::Data; use lemmy_apub::{ApubLikeableType, ApubObjectType}; @@ -14,6 +15,7 @@ use lemmy_db::{ moderator::*, naive_now, post::*, + post_report::*, post_view::*, site_view::*, Crud, @@ -21,6 +23,7 @@ use lemmy_db::{ ListingType, Saveable, SortType, + Reportable, }; use lemmy_structs::{blocking, post::*}; use lemmy_utils::{ @@ -32,7 +35,7 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{ - messages::{GetPostUsersOnline, JoinPostRoom, SendPost}, + messages::{GetPostUsersOnline, JoinPostRoom, SendPost, SendUserRoomMessage}, LemmyContext, UserOperation, }; @@ -741,3 +744,154 @@ impl Perform for PostJoin { Ok(PostJoinResponse { joined: true }) } } + +#[async_trait::async_trait(?Send)] +impl Perform for CreatePostReport { + type Response = CreatePostReportResponse; + + async fn perform( + &self, + context: &Data, + _websocket_id: Option, + ) -> Result { + let data: &CreatePostReport = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + // check size of report and check for whitespace + let reason = data.reason.trim(); + if reason.is_empty() { + return Err(APIError::err("report_reason_required").into()); + } + if reason.len() > 1000 { + return Err(APIError::err("report_too_long").into()); + } + + let user_id = user.id; + let post_id = data.post_id; + let post = blocking(context.pool(), move |conn| { + PostView::read(&conn, post_id, None) + }).await??; + + check_community_ban(user_id, post.community_id, context.pool()).await?; + + let report_form = PostReportForm { + creator_id: user_id, + post_id, + post_name: post.name, + post_url: post.url, + post_body: post.body, + reason: data.reason.to_owned(), + }; + + let _report = match blocking(context.pool(), move |conn| { + PostReport::report(conn, &report_form) + }).await? { + Ok(report) => report, + Err(_e) => return Err(APIError::err("couldnt_create_report").into()) + }; + + // to build on this, the user should get a success response, however + // mods should get a different response with more details + let res = CreatePostReportResponse { success: true }; + + // TODO this needs to use a SendModRoomMessage + // context.chat_server().do_send(SendUserRoomMessage { + // op: UserOperation::CreateReport, + // response: res.clone(), + // recipient_id: user.id, + // websocket_id, + // }); + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for ResolvePostReport { + type Response = ResolvePostReportResponse; + + async fn perform( + &self, + context: &Data, + _websocket_id: Option, + ) -> Result { + let data: &ResolvePostReport = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + let report_id = data.report_id; + let report = blocking(context.pool(), move |conn| { + PostReportView::read(&conn, report_id) + }).await??; + + let user_id = user.id; + is_mod_or_admin(context.pool(), user_id, report.community_id).await?; + + let resolved = data.resolved; + let resolve_fun = move |conn: &'_ _| { + if resolved { + PostReport::resolve(conn, report_id.clone(), user_id) + } else { + PostReport::unresolve(conn, report_id.clone()) + } + }; + + let res = ResolvePostReportResponse { + report_id, + resolved: true, + }; + + if blocking(context.pool(),resolve_fun).await?.is_err() { + return Err(APIError::err("couldnt_resolve_report").into()) + }; + + // TODO this needs to use a SendModRoomMessage + // context.chat_server().do_send(SendUserRoomMessage { + // op: UserOperation::ResolvePostReport, + // response: res.clone(), + // recipient_id: user.id, + // websocket_id, + // }); + + Ok(res) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for ListPostReports { + type Response = ListPostReportsResponse; + + async fn perform( + &self, + context: &Data, + websocket_id: Option, + ) -> Result { + let data: &ListPostReports = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + let user_id = user.id; + let community_id = data.community; + let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + + let page = data.page; + let limit = data.limit; + let posts = blocking(context.pool(), move |conn| { + PostReportQueryBuilder::create(conn) + .community_ids(community_ids) + .page(page) + .limit(limit) + .list() + }) + .await??; + + let res = ListPostReportsResponse { posts }; + + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::ListPostReports, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); + + Ok(res) + } +} diff --git a/lemmy_api/src/report.rs b/lemmy_api/src/report.rs deleted file mode 100644 index 5088eaa3e..000000000 --- a/lemmy_api/src/report.rs +++ /dev/null @@ -1,300 +0,0 @@ -use actix_web::web::Data; -use std::str::FromStr; - -use lemmy_db::{comment_report::*, comment_view::*, post_report::*, post_view::*, Reportable, ReportType,}; -use lemmy_structs::{blocking, report::*}; -use lemmy_utils::{APIError, ConnectionId, LemmyError}; -use lemmy_websocket::{LemmyContext, UserOperation, messages::SendUserRoomMessage}; - -use crate::{check_community_ban, get_user_from_jwt, is_mod_or_admin, Perform}; - -const MAX_REPORT_LEN: usize = 1000; - -#[async_trait::async_trait(?Send)] -impl Perform for CreateReport { - type Response = CreateReportResponse; - - async fn perform( - &self, - context: &Data, - websocket_id: Option, - ) -> Result { - let data: &CreateReport = &self; - let user = get_user_from_jwt(&data.auth, context.pool()).await?; - - // check size of report and check for whitespace - let reason = data.reason.clone(); - if reason.trim().is_empty() { - return Err(APIError::err("report_reason_required").into()); - } - if reason.len() > MAX_REPORT_LEN { - return Err(APIError::err("report_too_long").into()); - } - - let report_type = ReportType::from_str(&data.report_type)?; - let user_id = user.id; - match report_type { - ReportType::Comment => { create_comment_report(context, data, user_id).await?; } - ReportType::Post => { create_post_report(context, data, user_id).await?; } - } - - // to build on this, the user should get a success response, however - // mods should get a different response with more details - let res = CreateReportResponse { success: true }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::CreateReport, - response: res.clone(), - recipient_id: user.id, - websocket_id, - }); - - Ok(res) - } -} - -#[async_trait::async_trait(?Send)] -impl Perform for GetReportCount { - type Response = GetReportCountResponse; - - async fn perform( - &self, - context: &Data, - websocket_id: Option, - ) -> Result { - let data: &GetReportCount = &self; - let user = get_user_from_jwt(&data.auth, context.pool()).await?; - let community_id = data.community; - - // Check for mod/admin privileges - is_mod_or_admin(context.pool(), user.id, community_id).await?; - - let comment_reports = blocking(context.pool(), move |conn| { - CommentReportQueryBuilder::create(conn) - .community_id(community_id) - .resolved(false) - .count() - }) - .await??; - let post_reports = blocking(context.pool(), move |conn| { - PostReportQueryBuilder::create(conn) - .community_id(community_id) - .resolved(false) - .count() - }) - .await??; - - let res = GetReportCountResponse { - community: community_id, - comment_reports, - post_reports, - }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::ListReports, - response: res.clone(), - recipient_id: user.id, - websocket_id, - }); - - Ok(res) - } -} - -#[async_trait::async_trait(?Send)] -impl Perform for ListReports { - type Response = ListReportsResponse; - - async fn perform( - &self, - context: &Data, - websocket_id: Option, - ) -> Result { - let data: &ListReports = &self; - let user = get_user_from_jwt(&data.auth, context.pool()).await?; - let community_id = data.community; - - // Check for mod/admin privileges - is_mod_or_admin(context.pool(), user.id, community_id).await?; - - let page = data.page; - let limit = data.limit; - let comments = blocking(context.pool(), move |conn| { - CommentReportQueryBuilder::create(conn) - .community_id(community_id) - .page(page) - .limit(limit) - .list() - }) - .await??; - - let posts = blocking(context.pool(), move |conn| { - PostReportQueryBuilder::create(conn) - .community_id(community_id) - .page(page) - .limit(limit) - .list() - }) - .await??; - - let res = ListReportsResponse { comments, posts }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::ListReports, - response: res.clone(), - recipient_id: user.id, - websocket_id, - }); - - Ok(res) - } -} - -#[async_trait::async_trait(?Send)] -impl Perform for ResolveReport { - type Response = ResolveReportResponse; - - async fn perform( - &self, - context: &Data, - websocket_id: Option, - ) -> Result { - let data: &ResolveReport = &self; - let user = get_user_from_jwt(&data.auth, context.pool()).await?; - - let report_type = ReportType::from_str(&data.report_type)?; - let user_id = user.id; - match report_type { - ReportType::Comment => { resolve_comment_report(context, data, user_id).await?; } - ReportType::Post => { resolve_post_report(context, data, user_id).await?; } - } - - let report_id = data.report_id; - let res = ResolveReportResponse { - report_type: data.report_type.to_owned(), - report_id, - resolved: true, - }; - - context.chat_server().do_send(SendUserRoomMessage { - op: UserOperation::ResolveReport, - response: res.clone(), - recipient_id: user.id, - websocket_id, - }); - - Ok(res) - } -} - -async fn create_comment_report( - context: &Data, - data: &CreateReport, - user_id: i32, -) -> Result<(), LemmyError> { - let comment_id = data.entity_id; - let comment = blocking(context.pool(), move |conn| { - CommentView::read(&conn, comment_id, None) - }).await??; - - check_community_ban(user_id, comment.community_id, context.pool()).await?; - - let report_form = CommentReportForm { - creator_id: user_id, - comment_id, - comment_text: comment.content, - reason: data.reason.to_owned(), - }; - - return match blocking(context.pool(), move |conn| { - CommentReport::report(conn, &report_form) - }).await? { - Ok(_) => Ok(()), - Err(_e) => Err(APIError::err("couldnt_create_report").into()) - }; -} - -async fn create_post_report( - context: &Data, - data: &CreateReport, - user_id: i32, -) -> Result<(), LemmyError> { - let post_id = data.entity_id; - let post = blocking(context.pool(), move |conn| { - PostView::read(&conn, post_id, None) - }).await??; - - check_community_ban(user_id, post.community_id, context.pool()).await?; - - let report_form = PostReportForm { - creator_id: user_id, - post_id, - post_name: post.name, - post_url: post.url, - post_body: post.body, - reason: data.reason.to_owned(), - }; - - return match blocking(context.pool(), move |conn| { - PostReport::report(conn, &report_form) - }).await? { - Ok(_) => Ok(()), - Err(_e) => Err(APIError::err("couldnt_create_report").into()) - }; -} - -async fn resolve_comment_report( - context: &Data, - data: &ResolveReport, - user_id: i32, -) -> Result<(), LemmyError> { - let report_id = data.report_id; - let report = blocking(context.pool(), move |conn| { - CommentReportView::read(&conn, report_id) - }).await??; - - is_mod_or_admin(context.pool(), user_id, report.community_id).await?; - - let resolved = data.resolved; - let resolve_fun = move |conn: &'_ _| { - if resolved { - CommentReport::resolve(conn, report_id.clone(), user_id) - } else { - CommentReport::unresolve(conn, report_id.clone()) - } - }; - - if blocking(context.pool(),resolve_fun).await?.is_err() { - return Err(APIError::err("couldnt_resolve_report").into()) - }; - - Ok(()) -} - -async fn resolve_post_report( - context: &Data, - data: &ResolveReport, - user_id: i32, -) -> Result<(), LemmyError> { - let report_id = data.report_id; - let report = blocking(context.pool(), move |conn| { - PostReportView::read(&conn, report_id) - }).await??; - - is_mod_or_admin(context.pool(), user_id, report.community_id).await?; - - let resolved = data.resolved; - let resolve_fun = move |conn: &'_ _| { - if resolved { - PostReport::resolve(conn, report_id.clone(), user_id) - } else { - PostReport::unresolve(conn, report_id.clone()) - } - }; - - if blocking(context.pool(),resolve_fun).await?.is_err() { - return Err(APIError::err("couldnt_resolve_report").into()) - }; - - Ok(()) -} diff --git a/lemmy_api/src/user.rs b/lemmy_api/src/user.rs index 4828888ff..5efc63310 100644 --- a/lemmy_api/src/user.rs +++ b/lemmy_api/src/user.rs @@ -6,6 +6,7 @@ use crate::{ get_user_from_jwt_opt, is_admin, Perform, + collect_moderated_communities }; use actix_web::web::Data; use anyhow::Context; @@ -15,6 +16,7 @@ use chrono::Duration; use lemmy_apub::ApubObjectType; use lemmy_db::{ comment::*, + comment_report::CommentReportView, comment_view::*, community::*, community_view::*, @@ -23,6 +25,7 @@ use lemmy_db::{ naive_now, password_reset_request::*, post::*, + post_report::PostReportView, post_view::*, private_message::*, private_message_view::*, @@ -1294,3 +1297,54 @@ impl Perform for UserJoin { Ok(UserJoinResponse { joined: true }) } } + +#[async_trait::async_trait(?Send)] +impl Perform for GetReportCount { + type Response = GetReportCountResponse; + + async fn perform( + &self, + context: &Data, + websocket_id: Option, + ) -> Result { + let data: &GetReportCount = &self; + let user = get_user_from_jwt(&data.auth, context.pool()).await?; + + let user_id = user.id; + let community_id = data.community; + let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + + 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 ids = community_ids.clone(); + let post_reports = blocking(context.pool(), move |conn| + PostReportView::get_report_count(conn, &ids)).await??; + + GetReportCountResponse { + community: data.community, + comment_reports, + post_reports, + } + } + }; + + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::GetReportCount, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); + + Ok(res) + } +} diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index 2002f66e2..b9692aea1 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -50,34 +50,34 @@ impl Reportable for CommentReport { fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result { use crate::schema::comment_report::dsl::*; insert_into(comment_report) - .values(comment_report_form) - .get_result::(conn) + .values(comment_report_form) + .get_result::(conn) } fn resolve(conn: &PgConnection, report_id: i32, by_user_id: i32) -> Result { use crate::schema::comment_report::dsl::*; update(comment_report.find(report_id)) - .set(( - resolved.eq(true), - resolver_id.eq(by_user_id), - updated.eq(naive_now()), - )) - .execute(conn) + .set(( + resolved.eq(true), + resolver_id.eq(by_user_id), + updated.eq(naive_now()), + )) + .execute(conn) } fn unresolve(conn: &PgConnection, report_id: i32) -> Result { use crate::schema::comment_report::dsl::*; update(comment_report.find(report_id)) - .set(( - resolved.eq(false), - updated.eq(naive_now()), - )) - .execute(conn) + .set(( + resolved.eq(false), + updated.eq(naive_now()), + )) + .execute(conn) } } #[derive( - Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, +Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, )] #[table_name = "comment_report_view"] pub struct CommentReportView { @@ -100,7 +100,7 @@ pub struct CommentReportView { pub struct CommentReportQueryBuilder<'a> { conn: &'a PgConnection, query: comment_report_view::BoxedQuery<'a, Pg>, - for_community_id: Option, + for_community_ids: Option>, page: Option, limit: Option, resolved: Option, @@ -113,6 +113,14 @@ impl CommentReportView { .find(report_id) .first::(conn) } + + pub fn get_report_count(conn: &PgConnection, community_ids: &Vec) -> Result { + use super::comment_report::comment_report_view::dsl::*; + comment_report_view + .filter(resolved.eq(false).and(community_id.eq_any(community_ids))) + .select(sql::("COUNT(*)")) + .first::(conn) + } } impl<'a> CommentReportQueryBuilder<'a> { @@ -124,15 +132,15 @@ impl<'a> CommentReportQueryBuilder<'a> { CommentReportQueryBuilder { conn, query, - for_community_id: None, + for_community_ids: None, page: None, limit: None, resolved: Some(false), } } - pub fn community_id>(mut self, community_id: T) -> Self { - self.for_community_id = community_id.get_optional(); + pub fn community_ids>>(mut self, community_ids: T) -> Self { + self.for_community_ids = community_ids.get_optional(); self } @@ -156,8 +164,8 @@ impl<'a> CommentReportQueryBuilder<'a> { let mut query = self.query; - if let Some(comm_id) = self.for_community_id { - query = query.filter(community_id.eq(comm_id)); + if let Some(comm_ids) = self.for_community_ids { + query = query.filter(community_id.eq_any(comm_ids)); } if let Some(resolved_flag) = self.resolved { @@ -172,21 +180,4 @@ impl<'a> CommentReportQueryBuilder<'a> { .offset(offset) .load::(self.conn) } - - pub fn count(self) -> Result { - use super::comment_report::comment_report_view::dsl::*; - let mut query = self.query; - - if let Some(comm_id) = self.for_community_id { - query = query.filter(community_id.eq(comm_id)); - } - - if let Some(resolved_flag) = self.resolved { - query = query.filter(resolved.eq(resolved_flag)); - } - - query.execute(self.conn) - } } - - diff --git a/lemmy_db/src/community.rs b/lemmy_db/src/community.rs index 3473f25c7..a888015e4 100644 --- a/lemmy_db/src/community.rs +++ b/lemmy_db/src/community.rs @@ -224,6 +224,14 @@ impl CommunityModerator { use crate::schema::community_moderator::dsl::*; diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn) } + + pub fn get_user_moderated_communities(conn: &PgConnection, for_user_id: i32) -> Result, Error> { + use crate::schema::community_moderator::dsl::*; + community_moderator + .filter(user_id.eq(for_user_id)) + .select(community_id) + .load::(conn) + } } #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index 9e317f735..cf578c4cb 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -173,12 +173,6 @@ pub enum SearchType { Url, } -#[derive(EnumString, ToString, Debug, Serialize, Deserialize)] -pub enum ReportType { - Comment, - Post, -} - pub fn fuzzy_search(q: &str) -> String { let replaced = q.replace(" ", "%"); format!("%{}%", replaced) diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs index 3a2fc1565..1741edf84 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -27,58 +27,58 @@ table! { #[belongs_to(Post)] #[table_name = "post_report"] pub struct PostReport { - pub id: i32, - pub creator_id: i32, - pub post_id: i32, - pub post_name: String, - pub post_url: Option, - pub post_body: Option, - pub reason: String, - pub resolved: bool, - pub resolver_id: Option, - pub published: chrono::NaiveDateTime, - pub updated: Option, + pub id: i32, + pub creator_id: i32, + pub post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub reason: String, + pub resolved: bool, + pub resolver_id: Option, + pub published: chrono::NaiveDateTime, + pub updated: Option, } #[derive(Insertable, AsChangeset, Clone)] #[table_name = "post_report"] pub struct PostReportForm { - pub creator_id: i32, - pub post_id: i32, - pub post_name: String, - pub post_url: Option, - pub post_body: Option, - pub reason: String, + pub creator_id: i32, + pub post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub reason: String, } impl Reportable for PostReport { - fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result { - use crate::schema::post_report::dsl::*; - insert_into(post_report) - .values(post_report_form) - .get_result::(conn) - } - - fn resolve(conn: &PgConnection, report_id: i32, by_user_id: i32) -> Result { - use crate::schema::post_report::dsl::*; - update(post_report.find(report_id)) - .set(( - resolved.eq(true), - resolver_id.eq(by_user_id), - updated.eq(naive_now()), - )) - .execute(conn) - } - - fn unresolve(conn: &PgConnection, report_id: i32) -> Result { - use crate::schema::post_report::dsl::*; - update(post_report.find(report_id)) - .set(( - resolved.eq(false), - updated.eq(naive_now()), - )) - .execute(conn) - } + fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result { + use crate::schema::post_report::dsl::*; + insert_into(post_report) + .values(post_report_form) + .get_result::(conn) + } + + fn resolve(conn: &PgConnection, report_id: i32, by_user_id: i32) -> Result { + use crate::schema::post_report::dsl::*; + update(post_report.find(report_id)) + .set(( + resolved.eq(true), + resolver_id.eq(by_user_id), + updated.eq(naive_now()), + )) + .execute(conn) + } + + fn unresolve(conn: &PgConnection, report_id: i32) -> Result { + use crate::schema::post_report::dsl::*; + update(post_report.find(report_id)) + .set(( + resolved.eq(false), + updated.eq(naive_now()), + )) + .execute(conn) + } } #[derive( @@ -86,111 +86,104 @@ Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, )] #[table_name = "post_report_view"] pub struct PostReportView { - pub id: i32, - pub creator_id: i32, - pub post_id: i32, - pub post_name: String, - pub post_url: Option, - pub post_body: Option, - pub reason: String, - pub resolved: bool, - pub resolver_id: Option, - pub published: chrono::NaiveDateTime, - pub updated: Option, - pub community_id: i32, - pub creator_name: String, - pub post_creator_id: i32, - pub post_creator_name: String, + pub id: i32, + pub creator_id: i32, + pub post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub reason: String, + pub resolved: bool, + pub resolver_id: Option, + pub published: chrono::NaiveDateTime, + pub updated: Option, + pub community_id: i32, + pub creator_name: String, + pub post_creator_id: i32, + pub post_creator_name: String, } impl PostReportView { - pub fn read(conn: &PgConnection, report_id: i32) -> Result { - use super::post_report::post_report_view::dsl::*; - post_report_view - .find(report_id) - .first::(conn) - } + pub fn read(conn: &PgConnection, report_id: i32) -> Result { + use super::post_report::post_report_view::dsl::*; + post_report_view + .find(report_id) + .first::(conn) + } + + pub fn get_report_count(conn: &PgConnection, community_ids: &Vec) -> Result { + use super::post_report::post_report_view::dsl::*; + post_report_view + .filter(resolved.eq(false).and(community_id.eq_any(community_ids))) + .select(sql::("COUNT(*)")) + .first::(conn) + } } pub struct PostReportQueryBuilder<'a> { - conn: &'a PgConnection, - query: post_report_view::BoxedQuery<'a, Pg>, - for_community_id: Option, - page: Option, - limit: Option, - resolved: Option, + conn: &'a PgConnection, + query: post_report_view::BoxedQuery<'a, Pg>, + for_community_ids: Option>, + page: Option, + limit: Option, + resolved: Option, } impl<'a> PostReportQueryBuilder<'a> { - pub fn create(conn: &'a PgConnection) -> Self { - use super::post_report::post_report_view::dsl::*; - - let query = post_report_view.into_boxed(); - - PostReportQueryBuilder { - conn, - query, - for_community_id: None, - page: None, - limit: None, - resolved: Some(false), - } - } - - pub fn community_id>(mut self, community_id: T) -> Self { - self.for_community_id = community_id.get_optional(); - self + pub fn create(conn: &'a PgConnection) -> Self { + use super::post_report::post_report_view::dsl::*; + + let query = post_report_view.into_boxed(); + + PostReportQueryBuilder { + conn, + query, + for_community_ids: None, + page: None, + limit: None, + resolved: Some(false), } + } - pub fn page>(mut self, page: T) -> Self { - self.page = page.get_optional(); - self - } - - pub fn limit>(mut self, limit: T) -> Self { - self.limit = limit.get_optional(); - self - } - - pub fn resolved>(mut self, resolved: T) -> Self { - self.resolved = resolved.get_optional(); - self - } + pub fn community_ids>>(mut self, community_ids: T) -> Self { + self.for_community_ids = community_ids.get_optional(); + self + } - pub fn list(self) -> Result, Error> { - use super::post_report::post_report_view::dsl::*; + pub fn page>(mut self, page: T) -> Self { + self.page = page.get_optional(); + self + } - let mut query = self.query; + pub fn limit>(mut self, limit: T) -> Self { + self.limit = limit.get_optional(); + self + } - if let Some(comm_id) = self.for_community_id { - query = query.filter(community_id.eq(comm_id)); - } + pub fn resolved>(mut self, resolved: T) -> Self { + self.resolved = resolved.get_optional(); + self + } - if let Some(resolved_flag) = self.resolved { - query = query.filter(resolved.eq(resolved_flag)); - } + pub fn list(self) -> Result, Error> { + use super::post_report::post_report_view::dsl::*; - let (limit, offset) = limit_and_offset(self.page, self.limit); + let mut query = self.query; - query - .order_by(published.asc()) - .limit(limit) - .offset(offset) - .load::(self.conn) + if let Some(comm_ids) = self.for_community_ids { + query = query.filter(community_id.eq_any(comm_ids)); } - pub fn count(self) -> Result { - use super::post_report::post_report_view::dsl::*; - let mut query = self.query; - - if let Some(comm_id) = self.for_community_id { - query = query.filter(community_id.eq(comm_id)); - } + if let Some(resolved_flag) = self.resolved { + query = query.filter(resolved.eq(resolved_flag)); + } - if let Some(resolved_flag) = self.resolved { - query = query.filter(resolved.eq(resolved_flag)); - } + let (limit, offset) = limit_and_offset(self.page, self.limit); - query.execute(self.conn) - } + query + .order_by(published.asc()) + .limit(limit) + .offset(offset) + .load::(self.conn) + } } diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index 494c1ec87..b23deff34 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -556,38 +556,38 @@ joinable!(user_mention -> comment (comment_id)); joinable!(user_mention -> user_ (recipient_id)); allow_tables_to_appear_in_same_query!( - activity, - category, - comment, - comment_aggregates_fast, - comment_like, - comment_report, - comment_saved, - community, - community_aggregates_fast, - community_follower, - community_moderator, - community_user_ban, - mod_add, - mod_add_community, - mod_ban, - mod_ban_from_community, - mod_lock_post, - mod_remove_comment, - mod_remove_community, - mod_remove_post, - mod_sticky_post, - password_reset_request, - post, - post_aggregates_fast, - post_like, - post_read, - post_report, - post_saved, - private_message, - site, - user_, - user_ban, - user_fast, - user_mention, + activity, + category, + comment, + comment_aggregates_fast, + comment_like, + comment_report, + comment_saved, + community, + community_aggregates_fast, + community_follower, + community_moderator, + community_user_ban, + mod_add, + mod_add_community, + mod_ban, + mod_ban_from_community, + mod_lock_post, + mod_remove_comment, + mod_remove_community, + mod_remove_post, + mod_sticky_post, + password_reset_request, + post, + post_aggregates_fast, + post_like, + post_read, + post_report, + post_saved, + private_message, + site, + user_, + user_ban, + user_fast, + user_mention, ); diff --git a/lemmy_structs/Cargo.toml b/lemmy_structs/Cargo.toml index 303181e9b..8cf522c39 100644 --- a/lemmy_structs/Cargo.toml +++ b/lemmy_structs/Cargo.toml @@ -16,4 +16,4 @@ log = "0.4" diesel = "1.4" actix-web = { version = "3.0" } chrono = { version = "0.4", features = ["serde"] } -serde_json = { version = "1.0", features = ["preserve_order"]} \ No newline at end of file +serde_json = { version = "1.0", features = ["preserve_order"]} diff --git a/lemmy_structs/src/comment.rs b/lemmy_structs/src/comment.rs index 4c18a3dec..5ca7a5325 100644 --- a/lemmy_structs/src/comment.rs +++ b/lemmy_structs/src/comment.rs @@ -1,4 +1,7 @@ -use lemmy_db::comment_view::CommentView; +use lemmy_db::{ + comment_view::CommentView, + comment_report::CommentReportView, +}; use serde::{Deserialize, Serialize}; #[derive(Deserialize)] @@ -76,3 +79,41 @@ pub struct GetComments { pub struct GetCommentsResponse { pub comments: Vec, } + +#[derive(Serialize, Deserialize)] +pub struct CreateCommentReport { + pub comment_id: i32, + pub reason: String, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CreateCommentReportResponse { + pub success: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ResolveCommentReport { + pub report_id: i32, + pub resolved: bool, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ResolveCommentReportResponse { + pub report_id: i32, + pub resolved: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ListCommentReports { + pub page: Option, + pub limit: Option, + pub community: Option, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ListCommentReportsResponse { + pub comments: Vec, +} diff --git a/lemmy_structs/src/lib.rs b/lemmy_structs/src/lib.rs index 1b7ccd213..5d2e42733 100644 --- a/lemmy_structs/src/lib.rs +++ b/lemmy_structs/src/lib.rs @@ -1,7 +1,6 @@ pub mod comment; pub mod community; pub mod post; -pub mod report; pub mod site; pub mod user; pub mod websocket; diff --git a/lemmy_structs/src/post.rs b/lemmy_structs/src/post.rs index 1ccbe7e32..331c2dca4 100644 --- a/lemmy_structs/src/post.rs +++ b/lemmy_structs/src/post.rs @@ -1,6 +1,7 @@ use lemmy_db::{ comment_view::CommentView, community_view::{CommunityModeratorView, CommunityView}, + post_report::PostReportView, post_view::PostView, }; use serde::{Deserialize, Serialize}; @@ -113,3 +114,41 @@ pub struct PostJoin { pub struct PostJoinResponse { pub joined: bool, } + +#[derive(Serialize, Deserialize)] +pub struct CreatePostReport { + pub post_id: i32, + pub reason: String, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CreatePostReportResponse { + pub success: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ResolvePostReport { + pub report_id: i32, + 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)] +pub struct ListPostReports { + pub page: Option, + pub limit: Option, + pub community: Option, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ListPostReportsResponse { + pub posts: Vec, +} diff --git a/lemmy_structs/src/report.rs b/lemmy_structs/src/report.rs deleted file mode 100644 index 842bfcaaa..000000000 --- a/lemmy_structs/src/report.rs +++ /dev/null @@ -1,57 +0,0 @@ -use lemmy_db::{comment_report::CommentReportView, post_report::PostReportView}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct CreateReport { - pub report_type: String, - pub entity_id: i32, - pub reason: String, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct CreateReportResponse { - pub success: bool, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ListReports { - pub page: Option, - pub limit: Option, - pub community: i32, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ListReportsResponse { - pub posts: Vec, - pub comments: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetReportCount { - pub community: i32, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct GetReportCountResponse { - pub community: i32, - pub comment_reports: usize, - pub post_reports: usize, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ResolveReport { - pub report_type: String, - pub report_id: i32, - pub resolved: bool, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct ResolveReportResponse { - pub report_type: String, - pub report_id: i32, - pub resolved: bool, -} diff --git a/lemmy_structs/src/user.rs b/lemmy_structs/src/user.rs index 8e4ca5bd0..03a84ce4c 100644 --- a/lemmy_structs/src/user.rs +++ b/lemmy_structs/src/user.rs @@ -237,3 +237,16 @@ pub struct UserJoin { pub struct UserJoinResponse { pub joined: bool, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetReportCount { + pub community: Option, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct GetReportCountResponse { + pub community: Option, + pub comment_reports: i32, + pub post_reports: i32, +} diff --git a/lemmy_websocket/src/lib.rs b/lemmy_websocket/src/lib.rs index 30c95d6ad..7673510cd 100644 --- a/lemmy_websocket/src/lib.rs +++ b/lemmy_websocket/src/lib.rs @@ -97,6 +97,9 @@ pub enum UserOperation { MarkCommentAsRead, SaveComment, CreateCommentLike, + CreateCommentReport, + ResolveCommentReport, + ListCommentReports, GetPosts, CreatePostLike, EditPost, @@ -105,9 +108,9 @@ pub enum UserOperation { LockPost, StickyPost, SavePost, - CreateReport, - ResolveReport, - ListReports, + CreatePostReport, + ResolvePostReport, + ListPostReports, GetReportCount, EditCommunity, DeleteCommunity, diff --git a/src/routes/api.rs b/src/routes/api.rs index 6563b2305..e6d6b31a9 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -1,7 +1,7 @@ use actix_web::{error::ErrorBadRequest, *}; use lemmy_api::Perform; use lemmy_rate_limit::RateLimit; -use lemmy_structs::{comment::*, community::*, post::*, report::*, site::*, user::*}; +use lemmy_structs::{comment::*, community::*, post::*, site::*, user::*}; use lemmy_websocket::LemmyContext; use serde::Deserialize; @@ -57,7 +57,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route("/transfer", web::post().to(route_post::)) .route("/ban_user", web::post().to(route_post::)) .route("/mod", web::post().to(route_post::)) - .route("/join", web::post().to(route_post::)) + .route("/join", web::post().to(route_post::)), ) // Post .service( @@ -80,10 +80,13 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) .route("/join", web::post().to(route_post::)) + .route("/report", web::post().to(route_post::)) + .route("/report/resolve", web::put().to(route_post::)) + .route("/report/list", web::get().to(route_get::)) ) // Comment .service( - web::scope("/comment") + web::scope("/comment") .wrap(rate_limit.message()) .route("", web::post().to(route_post::)) .route("", web::put().to(route_post::)) @@ -96,6 +99,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) .route("/list", web::get().to(route_get::)) + .route("/report", web::post().to(route_post::)) + .route("/report/resolve", web::put().to(route_post::)) + .route("/report/list", web::get().to(route_get::)) ) // Private Message .service( @@ -163,6 +169,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route( "/save_user_settings", web::put().to(route_post::), + ) + .route( + "/report_count", + web::get().to(route_get::) ), ) // Admin Actions @@ -170,15 +180,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { web::resource("/admin/add") .wrap(rate_limit.message()) .route(web::post().to(route_post::)), - ) - // Reports - .service( - web::scope("/report") - .wrap(rate_limit.message()) - .route("", web::get().to(route_get::)) - .route("",web::post().to(route_post::)) - .route("/resolve",web::put().to(route_post::)) - .route("/list", web::get().to(route_get::)) ), ); }