From d6b1c8df2f552d69c3c7bf62bea95a289d3fb5f3 Mon Sep 17 00:00:00 2001 From: eiknat Date: Tue, 20 Oct 2020 20:31:01 -0400 Subject: [PATCH] reports: update db tables, combine api impl --- Cargo.lock | 3 - lemmy_api/src/lib.rs | 27 +- lemmy_api/src/report.rs | 494 +++++++----------- lemmy_db/Cargo.toml | 3 +- lemmy_db/src/comment_report.rs | 90 ++-- lemmy_db/src/lib.rs | 11 +- lemmy_db/src/post_report.rs | 102 ++-- lemmy_db/src/schema.rs | 96 ++-- lemmy_structs/Cargo.toml | 3 +- lemmy_structs/src/report.rs | 75 +-- lemmy_websocket/src/lib.rs | 11 +- .../down.sql | 1 - .../up.sql | 50 +- src/routes/api.rs | 18 +- 14 files changed, 431 insertions(+), 553 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8abe539bb..c5f984783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,7 +1054,6 @@ dependencies = [ "pq-sys", "r2d2", "serde_json", - "uuid 0.6.5", ] [[package]] @@ -1834,7 +1833,6 @@ dependencies = [ "strum", "strum_macros", "url", - "uuid 0.6.5", ] [[package]] @@ -1900,7 +1898,6 @@ dependencies = [ "log", "serde 1.0.117", "serde_json", - "uuid 0.6.5", ] [[package]] diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs index 6b35f6750..5920918bf 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -267,15 +267,6 @@ 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 => { @@ -302,14 +293,16 @@ pub async fn match_websocket_operation( UserOperation::CreateCommentLike => { do_websocket_operation::(context, id, op, data).await } - UserOperation::CreateCommentReport => { - do_websocket_operation::(context, id, op, data).await - }, - UserOperation::ListCommentReports => { - do_websocket_operation::(context, id, op, data).await - }, - UserOperation::ResolveCommentReport => { - 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::ResolveReport => { + do_websocket_operation::(context, id, op, data).await } UserOperation::GetReportCount => { do_websocket_operation::(context, id, op, data).await diff --git a/lemmy_api/src/report.rs b/lemmy_api/src/report.rs index f5557500e..5088eaa3e 100644 --- a/lemmy_api/src/report.rs +++ b/lemmy_api/src/report.rs @@ -1,119 +1,55 @@ use actix_web::web::Data; +use std::str::FromStr; -use lemmy_db::{ - comment_report::*, - comment_view::*, - community_view::*, - post_report::*, - post_view::*, - Reportable, - user_view::UserView, -}; +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; +use lemmy_websocket::{LemmyContext, UserOperation, messages::SendUserRoomMessage}; -use crate::{check_community_ban, get_user_from_jwt, Perform}; +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 CreateCommentReport { - type Response = CommentReportResponse; +impl Perform for CreateReport { + type Response = CreateReportResponse; 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: Option = match data.reason.clone() { - Some(s) if s.trim().is_empty() => None, - Some(s) if s.len() > MAX_REPORT_LEN => { - return Err(APIError::err("report_too_long").into()); - } - Some(s) => Some(s), - None => None, - }; - - // Fetch comment information - let comment_id = data.comment; - let comment = blocking(context.pool(), move |conn| CommentView::read(&conn, comment_id, None)).await??; - - // Check for community ban - check_community_ban(user.id, comment.community_id, context.pool()).await?; - - // Insert the report - let comment_time = match comment.updated { - Some(s) => s, - None => comment.published, - }; - let report_form = CommentReportForm { - time: None, // column defaults to now() in table - reason, - resolved: None, // column defaults to false - user_id: user.id, - comment_id, - comment_text: comment.content, - comment_time, - }; - blocking(context.pool(), move |conn| CommentReport::report(conn, &report_form)).await??; - - Ok(CommentReportResponse { success: true }) - } -} - -#[async_trait::async_trait(?Send)] -impl Perform for CreatePostReport { - type Response = PostReportResponse; - - async fn perform( - &self, - context: &Data, - _websocket_id: Option, - ) -> Result { - let data: &CreatePostReport = &self; + 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: Option = match data.reason.clone() { - Some(s) if s.trim().is_empty() => None, - Some(s) if s.len() > MAX_REPORT_LEN => { - return Err(APIError::err("report_too_long").into()); - } - Some(s) => Some(s), - None => None, - }; + // 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()); + } - // Fetch post information from the database - let post_id = data.post; - let post = blocking(context.pool(), move |conn| PostView::read(&conn, post_id, None)).await??; + 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?; } + } - // Check for community ban - check_community_ban(user.id, post.community_id, context.pool()).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 }; - // Insert the report - let post_time = match post.updated { - Some(s) => s, - None => post.published, - }; - let report_form = PostReportForm { - time: None, // column defaults to now() in table - reason, - resolved: None, // column defaults to false - user_id: user.id, - post_id, - post_name: post.name, - post_url: post.url, - post_body: post.body, - post_time, - }; - blocking(context.pool(), move |conn| PostReport::report(conn, &report_form)).await??; + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::CreateReport, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); - Ok(PostReportResponse { success: true }) + Ok(res) } } @@ -124,39 +60,14 @@ impl Perform for GetReportCount { async fn perform( &self, context: &Data, - _websocket_id: Option, + 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 community exists. - let community_id = blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await?? - .id; - // Check community ban - check_community_ban(user.id, data.community, context.pool()).await?; - - let mut mod_ids: Vec = Vec::new(); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - CommunityModeratorView::for_community(conn, community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - if !mod_ids.contains(&user.id) { - return Err(APIError::err("report_view_not_allowed").into()); - } + // 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) @@ -173,59 +84,42 @@ impl Perform for GetReportCount { }) .await??; - let response = GetReportCountResponse { + let res = GetReportCountResponse { community: community_id, comment_reports, post_reports, }; - Ok(response) + 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 ListCommentReports { - type Response = ListCommentReportResponse; +impl Perform for ListReports { + type Response = ListReportsResponse; async fn perform( &self, context: &Data, - _websocket_id: Option, - ) -> Result { - let data: &ListCommentReports = &self; + 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 community exists. - let community_id = blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await?? - .id; - check_community_ban(user.id, data.community, context.pool()).await?; - - let mut mod_ids: Vec = Vec::new(); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - CommunityModeratorView::for_community(conn, community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - if !mod_ids.contains(&user.id) { - return Err(APIError::err("report_view_not_allowed").into()); - } + // 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 reports = blocking(context.pool(), move |conn| { + let comments = blocking(context.pool(), move |conn| { CommentReportQueryBuilder::create(conn) .community_id(community_id) .page(page) @@ -234,161 +128,173 @@ impl Perform for ListCommentReports { }) .await??; - Ok(ListCommentReportResponse { reports }) - } -} - -#[async_trait::async_trait(?Send)] -impl Perform for ListPostReports { - type Response = ListPostReportResponse; - - 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 community_id = data.community; - //Check community exists. - let community_id = blocking(context.pool(), move |conn| { - CommunityView::read(conn, community_id, None) - }) - .await?? - .id; - // Check for community ban - check_community_ban(user.id, data.community, context.pool()).await?; - - let mut mod_ids: Vec = Vec::new(); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - CommunityModeratorView::for_community(conn, community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - if !mod_ids.contains(&user.id) { - return Err(APIError::err("report_view_not_allowed").into()); - } - - let page = data.page; - let limit = data.limit; - let reports = blocking(context.pool(), move |conn| { + let posts = blocking(context.pool(), move |conn| { PostReportQueryBuilder::create(conn) - .community_id(community_id) - .page(page) - .limit(limit) - .list() + .community_id(community_id) + .page(page) + .limit(limit) + .list() }) - .await??; + .await??; - Ok(ListPostReportResponse { reports }) + 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 ResolveCommentReport { - type Response = ResolveCommentReportResponse; +impl Perform for ResolveReport { + type Response = ResolveReportResponse; async fn perform( &self, context: &Data, - _websocket_id: Option, - ) -> Result { - let data: &ResolveCommentReport = &self; + websocket_id: Option, + ) -> Result { + let data: &ResolveReport = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; - // Fetch the report view - let report_id = data.report; - let report = blocking(context.pool(), move |conn| CommentReportView::read(&conn, &report_id)).await??; - - // Check for community ban - check_community_ban(user.id, report.community_id, context.pool()).await?; - - // Check for mod/admin privileges - let mut mod_ids: Vec = Vec::new(); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - CommunityModeratorView::for_community(conn, report.community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - if !mod_ids.contains(&user.id) { - return Err(APIError::err("resolve_report_not_allowed").into()); + 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?; } } - blocking(context.pool(), move |conn| { - CommentReport::resolve(conn, &report_id.clone()) - }) - .await??; - - Ok(ResolveCommentReportResponse { - report: report_id, + 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_trait::async_trait(?Send)] -impl Perform for ResolvePostReport { - type Response = ResolvePostReportResponse; +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??; - 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?; + check_community_ban(user_id, comment.community_id, context.pool()).await?; - // Fetch the report view - let report_id = data.report; - let report = blocking(context.pool(), move |conn| PostReportView::read(&conn, &report_id)).await??; + let report_form = CommentReportForm { + creator_id: user_id, + comment_id, + comment_text: comment.content, + reason: data.reason.to_owned(), + }; - // Check for community ban - check_community_ban(user.id, report.community_id, context.pool()).await?; - - // Check for mod/admin privileges - let mut mod_ids: Vec = Vec::new(); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - CommunityModeratorView::for_community(conn, report.community_id) - .map(|v| v.into_iter().map(|m| m.user_id).collect()) - }) - .await??, - ); - mod_ids.append( - &mut blocking(context.pool(), move |conn| { - UserView::admins(conn).map(|v| v.into_iter().map(|a| a.id).collect()) - }) - .await??, - ); - if !mod_ids.contains(&user.id) { - return Err(APIError::err("resolve_report_not_allowed").into()); - } - - blocking(context.pool(), move |conn| { - PostReport::resolve(conn, &report_id.clone()) - }) - .await??; - - Ok(ResolvePostReportResponse { - report: report_id, - resolved: true, - }) - } + 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_db/Cargo.toml b/lemmy_db/Cargo.toml index 2a358a3e1..904b16937 100644 --- a/lemmy_db/Cargo.toml +++ b/lemmy_db/Cargo.toml @@ -9,7 +9,7 @@ path = "src/lib.rs" [dependencies] lemmy_utils = { path = "../lemmy_utils" } -diesel = { version = "1.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json", "uuid"] } +diesel = { version = "1.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] } chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"]} @@ -21,4 +21,3 @@ bcrypt = "0.8" url = { version = "2.1", features = ["serde"] } lazy_static = "1.3" regex = "1.3" -uuid = { version = "0.6.5", features = ["serde", "v4"] } diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index 74d05e8c6..0725256c2 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -1,31 +1,24 @@ -use diesel::{PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods, insert_into, update}; -use diesel::pg::Pg; -use diesel::result::*; +use diesel::{dsl::*, pg::Pg, result::Error, *}; use serde::{Deserialize, Serialize}; -use crate::{ - limit_and_offset, - MaybeOptional, - schema::comment_report, - comment::Comment, - Reportable, -}; +use crate::{limit_and_offset, MaybeOptional, schema::comment_report, comment::Comment, Reportable, naive_now}; table! { comment_report_view (id) { - id -> Uuid, - time -> Timestamp, - reason -> Nullable, - resolved -> Bool, - user_id -> Int4, + id -> Int4, + creator_id -> Int4, comment_id -> Int4, comment_text -> Text, - comment_time -> Timestamp, + reason -> Text, + resolved -> Bool, + resolver_id -> Nullable, + published -> Timestamp, + updated -> Nullable, post_id -> Int4, community_id -> Int4, - user_name -> Varchar, - creator_id -> Int4, creator_name -> Varchar, + comment_creator_id -> Int4, + comment_creator_name -> Varchar, } } @@ -33,26 +26,24 @@ table! { #[belongs_to(Comment)] #[table_name = "comment_report"] pub struct CommentReport { - pub id: uuid::Uuid, - pub time: chrono::NaiveDateTime, - pub reason: Option, - pub resolved: bool, - pub user_id: i32, + pub id: i32, + pub creator_id: i32, pub comment_id: i32, pub comment_text: String, - pub comment_time: chrono::NaiveDateTime, + pub reason: String, + pub resolved: bool, + pub resolver_id: Option, + pub published: chrono::NaiveDateTime, + pub updated: Option, } #[derive(Insertable, AsChangeset, Clone)] #[table_name = "comment_report"] pub struct CommentReportForm { - pub time: Option, - pub reason: Option, - pub resolved: Option, - pub user_id: i32, + pub creator_id: i32, pub comment_id: i32, pub comment_text: String, - pub comment_time: chrono::NaiveDateTime, + pub reason: String, } impl Reportable for CommentReport { @@ -63,32 +54,47 @@ impl Reportable for CommentReport { .get_result::(conn) } - fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + 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)) + .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) } } #[derive( - Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone, + Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, )] #[table_name = "comment_report_view"] pub struct CommentReportView { - pub id: uuid::Uuid, - pub time: chrono::NaiveDateTime, - pub reason: Option, - pub resolved: bool, - pub user_id: i32, + pub id: i32, + pub creator_id: i32, pub comment_id: i32, pub comment_text: String, - pub comment_time: chrono::NaiveDateTime, + pub reason: String, + pub resolved: bool, + pub resolver_id: Option, + pub published: chrono::NaiveDateTime, + pub updated: Option, pub post_id: i32, pub community_id: i32, - pub user_name: String, - pub creator_id: i32, pub creator_name: String, + pub comment_creator_id: i32, + pub comment_creator_name: String, } pub struct CommentReportQueryBuilder<'a> { @@ -101,7 +107,7 @@ pub struct CommentReportQueryBuilder<'a> { } impl CommentReportView { - pub fn read(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + pub fn read(conn: &PgConnection, report_id: i32) -> Result { use super::comment_report::comment_report_view::dsl::*; comment_report_view .filter(id.eq(report_id)) @@ -161,7 +167,7 @@ impl<'a> CommentReportQueryBuilder<'a> { let (limit, offset) = limit_and_offset(self.page, self.limit); query - .order_by(time.desc()) + .order_by(published.desc()) .limit(limit) .offset(offset) .load::(self.conn) diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index cf0e68ade..9e317f735 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -115,7 +115,10 @@ pub trait Reportable { fn report(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result + fn resolve(conn: &PgConnection, report_id: i32, user_id: i32) -> Result + where + Self: Sized; + fn unresolve(conn: &PgConnection, report_id: i32) -> Result where Self: Sized; } @@ -170,6 +173,12 @@ 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 4cb5e5701..d461797c1 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -1,32 +1,25 @@ -use diesel::{PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods, insert_into, update}; -use diesel::pg::Pg; -use diesel::result::*; +use diesel::{dsl::*, pg::Pg, result::Error, *}; use serde::{Deserialize, Serialize}; -use crate::{ - limit_and_offset, - MaybeOptional, - schema::post_report, - post::Post, - Reportable, -}; +use crate::{limit_and_offset, MaybeOptional, schema::post_report, post::Post, Reportable, naive_now}; table! { post_report_view (id) { - id -> Uuid, - time -> Timestamp, - reason -> Nullable, - resolved -> Bool, - user_id -> Int4, - post_id -> Int4, - post_name -> Varchar, - post_url -> Nullable, - post_body -> Nullable, - post_time -> Timestamp, - community_id -> Int4, - user_name -> Varchar, - creator_id -> Int4, - creator_name -> Varchar, + id -> Int4, + creator_id -> Int4, + post_id -> Int4, + post_name -> Varchar, + post_url -> Nullable, + post_body -> Nullable, + reason -> Text, + resolved -> Bool, + resolver_id -> Nullable, + published -> Timestamp, + updated -> Nullable, + community_id -> Int4, + creator_name -> Varchar, + post_creator_id -> Int4, + post_creator_name -> Varchar, } } @@ -34,30 +27,28 @@ table! { #[belongs_to(Post)] #[table_name = "post_report"] pub struct PostReport { - pub id: uuid::Uuid, - pub time: chrono::NaiveDateTime, - pub reason: Option, - pub resolved: bool, - pub user_id: i32, + pub id: i32, + pub creator_id: i32, pub post_id: i32, pub post_name: String, pub post_url: Option, pub post_body: Option, - pub post_time: chrono::NaiveDateTime, + 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 time: Option, - pub reason: Option, - pub resolved: Option, - pub user_id: i32, + pub creator_id: i32, pub post_id: i32, pub post_name: String, pub post_url: Option, pub post_body: Option, - pub post_time: chrono::NaiveDateTime, + pub reason: String, } impl Reportable for PostReport { @@ -68,37 +59,52 @@ impl Reportable for PostReport { .get_result::(conn) } - fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + 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)) + .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( -Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, Clone, +Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, )] #[table_name = "post_report_view"] pub struct PostReportView { - pub id: uuid::Uuid, - pub time: chrono::NaiveDateTime, - pub reason: Option, - pub resolved: bool, - pub user_id: i32, + pub id: i32, + pub creator_id: i32, pub post_id: i32, pub post_name: String, pub post_url: Option, pub post_body: Option, - pub post_time: chrono::NaiveDateTime, + pub reason: String, + pub resolved: bool, + pub resolver_id: Option, + pub published: chrono::NaiveDateTime, + pub updated: Option, pub community_id: i32, - pub user_name: String, - pub creator_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: &uuid::Uuid) -> Result { + pub fn read(conn: &PgConnection, report_id: i32) -> Result { use super::post_report::post_report_view::dsl::*; post_report_view .filter(id.eq(report_id)) @@ -167,7 +173,7 @@ impl<'a> PostReportQueryBuilder<'a> { let (limit, offset) = limit_and_offset(self.page, self.limit); query - .order_by(time.desc()) + .order_by(published.desc()) .limit(limit) .offset(offset) .load::(self.conn) diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index 71034b7ad..494c1ec87 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -83,14 +83,15 @@ table! { table! { comment_report (id) { - id -> Uuid, - time -> Timestamp, - reason -> Nullable, - resolved -> Bool, - user_id -> Int4, + id -> Int4, + creator_id -> Int4, comment_id -> Int4, comment_text -> Text, - comment_time -> Timestamp, + reason -> Text, + resolved -> Bool, + resolver_id -> Nullable, + published -> Timestamp, + updated -> Nullable, } } @@ -385,16 +386,17 @@ table! { table! { post_report (id) { - id -> Uuid, - time -> Timestamp, - reason -> Nullable, - resolved -> Bool, - user_id -> Int4, + id -> Int4, + creator_id -> Int4, post_id -> Int4, post_name -> Varchar, post_url -> Nullable, post_body -> Nullable, - post_time -> Timestamp, + reason -> Text, + resolved -> Bool, + resolver_id -> Nullable, + published -> Timestamp, + updated -> Nullable, } } @@ -516,7 +518,6 @@ joinable!(comment_like -> comment (comment_id)); joinable!(comment_like -> post (post_id)); joinable!(comment_like -> user_ (user_id)); joinable!(comment_report -> comment (comment_id)); -joinable!(comment_report -> user_ (user_id)); joinable!(comment_saved -> comment (comment_id)); joinable!(comment_saved -> user_ (user_id)); joinable!(community -> category (category_id)); @@ -547,7 +548,6 @@ joinable!(post_like -> user_ (user_id)); joinable!(post_read -> post (post_id)); joinable!(post_read -> user_ (user_id)); joinable!(post_report -> post (post_id)); -joinable!(post_report -> user_ (user_id)); joinable!(post_saved -> post (post_id)); joinable!(post_saved -> user_ (user_id)); joinable!(site -> user_ (creator_id)); @@ -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 22fad79e9..303181e9b 100644 --- a/lemmy_structs/Cargo.toml +++ b/lemmy_structs/Cargo.toml @@ -16,5 +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"]} -uuid = { version = "0.6.5", features = ["serde", "v4"] } \ No newline at end of file +serde_json = { version = "1.0", features = ["preserve_order"]} \ No newline at end of file diff --git a/lemmy_structs/src/report.rs b/lemmy_structs/src/report.rs index ed12e2614..842bfcaaa 100644 --- a/lemmy_structs/src/report.rs +++ b/lemmy_structs/src/report.rs @@ -1,57 +1,31 @@ -use lemmy_db::{ - comment_report::CommentReportView, - post_report::PostReportView, -}; +use lemmy_db::{comment_report::CommentReportView, post_report::PostReportView}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] -pub struct CreateCommentReport { - pub comment: i32, - pub reason: Option, +pub struct CreateReport { + pub report_type: String, + pub entity_id: i32, + pub reason: String, pub auth: String, } #[derive(Serialize, Deserialize, Clone)] -pub struct CommentReportResponse { - pub success: bool, -} - -#[derive(Serialize, Deserialize)] -pub struct CreatePostReport { - pub post: i32, - pub reason: Option, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct PostReportResponse { +pub struct CreateReportResponse { pub success: bool, } #[derive(Serialize, Deserialize, Debug)] -pub struct ListCommentReports { +pub struct ListReports { pub page: Option, pub limit: Option, pub community: i32, pub auth: String, } -#[derive(Serialize, Deserialize, Debug)] -pub struct ListCommentReportResponse { - pub reports: Vec, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ListPostReports { - pub page: Option, - pub limit: Option, - pub community: i32, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ListPostReportResponse { - pub reports: Vec, +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ListReportsResponse { + pub posts: Vec, + pub comments: Vec, } #[derive(Serialize, Deserialize, Debug)] @@ -60,7 +34,7 @@ pub struct GetReportCount { pub auth: String, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct GetReportCountResponse { pub community: i32, pub comment_reports: usize, @@ -68,25 +42,16 @@ pub struct GetReportCountResponse { } #[derive(Serialize, Deserialize, Debug)] -pub struct ResolveCommentReport { - pub report: uuid::Uuid, +pub struct ResolveReport { + pub report_type: String, + pub report_id: i32, + pub resolved: bool, pub auth: String, } -#[derive(Serialize, Deserialize, Debug)] -pub struct ResolveCommentReportResponse { - pub report: uuid::Uuid, - pub resolved: bool, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ResolvePostReport { - pub report: uuid::Uuid, - pub auth: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ResolvePostReportResponse { - pub report: uuid::Uuid, +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ResolveReportResponse { + pub report_type: String, + pub report_id: i32, pub resolved: bool, } diff --git a/lemmy_websocket/src/lib.rs b/lemmy_websocket/src/lib.rs index d5e056dcf..30c95d6ad 100644 --- a/lemmy_websocket/src/lib.rs +++ b/lemmy_websocket/src/lib.rs @@ -97,9 +97,6 @@ pub enum UserOperation { MarkCommentAsRead, SaveComment, CreateCommentLike, - CreateCommentReport, - ListCommentReports, - ResolveCommentReport, GetPosts, CreatePostLike, EditPost, @@ -108,9 +105,10 @@ pub enum UserOperation { LockPost, StickyPost, SavePost, - CreatePostReport, - ListPostReports, - ResolvePostReport, + CreateReport, + ResolveReport, + ListReports, + GetReportCount, EditCommunity, DeleteCommunity, RemoveCommunity, @@ -121,7 +119,6 @@ pub enum UserOperation { GetUserMentions, MarkUserMentionAsRead, GetModlog, - GetReportCount, BanFromCommunity, AddModToCommunity, CreateSite, diff --git a/migrations/2020-10-13-212240_create_report_tables/down.sql b/migrations/2020-10-13-212240_create_report_tables/down.sql index 436f6dd4d..e1c39faaf 100644 --- a/migrations/2020-10-13-212240_create_report_tables/down.sql +++ b/migrations/2020-10-13-212240_create_report_tables/down.sql @@ -2,4 +2,3 @@ drop view comment_report_view; drop view post_report_view; drop table comment_report; drop table post_report; -drop extension "uuid-ossp"; diff --git a/migrations/2020-10-13-212240_create_report_tables/up.sql b/migrations/2020-10-13-212240_create_report_tables/up.sql index 7f6f55427..98553625d 100644 --- a/migrations/2020-10-13-212240_create_report_tables/up.sql +++ b/migrations/2020-10-13-212240_create_report_tables/up.sql @@ -1,51 +1,51 @@ -create extension "uuid-ossp"; - create table comment_report ( - id uuid primary key default uuid_generate_v4(), - time timestamp not null default now(), - reason text, - resolved bool not null default false, - user_id int references user_ on update cascade on delete cascade not null, -- user reporting comment + id serial primary key, + creator_id int references user_ on update cascade on delete cascade not null, -- user reporting comment comment_id int references comment on update cascade on delete cascade not null, -- comment being reported comment_text text not null, - comment_time timestamp not null, - unique(comment_id, user_id) -- users should only be able to report a comment once + reason text not null, + resolved bool not null default false, + resolver_id int references user_ on update cascade on delete cascade not null, -- user resolving report + published timestamp not null default now(), + updated timestamp null, + unique(comment_id, creator_id) -- users should only be able to report a comment once ); create table post_report ( - id uuid primary key default uuid_generate_v4(), - time timestamp not null default now(), - reason text, - resolved bool not null default false, - user_id int references user_ on update cascade on delete cascade not null, -- user reporting post + id serial primary key, + creator_id int references user_ on update cascade on delete cascade not null, -- user reporting post post_id int references post on update cascade on delete cascade not null, -- post being reported - post_name varchar(100) not null, + post_name varchar(100) not null, post_url text, post_body text, - post_time timestamp not null, - unique(post_id, user_id) -- users should only be able to report a post once + reason text not null, + resolved bool not null default false, + resolver_id int references user_ on update cascade on delete cascade not null, -- user resolving report + published timestamp not null default now(), + updated timestamp null, + unique(post_id, creator_id) -- users should only be able to report a post once ); create or replace view comment_report_view as select cr.*, c.post_id, p.community_id, -f.name as user_name, -u.id as creator_id, -u.name as creator_name +f.name as creator_name, +u.id as comment_creator_id, +u.name as comment_creator_name from comment_report cr left join comment c on c.id = cr.comment_id left join post p on p.id = c.post_id left join user_ u on u.id = c.creator_id -left join user_ f on f.id = cr.user_id; +left join user_ f on f.id = cr.creator_id; create or replace view post_report_view as select pr.*, p.community_id, -f.name as user_name, -u.id as creator_id, -u.name as creator_name +f.name as creator_name, +u.id as post_creator_id, +u.name as post_creator_name from post_report pr left join post p on p.id = pr.post_id left join user_ u on u.id = p.creator_id -left join user_ f on f.id = pr.user_id; +left join user_ f on f.id = pr.creator_id; diff --git a/src/routes/api.rs b/src/routes/api.rs index 9cbca6527..6563b2305 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -58,9 +58,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route("/ban_user", web::post().to(route_post::)) .route("/mod", web::post().to(route_post::)) .route("/join", web::post().to(route_post::)) - .route("/comment_reports",web::get().to(route_get::)) - .route("/post_reports", web::get().to(route_get::)) - .route("/reports", web::get().to(route_get::)), ) // Post .service( @@ -83,12 +80,10 @@ 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::put().to(route_post::)) - .route("/resolve_report",web::post().to(route_post::)), ) // Comment .service( - web::scope("/comment") + web::scope("/comment") .wrap(rate_limit.message()) .route("", web::post().to(route_post::)) .route("", web::put().to(route_post::)) @@ -101,8 +96,6 @@ 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::put().to(route_post::)) - .route("/resolve_report",web::post().to(route_post::)), ) // Private Message .service( @@ -177,6 +170,15 @@ 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::)) ), ); }