From 6d43202efb3101f162cc2bf8663f8a6dca160bdf Mon Sep 17 00:00:00 2001 From: eiknat Date: Tue, 13 Oct 2020 19:32:35 -0400 Subject: [PATCH 01/25] reports: initial reports api commit --- Cargo.lock | 3 + lemmy_api/src/lib.rs | 24 +- lemmy_api/src/report.rs | 394 ++++++++++++++++++ lemmy_db/Cargo.toml | 3 +- lemmy_db/src/comment_report.rs | 186 +++++++++ lemmy_db/src/lib.rs | 11 + lemmy_db/src/post_report.rs | 190 +++++++++ lemmy_db/src/schema.rs | 34 ++ lemmy_structs/Cargo.toml | 1 + lemmy_structs/src/lib.rs | 1 + lemmy_structs/src/report.rs | 92 ++++ lemmy_websocket/src/lib.rs | 7 + .../down.sql | 5 + .../up.sql | 51 +++ src/routes/api.rs | 15 +- 15 files changed, 1011 insertions(+), 6 deletions(-) create mode 100644 lemmy_api/src/report.rs create mode 100644 lemmy_db/src/comment_report.rs create mode 100644 lemmy_db/src/post_report.rs create mode 100644 lemmy_structs/src/report.rs create mode 100644 migrations/2020-10-13-212240_create_report_tables/down.sql create mode 100644 migrations/2020-10-13-212240_create_report_tables/up.sql diff --git a/Cargo.lock b/Cargo.lock index c5f984783..8abe539bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,6 +1054,7 @@ dependencies = [ "pq-sys", "r2d2", "serde_json", + "uuid 0.6.5", ] [[package]] @@ -1833,6 +1834,7 @@ dependencies = [ "strum", "strum_macros", "url", + "uuid 0.6.5", ] [[package]] @@ -1898,6 +1900,7 @@ 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 0f0367cf1..6b35f6750 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -8,7 +8,7 @@ use lemmy_db::{ Crud, DbPool, }; -use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*}; +use lemmy_structs::{blocking, comment::*, community::*, post::*, report::*, site::*, user::*}; use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError}; use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; use serde::Deserialize; @@ -19,6 +19,7 @@ pub mod claims; pub mod comment; pub mod community; pub mod post; +pub mod report; pub mod site; pub mod user; pub mod version; @@ -266,6 +267,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 => { @@ -292,6 +302,18 @@ 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 + } + UserOperation::GetReportCount => { + do_websocket_operation::(context, id, op, data).await + } } } diff --git a/lemmy_api/src/report.rs b/lemmy_api/src/report.rs new file mode 100644 index 000000000..f5557500e --- /dev/null +++ b/lemmy_api/src/report.rs @@ -0,0 +1,394 @@ +use actix_web::web::Data; + +use lemmy_db::{ + comment_report::*, + comment_view::*, + community_view::*, + post_report::*, + post_view::*, + Reportable, + user_view::UserView, +}; +use lemmy_structs::{blocking, report::*}; +use lemmy_utils::{APIError, ConnectionId, LemmyError}; +use lemmy_websocket::LemmyContext; + +use crate::{check_community_ban, get_user_from_jwt, Perform}; + +const MAX_REPORT_LEN: usize = 1000; + +#[async_trait::async_trait(?Send)] +impl Perform for CreateCommentReport { + type Response = CommentReportResponse; + + 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; + 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 post information from the database + let post_id = data.post; + let post = blocking(context.pool(), move |conn| PostView::read(&conn, post_id, None)).await??; + + // Check for community ban + check_community_ban(user.id, post.community_id, context.pool()).await?; + + // 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??; + + Ok(PostReportResponse { success: 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 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()); + } + + 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 response = GetReportCountResponse { + community: community_id, + comment_reports, + post_reports, + }; + + Ok(response) + } +} + +#[async_trait::async_trait(?Send)] +impl Perform for ListCommentReports { + type Response = ListCommentReportResponse; + + 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 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()); + } + + let page = data.page; + let limit = data.limit; + let reports = blocking(context.pool(), move |conn| { + CommentReportQueryBuilder::create(conn) + .community_id(community_id) + .page(page) + .limit(limit) + .list() + }) + .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| { + PostReportQueryBuilder::create(conn) + .community_id(community_id) + .page(page) + .limit(limit) + .list() + }) + .await??; + + Ok(ListPostReportResponse { reports }) + } +} + +#[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?; + + // 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()); + } + + blocking(context.pool(), move |conn| { + CommentReport::resolve(conn, &report_id.clone()) + }) + .await??; + + Ok(ResolveCommentReportResponse { + report: report_id, + resolved: true, + }) + } +} + +#[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?; + + // Fetch the report view + let report_id = data.report; + let report = blocking(context.pool(), move |conn| PostReportView::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()); + } + + blocking(context.pool(), move |conn| { + PostReport::resolve(conn, &report_id.clone()) + }) + .await??; + + Ok(ResolvePostReportResponse { + report: report_id, + resolved: true, + }) + } +} diff --git a/lemmy_db/Cargo.toml b/lemmy_db/Cargo.toml index 904b16937..2a358a3e1 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"] } +diesel = { version = "1.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json", "uuid"] } chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"]} @@ -21,3 +21,4 @@ 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 new file mode 100644 index 000000000..74d05e8c6 --- /dev/null +++ b/lemmy_db/src/comment_report.rs @@ -0,0 +1,186 @@ +use diesel::{PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods, insert_into, update}; +use diesel::pg::Pg; +use diesel::result::*; +use serde::{Deserialize, Serialize}; + +use crate::{ + limit_and_offset, + MaybeOptional, + schema::comment_report, + comment::Comment, + Reportable, +}; + +table! { + comment_report_view (id) { + id -> Uuid, + time -> Timestamp, + reason -> Nullable, + resolved -> Bool, + user_id -> Int4, + comment_id -> Int4, + comment_text -> Text, + comment_time -> Timestamp, + post_id -> Int4, + community_id -> Int4, + user_name -> Varchar, + creator_id -> Int4, + creator_name -> Varchar, + } +} + +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[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 comment_id: i32, + pub comment_text: String, + pub comment_time: chrono::NaiveDateTime, +} + +#[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 comment_id: i32, + pub comment_text: String, + pub comment_time: chrono::NaiveDateTime, +} + +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) + } + + fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + use crate::schema::comment_report::dsl::*; + update(comment_report.find(report_id)) + .set(resolved.eq(true)) + .execute(conn) + } +} + +#[derive( + Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, 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 comment_id: i32, + pub comment_text: String, + pub comment_time: chrono::NaiveDateTime, + pub post_id: i32, + pub community_id: i32, + pub user_name: String, + pub creator_id: i32, + pub creator_name: String, +} + +pub struct CommentReportQueryBuilder<'a> { + conn: &'a PgConnection, + query: comment_report_view::BoxedQuery<'a, Pg>, + for_community_id: Option, + page: Option, + limit: Option, + resolved: Option, +} + +impl CommentReportView { + pub fn read(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + use super::comment_report::comment_report_view::dsl::*; + comment_report_view + .filter(id.eq(report_id)) + .first::(conn) + } +} + +impl<'a> CommentReportQueryBuilder<'a> { + pub fn create(conn: &'a PgConnection) -> Self { + use super::comment_report::comment_report_view::dsl::*; + + let query = comment_report_view.into_boxed(); + + CommentReportQueryBuilder { + 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 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 list(self) -> Result, Error> { + 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)); + } + + let (limit, offset) = limit_and_offset(self.page, self.limit); + + query + .order_by(time.desc()) + .limit(limit) + .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/lib.rs b/lemmy_db/src/lib.rs index c17bc0254..cf0e68ade 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -14,6 +14,7 @@ use std::{env, env::VarError}; pub mod activity; pub mod category; pub mod comment; +pub mod comment_report; pub mod comment_view; pub mod community; pub mod community_view; @@ -21,6 +22,7 @@ pub mod moderator; pub mod moderator_views; pub mod password_reset_request; pub mod post; +pub mod post_report; pub mod post_view; pub mod private_message; pub mod private_message_view; @@ -109,6 +111,15 @@ pub trait Readable { Self: Sized; } +pub trait Reportable { + fn report(conn: &PgConnection, form: &T) -> Result + where + Self: Sized; + fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result + where + Self: Sized; +} + pub trait MaybeOptional { fn get_optional(self) -> Option; } diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs new file mode 100644 index 000000000..4cb5e5701 --- /dev/null +++ b/lemmy_db/src/post_report.rs @@ -0,0 +1,190 @@ +use diesel::{PgConnection, QueryDsl, RunQueryDsl, ExpressionMethods, insert_into, update}; +use diesel::pg::Pg; +use diesel::result::*; +use serde::{Deserialize, Serialize}; + +use crate::{ + limit_and_offset, + MaybeOptional, + schema::post_report, + post::Post, + Reportable, +}; + +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, + } +} + +#[derive(Identifiable, Queryable, Associations, PartialEq, Serialize, Deserialize, Debug)] +#[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 post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub post_time: chrono::NaiveDateTime, +} + +#[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 post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub post_time: chrono::NaiveDateTime, +} + +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: &uuid::Uuid) -> Result { + use crate::schema::post_report::dsl::*; + update(post_report.find(report_id)) + .set(resolved.eq(true)) + .execute(conn) + } +} + +#[derive( +Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, QueryableByName, 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 post_id: i32, + pub post_name: String, + pub post_url: Option, + pub post_body: Option, + pub post_time: chrono::NaiveDateTime, + pub community_id: i32, + pub user_name: String, + pub creator_id: i32, + pub creator_name: String, +} + +impl PostReportView { + pub fn read(conn: &PgConnection, report_id: &uuid::Uuid) -> Result { + use super::post_report::post_report_view::dsl::*; + post_report_view + .filter(id.eq(report_id)) + .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, +} + +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 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 list(self) -> Result, Error> { + 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)); + } + + let (limit, offset) = limit_and_offset(self.page, self.limit); + + query + .order_by(time.desc()) + .limit(limit) + .offset(offset) + .load::(self.conn) + } + + 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)); + } + + query.execute(self.conn) + } +} diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index 65838b1a8..71034b7ad 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -81,6 +81,19 @@ table! { } } +table! { + comment_report (id) { + id -> Uuid, + time -> Timestamp, + reason -> Nullable, + resolved -> Bool, + user_id -> Int4, + comment_id -> Int4, + comment_text -> Text, + comment_time -> Timestamp, + } +} + table! { comment_saved (id) { id -> Int4, @@ -370,6 +383,21 @@ table! { } } +table! { + post_report (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, + } +} + table! { post_saved (id) { id -> Int4, @@ -487,6 +515,8 @@ joinable!(comment -> user_ (creator_id)); 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)); @@ -516,6 +546,8 @@ joinable!(post_like -> post (post_id)); 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)); @@ -529,6 +561,7 @@ allow_tables_to_appear_in_same_query!( comment, comment_aggregates_fast, comment_like, + comment_report, comment_saved, community, community_aggregates_fast, @@ -549,6 +582,7 @@ allow_tables_to_appear_in_same_query!( post_aggregates_fast, post_like, post_read, + post_report, post_saved, private_message, site, diff --git a/lemmy_structs/Cargo.toml b/lemmy_structs/Cargo.toml index 8cf522c39..22fad79e9 100644 --- a/lemmy_structs/Cargo.toml +++ b/lemmy_structs/Cargo.toml @@ -17,3 +17,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 diff --git a/lemmy_structs/src/lib.rs b/lemmy_structs/src/lib.rs index 5d2e42733..1b7ccd213 100644 --- a/lemmy_structs/src/lib.rs +++ b/lemmy_structs/src/lib.rs @@ -1,6 +1,7 @@ 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/report.rs b/lemmy_structs/src/report.rs new file mode 100644 index 000000000..ed12e2614 --- /dev/null +++ b/lemmy_structs/src/report.rs @@ -0,0 +1,92 @@ +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 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 success: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ListCommentReports { + 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, Debug)] +pub struct GetReportCount { + pub community: i32, + pub auth: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetReportCountResponse { + pub community: i32, + pub comment_reports: usize, + pub post_reports: usize, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ResolveCommentReport { + pub report: uuid::Uuid, + 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, + pub resolved: bool, +} diff --git a/lemmy_websocket/src/lib.rs b/lemmy_websocket/src/lib.rs index 26b00a061..d5e056dcf 100644 --- a/lemmy_websocket/src/lib.rs +++ b/lemmy_websocket/src/lib.rs @@ -97,6 +97,9 @@ pub enum UserOperation { MarkCommentAsRead, SaveComment, CreateCommentLike, + CreateCommentReport, + ListCommentReports, + ResolveCommentReport, GetPosts, CreatePostLike, EditPost, @@ -105,6 +108,9 @@ pub enum UserOperation { LockPost, StickyPost, SavePost, + CreatePostReport, + ListPostReports, + ResolvePostReport, EditCommunity, DeleteCommunity, RemoveCommunity, @@ -115,6 +121,7 @@ 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 new file mode 100644 index 000000000..436f6dd4d --- /dev/null +++ b/migrations/2020-10-13-212240_create_report_tables/down.sql @@ -0,0 +1,5 @@ +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 new file mode 100644 index 000000000..7f6f55427 --- /dev/null +++ b/migrations/2020-10-13-212240_create_report_tables/up.sql @@ -0,0 +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 + 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 +); + +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 + post_id int references post on update cascade on delete cascade not null, -- post being reported + 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 +); + +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 +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; + +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 +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; diff --git a/src/routes/api.rs b/src/routes/api.rs index 7a8ddbf1e..9cbca6527 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::*, site::*, user::*}; +use lemmy_structs::{comment::*, community::*, post::*, report::*, site::*, user::*}; use lemmy_websocket::LemmyContext; use serde::Deserialize; @@ -57,7 +57,10 @@ 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::)) + .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( @@ -79,7 +82,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .route("/list", web::get().to(route_get::)) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) - .route("/join", web::post().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( @@ -95,7 +100,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("/list", web::get().to(route_get::)) + .route("/report", web::put().to(route_post::)) + .route("/resolve_report",web::post().to(route_post::)), ) // Private Message .service( From d6b1c8df2f552d69c3c7bf62bea95a289d3fb5f3 Mon Sep 17 00:00:00 2001 From: eiknat Date: Tue, 20 Oct 2020 20:31:01 -0400 Subject: [PATCH 02/25] 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::)) ), ); } From e8e089034107c51c200e61bdf45796e5c22f2d6d Mon Sep 17 00:00:00 2001 From: eiknat Date: Tue, 20 Oct 2020 21:37:37 -0400 Subject: [PATCH 03/25] db: fix a few comments i missed --- lemmy_db/src/comment_report.rs | 4 ++-- lemmy_db/src/post_report.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index 0725256c2..2002f66e2 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -110,7 +110,7 @@ impl CommentReportView { 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)) + .find(report_id) .first::(conn) } } @@ -167,7 +167,7 @@ impl<'a> CommentReportQueryBuilder<'a> { let (limit, offset) = limit_and_offset(self.page, self.limit); query - .order_by(published.desc()) + .order_by(published.asc()) .limit(limit) .offset(offset) .load::(self.conn) diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs index d461797c1..3a2fc1565 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -107,7 +107,7 @@ impl PostReportView { 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)) + .find(report_id) .first::(conn) } } @@ -173,7 +173,7 @@ impl<'a> PostReportQueryBuilder<'a> { let (limit, offset) = limit_and_offset(self.page, self.limit); query - .order_by(published.desc()) + .order_by(published.asc()) .limit(limit) .offset(offset) .load::(self.conn) From 2cd2a4df4508865164ced5d084ebddb5b4f62835 Mon Sep 17 00:00:00 2001 From: eiknat Date: Sat, 24 Oct 2020 22:59:13 -0400 Subject: [PATCH 04/25] reports: split post/comment out again, add some other fixes --- lemmy_api/src/comment.rs | 155 ++++++++++++++++- lemmy_api/src/lib.rs | 51 ++++-- lemmy_api/src/post.rs | 156 ++++++++++++++++- lemmy_api/src/report.rs | 300 --------------------------------- lemmy_api/src/user.rs | 54 ++++++ lemmy_db/src/comment_report.rs | 65 +++---- lemmy_db/src/community.rs | 8 + lemmy_db/src/lib.rs | 6 - lemmy_db/src/post_report.rs | 259 ++++++++++++++-------------- lemmy_db/src/schema.rs | 68 ++++---- lemmy_structs/Cargo.toml | 2 +- lemmy_structs/src/comment.rs | 43 ++++- lemmy_structs/src/lib.rs | 1 - lemmy_structs/src/post.rs | 39 +++++ lemmy_structs/src/report.rs | 57 ------- lemmy_structs/src/user.rs | 13 ++ lemmy_websocket/src/lib.rs | 9 +- src/routes/api.rs | 25 +-- 18 files changed, 710 insertions(+), 601 deletions(-) delete mode 100644 lemmy_api/src/report.rs delete mode 100644 lemmy_structs/src/report.rs 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::CreateCommentReport => { + do_websocket_operation::(context, id, op, data).await } - UserOperation::ListReports => { - do_websocket_operation::(context, id, op, data).await + UserOperation::ListCommentReports => { + 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 + 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 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 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 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::*; + pub fn create(conn: &'a PgConnection) -> Self { + use super::post_report::post_report_view::dsl::*; - let query = post_report_view.into_boxed(); + let query = post_report_view.into_boxed(); - PostReportQueryBuilder { - conn, - query, - for_community_id: None, - page: None, - limit: None, - resolved: Some(false), - } + PostReportQueryBuilder { + conn, + query, + for_community_ids: None, + page: None, + limit: None, + resolved: Some(false), + } + } + + pub fn community_ids>>(mut self, community_ids: T) -> Self { + self.for_community_ids = community_ids.get_optional(); + self + } + + 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 list(self) -> Result, Error> { + use super::post_report::post_report_view::dsl::*; + + let mut query = self.query; + + if let Some(comm_ids) = self.for_community_ids { + query = query.filter(community_id.eq_any(comm_ids)); } - pub fn community_id>(mut self, community_id: T) -> Self { - self.for_community_id = community_id.get_optional(); - self + if let Some(resolved_flag) = self.resolved { + query = query.filter(resolved.eq(resolved_flag)); } - pub fn page>(mut self, page: T) -> Self { - self.page = page.get_optional(); - self - } + let (limit, offset) = limit_and_offset(self.page, self.limit); - 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 list(self) -> Result, Error> { - 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)); - } - - let (limit, offset) = limit_and_offset(self.page, self.limit); - - query - .order_by(published.asc()) - .limit(limit) - .offset(offset) - .load::(self.conn) - } - - 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)); - } - - 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::)) ), ); } From 070efe72af4ba400d9e4b3d2194af599c2d45e5f Mon Sep 17 00:00:00 2001 From: eiknat Date: Sat, 24 Oct 2020 23:24:50 -0400 Subject: [PATCH 05/25] add current context for reports --- lemmy_db/src/comment_report.rs | 2 ++ lemmy_db/src/post_report.rs | 6 ++++++ migrations/2020-10-13-212240_create_report_tables/up.sql | 8 ++++++-- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index b9692aea1..a2b4e1d30 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -15,6 +15,7 @@ table! { published -> Timestamp, updated -> Nullable, post_id -> Int4, + current_comment_text -> Text, community_id -> Int4, creator_name -> Varchar, comment_creator_id -> Int4, @@ -91,6 +92,7 @@ pub struct CommentReportView { pub published: chrono::NaiveDateTime, pub updated: Option, pub post_id: i32, + pub current_comment_text: String, pub community_id: i32, pub creator_name: String, pub comment_creator_id: i32, diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs index 1741edf84..234b0e4db 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -16,6 +16,9 @@ table! { resolver_id -> Nullable, published -> Timestamp, updated -> Nullable, + current_post_name -> Varchar, + current_post_url -> Nullable, + current_post_body -> Nullable, community_id -> Int4, creator_name -> Varchar, post_creator_id -> Int4, @@ -97,6 +100,9 @@ pub struct PostReportView { pub resolver_id: Option, pub published: chrono::NaiveDateTime, pub updated: Option, + pub current_post_name: String, + pub current_post_url: Option, + pub current_post_body: Option, pub community_id: i32, pub creator_name: String, pub post_creator_id: i32, 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 98553625d..840d3d99a 100644 --- a/migrations/2020-10-13-212240_create_report_tables/up.sql +++ b/migrations/2020-10-13-212240_create_report_tables/up.sql @@ -5,7 +5,7 @@ create table comment_report ( comment_text text not null, 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 + resolver_id int references user_ on update cascade on delete cascade, -- 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 @@ -20,7 +20,7 @@ create table post_report ( post_body text, 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 + resolver_id int references user_ on update cascade on delete cascade, -- 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 @@ -29,6 +29,7 @@ create table post_report ( create or replace view comment_report_view as select cr.*, c.post_id, +c.content as current_comment_text, p.community_id, f.name as creator_name, u.id as comment_creator_id, @@ -41,6 +42,9 @@ left join user_ f on f.id = cr.creator_id; create or replace view post_report_view as select pr.*, +p.name as current_post_name, +p.url as current_post_url, +p.body as current_post_body, p.community_id, f.name as creator_name, u.id as post_creator_id, From 30d784c27c1cc7517a5c8fd3ed537475aa731ffa Mon Sep 17 00:00:00 2001 From: eiknat Date: Mon, 26 Oct 2020 20:25:18 -0400 Subject: [PATCH 06/25] add mod room websocket impl --- lemmy_api/src/comment.rs | 42 +++++++++++++------------ lemmy_api/src/community.rs | 24 ++++++++++++++ lemmy_api/src/lib.rs | 3 ++ lemmy_api/src/post.rs | 49 +++++++++++++++++------------ lemmy_db/src/comment_report.rs | 2 +- lemmy_structs/src/community.rs | 10 ++++++ lemmy_websocket/src/chat_server.rs | 50 ++++++++++++++++++++++++++++++ lemmy_websocket/src/handlers.rs | 21 +++++++++++++ lemmy_websocket/src/lib.rs | 1 + lemmy_websocket/src/messages.rs | 16 ++++++++++ src/routes/api.rs | 3 +- 11 files changed, 180 insertions(+), 41 deletions(-) diff --git a/lemmy_api/src/comment.rs b/lemmy_api/src/comment.rs index e9ed0a445..2b0ee0544 100644 --- a/lemmy_api/src/comment.rs +++ b/lemmy_api/src/comment.rs @@ -34,6 +34,7 @@ use lemmy_utils::{ }; use lemmy_websocket::{messages::{SendComment, SendUserRoomMessage}, LemmyContext, UserOperation}; use std::str::FromStr; +use lemmy_websocket::messages::SendModRoomMessage; #[async_trait::async_trait(?Send)] impl Perform for CreateComment { @@ -693,7 +694,7 @@ impl Perform for CreateCommentReport { async fn perform( &self, context: &Data, - _websocket_id: Option, + websocket_id: Option, ) -> Result { let data: &CreateCommentReport = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -722,24 +723,28 @@ impl Perform for CreateCommentReport { reason: data.reason.to_owned(), }; - let _report = match blocking(context.pool(), move |conn| { + 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, - // }); + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::CreateCommentReport, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); + + context.chat_server().do_send(SendModRoomMessage { + op: UserOperation::CreateCommentReport, + response: report, + community_id: comment.community_id, + websocket_id, + }); Ok(res) } @@ -752,7 +757,7 @@ impl Perform for ResolveCommentReport { async fn perform( &self, context: &Data, - _websocket_id: Option, + websocket_id: Option, ) -> Result { let data: &ResolveCommentReport = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -784,13 +789,12 @@ impl Perform for ResolveCommentReport { 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, - // }); + context.chat_server().do_send(SendModRoomMessage { + op: UserOperation::ResolveCommentReport, + response: res.clone(), + community_id: report.community_id, + websocket_id, + }); Ok(res) } diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index a9e130b9b..ae19c5f8f 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -41,6 +41,7 @@ use lemmy_websocket::{ UserOperation, }; use std::str::FromStr; +use lemmy_websocket::messages::JoinModRoom; #[async_trait::async_trait(?Send)] impl Perform for GetCommunity { @@ -883,3 +884,26 @@ impl Perform for CommunityJoin { Ok(CommunityJoinResponse { joined: true }) } } + +// is this the right place for this? +#[async_trait::async_trait(?Send)] +impl Perform for ModJoin { + type Response = ModJoinResponse; + + async fn perform( + &self, + context: &Data, + websocket_id: Option, + ) -> Result { + let data: &ModJoin = &self; + + if let Some(ws_id) = websocket_id { + context.chat_server().do_send(JoinModRoom { + community_id: data.community_id, + id: ws_id, + }); + } + + Ok(ModJoinResponse { joined: true }) + } +} diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs index 38cf80435..96f2b0612 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -195,6 +195,9 @@ pub async fn match_websocket_operation( UserOperation::CommunityJoin => { do_websocket_operation::(context, id, op, data).await } + UserOperation::ModJoin => { + do_websocket_operation::(context, id, op, data).await + } UserOperation::SaveUserSettings => { do_websocket_operation::(context, id, op, data).await } diff --git a/lemmy_api/src/post.rs b/lemmy_api/src/post.rs index 0bf76b4d4..0abb879cf 100644 --- a/lemmy_api/src/post.rs +++ b/lemmy_api/src/post.rs @@ -35,7 +35,13 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{ - messages::{GetPostUsersOnline, JoinPostRoom, SendPost, SendUserRoomMessage}, + messages::{ + GetPostUsersOnline, + JoinPostRoom, + SendModRoomMessage, + SendPost, + SendUserRoomMessage + }, LemmyContext, UserOperation, }; @@ -752,7 +758,7 @@ impl Perform for CreatePostReport { async fn perform( &self, context: &Data, - _websocket_id: Option, + websocket_id: Option, ) -> Result { let data: &CreatePostReport = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -783,24 +789,28 @@ impl Perform for CreatePostReport { reason: data.reason.to_owned(), }; - let _report = match blocking(context.pool(), move |conn| { + 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, - // }); + context.chat_server().do_send(SendUserRoomMessage { + op: UserOperation::CreatePostReport, + response: res.clone(), + recipient_id: user.id, + websocket_id, + }); + + context.chat_server().do_send(SendModRoomMessage { + op: UserOperation::CreatePostReport, + response: report, + community_id: post.community_id, + websocket_id, + }); Ok(res) } @@ -813,7 +823,7 @@ impl Perform for ResolvePostReport { async fn perform( &self, context: &Data, - _websocket_id: Option, + websocket_id: Option, ) -> Result { let data: &ResolvePostReport = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; @@ -844,13 +854,12 @@ impl Perform for ResolvePostReport { 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, - // }); + context.chat_server().do_send(SendModRoomMessage { + op: UserOperation::ResolvePostReport, + response: res.clone(), + community_id: report.community_id, + websocket_id, + }); Ok(res) } diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index a2b4e1d30..a5ac701c1 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -23,7 +23,7 @@ table! { } } -#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] +#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Serialize)] #[belongs_to(Comment)] #[table_name = "comment_report"] pub struct CommentReport { diff --git a/lemmy_structs/src/community.rs b/lemmy_structs/src/community.rs index 6c543eac2..3535c05a9 100644 --- a/lemmy_structs/src/community.rs +++ b/lemmy_structs/src/community.rs @@ -139,3 +139,13 @@ pub struct CommunityJoin { pub struct CommunityJoinResponse { pub joined: bool, } + +#[derive(Deserialize, Debug)] +pub struct ModJoin { + pub community_id: i32, +} + +#[derive(Serialize, Clone)] +pub struct ModJoinResponse { + pub joined: bool, +} diff --git a/lemmy_websocket/src/chat_server.rs b/lemmy_websocket/src/chat_server.rs index 8346a32f6..0a524e3db 100644 --- a/lemmy_websocket/src/chat_server.rs +++ b/lemmy_websocket/src/chat_server.rs @@ -47,6 +47,8 @@ pub struct ChatServer { /// A map from community to set of connectionIDs pub community_rooms: HashMap>, + pub mod_rooms: HashMap>, + /// A map from user id to its connection ID for joined users. Remember a user can have multiple /// sessions (IE clients) pub(super) user_rooms: HashMap>, @@ -90,6 +92,7 @@ impl ChatServer { sessions: HashMap::new(), post_rooms: HashMap::new(), community_rooms: HashMap::new(), + mod_rooms: HashMap::new(), user_rooms: HashMap::new(), rng: rand::thread_rng(), pool, @@ -130,6 +133,29 @@ impl ChatServer { Ok(()) } + pub fn join_mod_room( + &mut self, + community_id: CommunityId, + id: ConnectionId, + ) -> Result<(), LemmyError> { + // remove session from all rooms + for sessions in self.mod_rooms.values_mut() { + sessions.remove(&id); + } + + // If the room doesn't exist yet + if self.mod_rooms.get_mut(&community_id).is_none() { + self.mod_rooms.insert(community_id, HashSet::new()); + } + + self + .mod_rooms + .get_mut(&community_id) + .context(location_info!())? + .insert(id); + Ok(()) + } + pub fn join_post_room(&mut self, post_id: PostId, id: ConnectionId) -> Result<(), LemmyError> { // remove session from all rooms for sessions in self.post_rooms.values_mut() { @@ -227,6 +253,30 @@ impl ChatServer { Ok(()) } + pub fn send_mod_room_message( + &self, + op: &UserOperation, + response: &Response, + community_id: CommunityId, + websocket_id: Option, + ) -> Result<(), LemmyError> + where + Response: Serialize, + { + let res_str = &serialize_websocket_message(op, response)?; + if let Some(sessions) = self.mod_rooms.get(&community_id) { + for id in sessions { + if let Some(my_id) = websocket_id { + if *id == my_id { + continue; + } + } + self.sendit(res_str, *id); + } + } + Ok(()) + } + pub fn send_all_message( &self, op: &UserOperation, diff --git a/lemmy_websocket/src/handlers.rs b/lemmy_websocket/src/handlers.rs index 258098d62..0b5703ab1 100644 --- a/lemmy_websocket/src/handlers.rs +++ b/lemmy_websocket/src/handlers.rs @@ -120,6 +120,19 @@ where } } +impl Handler> for ChatServer + where + Response: Serialize, +{ + type Result = (); + + fn handle(&mut self, msg: SendModRoomMessage, _: &mut Context) { + self + .send_mod_room_message(&msg.op, &msg.response, msg.community_id, msg.websocket_id) + .ok(); + } +} + impl Handler for ChatServer { type Result = (); @@ -154,6 +167,14 @@ impl Handler for ChatServer { } } +impl Handler for ChatServer { + type Result = (); + + fn handle(&mut self, msg: JoinModRoom, _: &mut Context) { + self.join_mod_room(msg.community_id, msg.id).ok(); + } +} + impl Handler for ChatServer { type Result = (); diff --git a/lemmy_websocket/src/lib.rs b/lemmy_websocket/src/lib.rs index 7673510cd..d789efdd1 100644 --- a/lemmy_websocket/src/lib.rs +++ b/lemmy_websocket/src/lib.rs @@ -148,4 +148,5 @@ pub enum UserOperation { SaveSiteConfig, PostJoin, CommunityJoin, + ModJoin, } diff --git a/lemmy_websocket/src/messages.rs b/lemmy_websocket/src/messages.rs index d9f8320a8..c678a96ef 100644 --- a/lemmy_websocket/src/messages.rs +++ b/lemmy_websocket/src/messages.rs @@ -63,6 +63,15 @@ pub struct SendCommunityRoomMessage { pub websocket_id: Option, } +#[derive(Message)] +#[rtype(result = "()")] +pub struct SendModRoomMessage { + pub op: UserOperation, + pub response: Response, + pub community_id: CommunityId, + pub websocket_id: Option, +} + #[derive(Message)] #[rtype(result = "()")] pub struct SendPost { @@ -93,6 +102,13 @@ pub struct JoinCommunityRoom { pub id: ConnectionId, } +#[derive(Message)] +#[rtype(result = "()")] +pub struct JoinModRoom { + pub community_id: CommunityId, + pub id: ConnectionId, +} + #[derive(Message)] #[rtype(result = "()")] pub struct JoinPostRoom { diff --git a/src/routes/api.rs b/src/routes/api.rs index e6d6b31a9..7b95b4ee4 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -57,7 +57,8 @@ 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::)) + .route("/mod/join", web::post().to(route_post::)), ) // Post .service( From 438414a64b29bf271fccdb757ab9bc19acac4bc1 Mon Sep 17 00:00:00 2001 From: eiknat Date: Mon, 2 Nov 2020 22:06:20 -0500 Subject: [PATCH 07/25] add more details to the report views --- lemmy_api/src/comment.rs | 2 +- lemmy_api/src/community.rs | 1 - lemmy_api/src/post.rs | 2 +- lemmy_db/src/comment_report.rs | 33 ++++++++++++++-- lemmy_db/src/lib.rs | 4 +- lemmy_db/src/post_report.rs | 33 ++++++++++++++-- .../up.sql | 39 ++++++++++++++++++- 7 files changed, 101 insertions(+), 13 deletions(-) diff --git a/lemmy_api/src/comment.rs b/lemmy_api/src/comment.rs index 2b0ee0544..083cb8410 100644 --- a/lemmy_api/src/comment.rs +++ b/lemmy_api/src/comment.rs @@ -775,7 +775,7 @@ impl Perform for ResolveCommentReport { if resolved { CommentReport::resolve(conn, report_id.clone(), user_id) } else { - CommentReport::unresolve(conn, report_id.clone()) + CommentReport::unresolve(conn, report_id.clone(), user_id) } }; diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index ae19c5f8f..35ed4aa9c 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -885,7 +885,6 @@ impl Perform for CommunityJoin { } } -// is this the right place for this? #[async_trait::async_trait(?Send)] impl Perform for ModJoin { type Response = ModJoinResponse; diff --git a/lemmy_api/src/post.rs b/lemmy_api/src/post.rs index 0abb879cf..b703c2ef8 100644 --- a/lemmy_api/src/post.rs +++ b/lemmy_api/src/post.rs @@ -841,7 +841,7 @@ impl Perform for ResolvePostReport { if resolved { PostReport::resolve(conn, report_id.clone(), user_id) } else { - PostReport::unresolve(conn, report_id.clone()) + PostReport::unresolve(conn, report_id.clone(), user_id) } }; diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index a5ac701c1..83f432407 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -17,9 +17,22 @@ table! { post_id -> Int4, current_comment_text -> Text, community_id -> Int4, + creator_actor_id -> Text, creator_name -> Varchar, + creator_preferred_username -> Nullable, + creator_avatar -> Nullable, + creator_local -> Bool, comment_creator_id -> Int4, + comment_creator_actor_id -> Text, comment_creator_name -> Varchar, + comment_creator_preferred_username -> Nullable, + comment_creator_avatar -> Nullable, + comment_creator_local -> Bool, + resolver_actor_id -> Nullable, + resolver_name -> Nullable, + resolver_preferred_username -> Nullable, + resolver_avatar -> Nullable, + resolver_local -> Nullable, } } @@ -55,22 +68,23 @@ impl Reportable for CommentReport { .get_result::(conn) } - fn resolve(conn: &PgConnection, report_id: i32, by_user_id: i32) -> Result { + fn resolve(conn: &PgConnection, report_id: i32, by_resolver_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), + resolver_id.eq(by_resolver_id), updated.eq(naive_now()), )) .execute(conn) } - fn unresolve(conn: &PgConnection, report_id: i32) -> Result { + fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::comment_report::dsl::*; update(comment_report.find(report_id)) .set(( resolved.eq(false), + resolver_id.eq(by_resolver_id), updated.eq(naive_now()), )) .execute(conn) @@ -94,9 +108,22 @@ pub struct CommentReportView { pub post_id: i32, pub current_comment_text: String, pub community_id: i32, + pub creator_actor_id: String, pub creator_name: String, + pub creator_preferred_username: Option, + pub creator_avatar: Option, + pub creator_local: bool, pub comment_creator_id: i32, + pub comment_creator_actor_id: String, pub comment_creator_name: String, + pub comment_creator_preferred_username: Option, + pub comment_creator_avatar: Option, + pub comment_creator_local: bool, + pub resolver_actor_id: Option, + pub resolver_name: Option, + pub resolver_preferred_username: Option, + pub resolver_avatar: Option, + pub resolver_local: Option, } pub struct CommentReportQueryBuilder<'a> { diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index cf578c4cb..d7f59d03f 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -115,10 +115,10 @@ pub trait Reportable { fn report(conn: &PgConnection, form: &T) -> Result where Self: Sized; - fn resolve(conn: &PgConnection, report_id: i32, user_id: i32) -> Result + fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result where Self: Sized; - fn unresolve(conn: &PgConnection, report_id: i32) -> Result + fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result where Self: Sized; } diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs index 234b0e4db..61c32c0e2 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -20,9 +20,22 @@ table! { current_post_url -> Nullable, current_post_body -> Nullable, community_id -> Int4, + creator_actor_id -> Text, creator_name -> Varchar, + creator_preferred_username -> Nullable, + creator_avatar -> Nullable, + creator_local -> Bool, post_creator_id -> Int4, + post_creator_actor_id -> Text, post_creator_name -> Varchar, + post_creator_preferred_username -> Nullable, + post_creator_avatar -> Nullable, + post_creator_local -> Bool, + resolver_actor_id -> Nullable, + resolver_name -> Nullable, + resolver_preferred_username -> Nullable, + resolver_avatar -> Nullable, + resolver_local -> Nullable, } } @@ -62,22 +75,23 @@ impl Reportable for PostReport { .get_result::(conn) } - fn resolve(conn: &PgConnection, report_id: i32, by_user_id: i32) -> Result { + fn resolve(conn: &PgConnection, report_id: i32, by_resolver_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), + resolver_id.eq(by_resolver_id), updated.eq(naive_now()), )) .execute(conn) } - fn unresolve(conn: &PgConnection, report_id: i32) -> Result { + fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::post_report::dsl::*; update(post_report.find(report_id)) .set(( resolved.eq(false), + resolver_id.eq(by_resolver_id), updated.eq(naive_now()), )) .execute(conn) @@ -104,9 +118,22 @@ pub struct PostReportView { pub current_post_url: Option, pub current_post_body: Option, pub community_id: i32, + pub creator_actor_id: String, pub creator_name: String, + pub creator_preferred_username: Option, + pub creator_avatar: Option, + pub creator_local: bool, pub post_creator_id: i32, + pub post_creator_actor_id: String, pub post_creator_name: String, + pub post_creator_preferred_username: Option, + pub post_creator_avatar: Option, + pub post_creator_local: bool, + pub resolver_actor_id: Option, + pub resolver_name: Option, + pub resolver_preferred_username: Option, + pub resolver_avatar: Option, + pub resolver_local: Option, } impl PostReportView { 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 840d3d99a..c0547d084 100644 --- a/migrations/2020-10-13-212240_create_report_tables/up.sql +++ b/migrations/2020-10-13-212240_create_report_tables/up.sql @@ -31,14 +31,31 @@ select cr.*, c.post_id, c.content as current_comment_text, p.community_id, +-- report creator details +f.actor_id as creator_actor_id, f.name as creator_name, +f.preferred_username as creator_preferred_username, +f.avatar as creator_avatar, +f.local as creator_local, +-- comment creator details u.id as comment_creator_id, -u.name as comment_creator_name +u.actor_id as comment_creator_actor_id, +u.name as comment_creator_name, +u.preferred_username as comment_creator_preferred_username, +u.avatar as comment_creator_avatar, +u.local as comment_creator_local, +-- resolver details +r.actor_id as resolver_actor_id, +r.name as resolver_name, +r.preferred_username as resolver_preferred_username, +r.avatar as resolver_avatar, +r.local as resolver_local 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.creator_id; +left join user_ r on r.id = cr.resolver_id; create or replace view post_report_view as select pr.*, @@ -47,9 +64,27 @@ p.url as current_post_url, p.body as current_post_body, p.community_id, f.name as creator_name, +-- report creator details +f.actor_id as creator_actor_id, +f.name as creator_name, +f.preferred_username as creator_preferred_username, +f.avatar as creator_avatar, +f.local as creator_local, +-- post creator details u.id as post_creator_id, -u.name as post_creator_name +u.actor_id as post_creator_actor_id, +u.name as post_creator_name, +u.preferred_username as post_creator_preferred_username, +u.avatar as post_creator_avatar, +u.local as post_creator_local, +-- resolver details +r.actor_id as resolver_actor_id, +r.name as resolver_name, +r.preferred_username as resolver_preferred_username, +r.avatar as resolver_avatar, +r.local as resolver_local 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.creator_id; +left join user_ r on r.id = pr.resolver_id; From 9e604b4038ddea3b488565044a5da8c54a0783a2 Mon Sep 17 00:00:00 2001 From: eiknat Date: Tue, 3 Nov 2020 21:15:11 -0500 Subject: [PATCH 08/25] update/fix migration, add some doc also run cargo fmt/clippy --- lemmy_api/src/comment.rs | 50 ++++++++------ lemmy_api/src/community.rs | 3 +- lemmy_api/src/lib.rs | 14 ++-- lemmy_api/src/post.rs | 53 ++++++++------- lemmy_api/src/user.rs | 17 +++-- lemmy_db/src/comment_report.rs | 51 ++++++++++---- lemmy_db/src/community.rs | 11 +-- lemmy_db/src/lib.rs | 12 ++-- lemmy_db/src/post_report.rs | 67 +++++++++++++------ lemmy_db/src/schema.rs | 8 +-- lemmy_structs/src/comment.rs | 5 +- lemmy_structs/src/user.rs | 4 +- lemmy_websocket/src/chat_server.rs | 4 +- lemmy_websocket/src/handlers.rs | 4 +- .../up.sql | 13 ++-- src/routes/api.rs | 22 +++--- 16 files changed, 207 insertions(+), 131 deletions(-) diff --git a/lemmy_api/src/comment.rs b/lemmy_api/src/comment.rs index 083cb8410..b1107d0dc 100644 --- a/lemmy_api/src/comment.rs +++ b/lemmy_api/src/comment.rs @@ -1,11 +1,11 @@ use crate::{ check_community_ban, + collect_moderated_communities, get_post, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, Perform, - collect_moderated_communities, }; use actix_web::web::Data; use lemmy_apub::{ApubLikeableType, ApubObjectType}; @@ -20,9 +20,9 @@ use lemmy_db::{ Crud, Likeable, ListingType, + Reportable, Saveable, SortType, - Reportable, }; use lemmy_structs::{blocking, comment::*, send_local_notifs}; use lemmy_utils::{ @@ -32,9 +32,12 @@ use lemmy_utils::{ ConnectionId, LemmyError, }; -use lemmy_websocket::{messages::{SendComment, SendUserRoomMessage}, LemmyContext, UserOperation}; +use lemmy_websocket::{ + messages::{SendComment, SendModRoomMessage, SendUserRoomMessage}, + LemmyContext, + UserOperation, +}; use std::str::FromStr; -use lemmy_websocket::messages::SendModRoomMessage; #[async_trait::async_trait(?Send)] impl Perform for CreateComment { @@ -687,6 +690,7 @@ impl Perform for GetComments { } } +/// Creates a comment report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreateCommentReport { type Response = CreateCommentReportResponse; @@ -707,27 +711,30 @@ impl Perform for CreateCommentReport { 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??; + }) + .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, + original_comment_text: comment.content, reason: data.reason.to_owned(), }; let report = match blocking(context.pool(), move |conn| { CommentReport::report(conn, &report_form) - }).await? { + }) + .await? + { Ok(report) => report, - Err(_e) => return Err(APIError::err("couldnt_create_report").into()) + Err(_e) => return Err(APIError::err("couldnt_create_report").into()), }; let res = CreateCommentReportResponse { success: true }; @@ -750,6 +757,7 @@ 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; @@ -765,7 +773,8 @@ impl Perform for ResolveCommentReport { let report_id = data.report_id; let report = blocking(context.pool(), move |conn| { CommentReportView::read(&conn, report_id) - }).await??; + }) + .await??; let user_id = user.id; is_mod_or_admin(context.pool(), user_id, report.community_id).await?; @@ -779,8 +788,8 @@ impl Perform for ResolveCommentReport { } }; - if blocking(context.pool(),resolve_fun).await?.is_err() { - return Err(APIError::err("couldnt_resolve_report").into()) + if blocking(context.pool(), resolve_fun).await?.is_err() { + return Err(APIError::err("couldnt_resolve_report").into()); }; let report_id = data.report_id; @@ -800,6 +809,8 @@ impl Perform for ResolveCommentReport { } } +/// Lists comment reports for a community if an id is supplied +/// or returns all comment reports for communities a user moderates #[async_trait::async_trait(?Send)] impl Perform for ListCommentReports { type Response = ListCommentReportsResponse; @@ -814,18 +825,19 @@ impl Perform for ListCommentReports { let user_id = user.id; let community_id = data.community; - let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + 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() + .community_ids(community_ids) + .page(page) + .limit(limit) + .list() }) - .await??; + .await??; let res = ListCommentReportsResponse { comments }; @@ -838,4 +850,4 @@ impl Perform for ListCommentReports { Ok(res) } -} \ No newline at end of file +} diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index 35ed4aa9c..861433a86 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -36,12 +36,11 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{ - messages::{GetCommunityUsersOnline, JoinCommunityRoom, SendCommunityRoomMessage}, + messages::{GetCommunityUsersOnline, JoinCommunityRoom, JoinModRoom, SendCommunityRoomMessage}, LemmyContext, UserOperation, }; use std::str::FromStr; -use lemmy_websocket::messages::JoinModRoom; #[async_trait::async_trait(?Send)] impl Perform for GetCommunity { diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs index 96f2b0612..7c5c5e208 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -100,6 +100,13 @@ pub(in crate) async fn check_community_ban( } } +/// 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 +/// +/// * `user_id` - the user id of the moderator +/// * `community_id` - optional community id to check for moderator privileges +/// * `pool` - the diesel db pool pub(in crate) async fn collect_moderated_communities( user_id: i32, community_id: Option, @@ -112,7 +119,8 @@ pub(in crate) async fn collect_moderated_communities( } else { let ids = blocking(pool, move |conn: &'_ _| { CommunityModerator::get_user_moderated_communities(conn, user_id) - }).await??; + }) + .await??; Ok(ids) } } @@ -195,9 +203,7 @@ pub async fn match_websocket_operation( UserOperation::CommunityJoin => { do_websocket_operation::(context, id, op, data).await } - UserOperation::ModJoin => { - do_websocket_operation::(context, id, op, data).await - } + UserOperation::ModJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::SaveUserSettings => { do_websocket_operation::(context, id, op, data).await } diff --git a/lemmy_api/src/post.rs b/lemmy_api/src/post.rs index b703c2ef8..707c83359 100644 --- a/lemmy_api/src/post.rs +++ b/lemmy_api/src/post.rs @@ -1,11 +1,11 @@ use crate::{ check_community_ban, check_optional_url, + collect_moderated_communities, get_user_from_jwt, get_user_from_jwt_opt, is_mod_or_admin, Perform, - collect_moderated_communities, }; use actix_web::web::Data; use lemmy_apub::{ApubLikeableType, ApubObjectType}; @@ -21,9 +21,9 @@ use lemmy_db::{ Crud, Likeable, ListingType, + Reportable, Saveable, SortType, - Reportable, }; use lemmy_structs::{blocking, post::*}; use lemmy_utils::{ @@ -35,13 +35,7 @@ use lemmy_utils::{ LemmyError, }; use lemmy_websocket::{ - messages::{ - GetPostUsersOnline, - JoinPostRoom, - SendModRoomMessage, - SendPost, - SendUserRoomMessage - }, + messages::{GetPostUsersOnline, JoinPostRoom, SendModRoomMessage, SendPost, SendUserRoomMessage}, LemmyContext, UserOperation, }; @@ -751,6 +745,7 @@ impl Perform for PostJoin { } } +/// Creates a post report and notifies the moderators of the community #[async_trait::async_trait(?Send)] impl Perform for CreatePostReport { type Response = CreatePostReportResponse; @@ -771,29 +766,32 @@ impl Perform for CreatePostReport { 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??; + }) + .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, + original_post_name: post.name, + original_post_url: post.url, + original_post_body: post.body, reason: data.reason.to_owned(), }; let report = match blocking(context.pool(), move |conn| { PostReport::report(conn, &report_form) - }).await? { + }) + .await? + { Ok(report) => report, - Err(_e) => return Err(APIError::err("couldnt_create_report").into()) + Err(_e) => return Err(APIError::err("couldnt_create_report").into()), }; let res = CreatePostReportResponse { success: true }; @@ -816,6 +814,7 @@ 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; @@ -831,7 +830,8 @@ impl Perform for ResolvePostReport { let report_id = data.report_id; let report = blocking(context.pool(), move |conn| { PostReportView::read(&conn, report_id) - }).await??; + }) + .await??; let user_id = user.id; is_mod_or_admin(context.pool(), user_id, report.community_id).await?; @@ -850,8 +850,8 @@ impl Perform for ResolvePostReport { resolved: true, }; - if blocking(context.pool(),resolve_fun).await?.is_err() { - return Err(APIError::err("couldnt_resolve_report").into()) + if blocking(context.pool(), resolve_fun).await?.is_err() { + return Err(APIError::err("couldnt_resolve_report").into()); }; context.chat_server().do_send(SendModRoomMessage { @@ -865,6 +865,8 @@ impl Perform for ResolvePostReport { } } +/// Lists post reports for a community if an id is supplied +/// or returns all post reports for communities a user moderates #[async_trait::async_trait(?Send)] impl Perform for ListPostReports { type Response = ListPostReportsResponse; @@ -879,18 +881,19 @@ impl Perform for ListPostReports { let user_id = user.id; let community_id = data.community; - let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + 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() + .community_ids(community_ids) + .page(page) + .limit(limit) + .list() }) - .await??; + .await??; let res = ListPostReportsResponse { posts }; diff --git a/lemmy_api/src/user.rs b/lemmy_api/src/user.rs index 5efc63310..3b0b6d3fe 100644 --- a/lemmy_api/src/user.rs +++ b/lemmy_api/src/user.rs @@ -2,11 +2,11 @@ use crate::{ captcha_espeak_wav_base64, check_optional_url, claims::Claims, + collect_moderated_communities, get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform, - collect_moderated_communities }; use actix_web::web::Data; use anyhow::Context; @@ -1312,7 +1312,8 @@ impl Perform for GetReportCount { let user_id = user.id; let community_id = data.community; - let community_ids = collect_moderated_communities(user_id, community_id, context.pool()).await?; + let community_ids = + collect_moderated_communities(user_id, community_id, context.pool()).await?; let res = { if community_ids.is_empty() { @@ -1323,12 +1324,16 @@ impl Perform for GetReportCount { } } 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, &ids) + }) + .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, &ids) + }) + .await??; GetReportCountResponse { community: data.community, diff --git a/lemmy_db/src/comment_report.rs b/lemmy_db/src/comment_report.rs index 83f432407..b1037f111 100644 --- a/lemmy_db/src/comment_report.rs +++ b/lemmy_db/src/comment_report.rs @@ -1,14 +1,21 @@ use diesel::{dsl::*, pg::Pg, result::Error, *}; use serde::{Deserialize, Serialize}; -use crate::{limit_and_offset, MaybeOptional, schema::comment_report, comment::Comment, Reportable, naive_now}; +use crate::{ + comment::Comment, + limit_and_offset, + naive_now, + schema::comment_report, + MaybeOptional, + Reportable, +}; table! { comment_report_view (id) { id -> Int4, creator_id -> Int4, comment_id -> Int4, - comment_text -> Text, + original_comment_text -> Text, reason -> Text, resolved -> Bool, resolver_id -> Nullable, @@ -43,7 +50,7 @@ pub struct CommentReport { pub id: i32, pub creator_id: i32, pub comment_id: i32, - pub comment_text: String, + pub original_comment_text: String, pub reason: String, pub resolved: bool, pub resolver_id: Option, @@ -56,11 +63,15 @@ pub struct CommentReport { pub struct CommentReportForm { pub creator_id: i32, pub comment_id: i32, - pub comment_text: String, + pub original_comment_text: String, pub reason: String, } impl Reportable for CommentReport { + /// creates a comment report and returns it + /// + /// * `conn` - the postgres connection + /// * `comment_report_form` - the filled CommentReportForm to insert fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result { use crate::schema::comment_report::dsl::*; insert_into(comment_report) @@ -68,6 +79,11 @@ impl Reportable for CommentReport { .get_result::(conn) } + /// resolve a comment report + /// + /// * `conn` - the postgres connection + /// * `report_id` - the id of the report to resolve + /// * `by_resolver_id` - the id of the user resolving the report fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::comment_report::dsl::*; update(comment_report.find(report_id)) @@ -79,6 +95,11 @@ impl Reportable for CommentReport { .execute(conn) } + /// unresolve a comment report + /// + /// * `conn` - the postgres connection + /// * `report_id` - the id of the report to unresolve + /// * `by_resolver_id` - the id of the user unresolving the report fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::comment_report::dsl::*; update(comment_report.find(report_id)) @@ -91,15 +112,13 @@ impl Reportable for CommentReport { } } -#[derive( -Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, -)] +#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone)] #[table_name = "comment_report_view"] pub struct CommentReportView { pub id: i32, pub creator_id: i32, pub comment_id: i32, - pub comment_text: String, + pub original_comment_text: String, pub reason: String, pub resolved: bool, pub resolver_id: Option, @@ -136,19 +155,23 @@ pub struct CommentReportQueryBuilder<'a> { } 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 { use super::comment_report::comment_report_view::dsl::*; - comment_report_view - .find(report_id) - .first::(conn) + comment_report_view.find(report_id).first::(conn) } - pub fn get_report_count(conn: &PgConnection, community_ids: &Vec) -> Result { + /// returns the current unresolved comment report count for the supplied community ids + /// + /// * `community_ids` - a Vec of community_ids to get a count for + 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) + .select(count(id)) + .first::(conn) } } diff --git a/lemmy_db/src/community.rs b/lemmy_db/src/community.rs index a888015e4..b04743986 100644 --- a/lemmy_db/src/community.rs +++ b/lemmy_db/src/community.rs @@ -225,12 +225,15 @@ impl CommunityModerator { 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> { + 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) + .filter(user_id.eq(for_user_id)) + .select(community_id) + .load::(conn) } } diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index d7f59d03f..655ba68b0 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -113,14 +113,14 @@ pub trait Readable { pub trait Reportable { fn report(conn: &PgConnection, form: &T) -> Result - where - Self: Sized; + where + Self: Sized; fn resolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result - where - Self: Sized; + where + Self: Sized; fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: i32) -> Result - where - Self: Sized; + where + Self: Sized; } pub trait MaybeOptional { diff --git a/lemmy_db/src/post_report.rs b/lemmy_db/src/post_report.rs index 61c32c0e2..3422f54a5 100644 --- a/lemmy_db/src/post_report.rs +++ b/lemmy_db/src/post_report.rs @@ -1,16 +1,23 @@ use diesel::{dsl::*, pg::Pg, result::Error, *}; use serde::{Deserialize, Serialize}; -use crate::{limit_and_offset, MaybeOptional, schema::post_report, post::Post, Reportable, naive_now}; +use crate::{ + limit_and_offset, + naive_now, + post::Post, + schema::post_report, + MaybeOptional, + Reportable, +}; table! { post_report_view (id) { id -> Int4, creator_id -> Int4, post_id -> Int4, - post_name -> Varchar, - post_url -> Nullable, - post_body -> Nullable, + original_post_name -> Varchar, + original_post_url -> Nullable, + original_post_body -> Nullable, reason -> Text, resolved -> Bool, resolver_id -> Nullable, @@ -46,9 +53,9 @@ 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 original_post_name: String, + pub original_post_url: Option, + pub original_post_body: Option, pub reason: String, pub resolved: bool, pub resolver_id: Option, @@ -61,13 +68,17 @@ pub struct PostReport { pub struct PostReportForm { pub creator_id: i32, pub post_id: i32, - pub post_name: String, - pub post_url: Option, - pub post_body: Option, + pub original_post_name: String, + pub original_post_url: Option, + pub original_post_body: Option, pub reason: String, } impl Reportable for PostReport { + /// creates a post report and returns it + /// + /// * `conn` - the postgres connection + /// * `post_report_form` - the filled CommentReportForm to insert fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result { use crate::schema::post_report::dsl::*; insert_into(post_report) @@ -75,6 +86,11 @@ impl Reportable for PostReport { .get_result::(conn) } + /// resolve a post report + /// + /// * `conn` - the postgres connection + /// * `report_id` - the id of the report to resolve + /// * `by_resolver_id` - the id of the user resolving the report fn resolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::post_report::dsl::*; update(post_report.find(report_id)) @@ -86,6 +102,11 @@ impl Reportable for PostReport { .execute(conn) } + /// resolve a post report + /// + /// * `conn` - the postgres connection + /// * `report_id` - the id of the report to unresolve + /// * `by_resolver_id` - the id of the user unresolving the report fn unresolve(conn: &PgConnection, report_id: i32, by_resolver_id: i32) -> Result { use crate::schema::post_report::dsl::*; update(post_report.find(report_id)) @@ -98,17 +119,15 @@ impl Reportable for PostReport { } } -#[derive( -Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, -)] +#[derive(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 original_post_name: String, + pub original_post_url: Option, + pub original_post_body: Option, pub reason: String, pub resolved: bool, pub resolver_id: Option, @@ -137,19 +156,23 @@ pub struct PostReportView { } 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 { use super::post_report::post_report_view::dsl::*; - post_report_view - .find(report_id) - .first::(conn) + post_report_view.find(report_id).first::(conn) } - pub fn get_report_count(conn: &PgConnection, community_ids: &Vec) -> Result { + /// returns the current unresolved post report count for the supplied community ids + /// + /// * `community_ids` - a Vec of community_ids to get a count for + 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) + .select(count(id)) + .first::(conn) } } diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index b23deff34..400c87d42 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -86,7 +86,7 @@ table! { id -> Int4, creator_id -> Int4, comment_id -> Int4, - comment_text -> Text, + original_comment_text -> Text, reason -> Text, resolved -> Bool, resolver_id -> Nullable, @@ -389,9 +389,9 @@ table! { id -> Int4, creator_id -> Int4, post_id -> Int4, - post_name -> Varchar, - post_url -> Nullable, - post_body -> Nullable, + original_post_name -> Varchar, + original_post_url -> Nullable, + original_post_body -> Nullable, reason -> Text, resolved -> Bool, resolver_id -> Nullable, diff --git a/lemmy_structs/src/comment.rs b/lemmy_structs/src/comment.rs index 5ca7a5325..ed292caed 100644 --- a/lemmy_structs/src/comment.rs +++ b/lemmy_structs/src/comment.rs @@ -1,7 +1,4 @@ -use lemmy_db::{ - comment_view::CommentView, - comment_report::CommentReportView, -}; +use lemmy_db::{comment_report::CommentReportView, comment_view::CommentView}; use serde::{Deserialize, Serialize}; #[derive(Deserialize)] diff --git a/lemmy_structs/src/user.rs b/lemmy_structs/src/user.rs index 03a84ce4c..bf4a36286 100644 --- a/lemmy_structs/src/user.rs +++ b/lemmy_structs/src/user.rs @@ -247,6 +247,6 @@ pub struct GetReportCount { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct GetReportCountResponse { pub community: Option, - pub comment_reports: i32, - pub post_reports: i32, + pub comment_reports: i64, + pub post_reports: i64, } diff --git a/lemmy_websocket/src/chat_server.rs b/lemmy_websocket/src/chat_server.rs index 0a524e3db..0be54c33f 100644 --- a/lemmy_websocket/src/chat_server.rs +++ b/lemmy_websocket/src/chat_server.rs @@ -260,8 +260,8 @@ impl ChatServer { community_id: CommunityId, websocket_id: Option, ) -> Result<(), LemmyError> - where - Response: Serialize, + where + Response: Serialize, { let res_str = &serialize_websocket_message(op, response)?; if let Some(sessions) = self.mod_rooms.get(&community_id) { diff --git a/lemmy_websocket/src/handlers.rs b/lemmy_websocket/src/handlers.rs index 0b5703ab1..d95dfd57f 100644 --- a/lemmy_websocket/src/handlers.rs +++ b/lemmy_websocket/src/handlers.rs @@ -121,8 +121,8 @@ where } impl Handler> for ChatServer - where - Response: Serialize, +where + Response: Serialize, { type Result = (); 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 c0547d084..e9dce1adb 100644 --- a/migrations/2020-10-13-212240_create_report_tables/up.sql +++ b/migrations/2020-10-13-212240_create_report_tables/up.sql @@ -2,7 +2,7 @@ create table comment_report ( 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, + original_comment_text text not null, reason text not null, resolved bool not null default false, resolver_id int references user_ on update cascade on delete cascade, -- user resolving report @@ -15,9 +15,9 @@ create table post_report ( 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_url text, - post_body text, + original_post_name varchar(100) not null, + original_post_url text, + original_post_body text, reason text not null, resolved bool not null default false, resolver_id int references user_ on update cascade on delete cascade, -- user resolving report @@ -54,7 +54,7 @@ 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.creator_id; +left join user_ f on f.id = cr.creator_id left join user_ r on r.id = cr.resolver_id; create or replace view post_report_view as @@ -63,7 +63,6 @@ p.name as current_post_name, p.url as current_post_url, p.body as current_post_body, p.community_id, -f.name as creator_name, -- report creator details f.actor_id as creator_actor_id, f.name as creator_name, @@ -86,5 +85,5 @@ r.local as resolver_local 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.creator_id; +left join user_ f on f.id = pr.creator_id left join user_ r on r.id = pr.resolver_id; diff --git a/src/routes/api.rs b/src/routes/api.rs index 7b95b4ee4..167797d7d 100644 --- a/src/routes/api.rs +++ b/src/routes/api.rs @@ -82,8 +82,11 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .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::)) + .route( + "/report/resolve", + web::put().to(route_post::), + ) + .route("/report/list", web::get().to(route_get::)), ) // Comment .service( @@ -101,8 +104,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { .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::)) + .route( + "/report/resolve", + web::put().to(route_post::), + ) + .route( + "/report/list", + web::get().to(route_get::), + ), ) // Private Message .service( @@ -171,10 +180,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { "/save_user_settings", web::put().to(route_post::), ) - .route( - "/report_count", - web::get().to(route_get::) - ), + .route("/report_count", web::get().to(route_get::)), ) // Admin Actions .service( From a68cff51b1f1e07c943c363239402d09f3cde997 Mon Sep 17 00:00:00 2001 From: eiknat Date: Wed, 11 Nov 2020 15:41:40 -0500 Subject: [PATCH 09/25] add doc --- docs/src/contributing_websocket_http_api.md | 215 +++++++++++++++++++- 1 file changed, 214 insertions(+), 1 deletion(-) diff --git a/docs/src/contributing_websocket_http_api.md b/docs/src/contributing_websocket_http_api.md index 8a6df622d..a3b678eb9 100644 --- a/docs/src/contributing_websocket_http_api.md +++ b/docs/src/contributing_websocket_http_api.md @@ -306,11 +306,12 @@ Connect to ws://***host***/api/v1/ws to get started. If the ***`host`*** supports secure connections, you can use wss://***host***/api/v1/ws. -To receive websocket messages, you must join a room / context. The three available are: +To receive websocket messages, you must join a room / context. The four available are: - [UserJoin](#user-join). Receives replies, private messages, etc. - [PostJoin](#post-join). Receives new comments on a post. - [CommunityJoin](#community-join). Receives front page / community posts. +- [ModJoin](#mod-join). Receives community moderator updates like reports. #### Testing with Websocat @@ -913,6 +914,35 @@ Marks all user replies and mentions as read. `POST /user/join` +#### Get Report Count + +If a community is supplied, returns the report count for only that community, otherwise returns the report count for all communities the user moderates. + +##### Request +```rust +{ + op: "GetReportCount", + data: { + community: Option, + auth: String + } +} +``` +##### Response +```rust +{ + op: "GetReportCount", + data: { + community: Option, + comment_reports: i64, + post_reports: i64, + } +} +``` +##### HTTP + +`GET /user/report_count` + ### Site #### List Categories ##### Request @@ -1489,6 +1519,29 @@ The main / frontpage community is `community_id: 0`. `POST /community/join` +#### Mod Join +##### Request +```rust +{ + op: "ModJoin", + data: { + community_id: i32 + } +} +``` +##### Response +```rust +{ + op: "ModJoin", + data: { + joined: bool, + } +} +``` +##### HTTP + +`POST /community/mod/join` + ### Post #### Create Post ##### Request @@ -1796,6 +1849,86 @@ Only admins and mods can sticky a post. `POST /post/join` +#### Create Post Report +##### Request +```rust +{ + op: "CreatePostReport", + data: { + post_id: i32, + reason: String, + auth: String + } +} +``` +##### Response +```rust +{ + op: "CreatePostReport", + data: { + success: bool + } +} +``` +##### HTTP + +`POST /post/report` + +#### Resolve Post Report +##### Request +```rust +{ + op: "ResolvePostReport", + data: { + report_id: i32, + resolved: bool, + auth: String + } +} +``` +##### Response +```rust +{ + op: "ResolvePostReport", + data: { + report_id: i32, + resolved: bool + } +} +``` +##### HTTP + +`PUT /post/report/resolve` + +#### List Post Reports + +If a community is supplied, returns reports for only that community, otherwise returns the reports for all communities the user moderates + +##### Request +```rust +{ + op: "ListPostReports", + data: { + page: Option, + limit: Option, + community: Option, + auth: String + } +} +``` +##### Response +```rust +{ + op: "ListPostReports", + data: { + posts: Vec + } +} +``` +##### HTTP + +`GET /post/report/list` + ### Comment #### Create Comment ##### Request @@ -2025,6 +2158,86 @@ Only the recipient can do this. `POST /comment/like` +#### Create Comment Report +##### Request +```rust +{ + op: "CreateCommentReport", + data: { + comment_id: i32, + reason: String, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "CreateCommentReport", + data: { + success: bool, + } +} +``` +##### HTTP + +`POST /comment/report` + +#### Resolve Comment Report +##### Request +```rust +{ + op: "ResolveCommentReport", + data: { + report_id: i32, + resolved: bool, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "ResolveCommentReport", + data: { + report_id: i32, + resolved: bool, + } +} +``` +##### HTTP + +`PUT /comment/report/resolve` + +#### List Comment Reports + +If a community is supplied, returns reports for only that community, otherwise returns the reports for all communities the user moderates + +##### Request +```rust +{ + op: "ListCommentReports", + data: { + page: Option, + limit: Option, + community: Option, + auth: String, + } +} +``` +##### Response +```rust +{ + op: "ListCommentReports", + data: { + comments: Vec + } +} +``` +##### HTTP + +`GET /comment/report/list` + ### RSS / Atom feeds #### All From aaa3fc08af27ce6204ba359f68d4bb1f2cc60327 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 11 Nov 2020 17:40:45 +0100 Subject: [PATCH 10/25] Add user_inbox check that activities are really addressed to local users --- lemmy_apub/src/inbox/mod.rs | 42 ++++++++++++++++++++++++- lemmy_apub/src/inbox/shared_inbox.rs | 46 +++++----------------------- lemmy_apub/src/inbox/user_inbox.rs | 42 +++++++++++++++++++++++-- lemmy_db/src/community.rs | 9 ++++++ lemmy_db/src/lib.rs | 1 + 5 files changed, 97 insertions(+), 43 deletions(-) diff --git a/lemmy_apub/src/inbox/mod.rs b/lemmy_apub/src/inbox/mod.rs index cb5bd9a79..4fdbb7a53 100644 --- a/lemmy_apub/src/inbox/mod.rs +++ b/lemmy_apub/src/inbox/mod.rs @@ -12,7 +12,7 @@ use activitystreams::{ }; use actix_web::HttpRequest; use anyhow::{anyhow, Context}; -use lemmy_db::{activity::Activity, DbPool}; +use lemmy_db::{activity::Activity, community::Community, user::User_, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -111,3 +111,43 @@ where verify_signature(&request, actor.as_ref())?; Ok(actor) } + +/// Returns true if `to_and_cc` contains at least one local user. +pub(crate) async fn is_addressed_to_local_user( + to_and_cc: &[Url], + pool: &DbPool, +) -> Result { + for url in to_and_cc { + let url = url.to_string(); + let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?; + if let Ok(u) = user { + if u.local { + return Ok(true); + } + } + } + Ok(false) +} + +/// If `to_and_cc` contains the followers collection of a remote community, returns this community +/// (like `https://example.com/c/main/followers`) +pub(crate) async fn is_addressed_to_community_followers( + to_and_cc: &[Url], + pool: &DbPool, +) -> Result, LemmyError> { + for url in to_and_cc { + let url = url.to_string(); + // TODO: extremely hacky, we should just store the followers url for each community in the db + if url.ends_with("/followers") { + let community_url = url.replace("/followers", ""); + let community = blocking(&pool, move |conn| { + Community::read_from_actor_id(&conn, &community_url) + }) + .await??; + if !community.local { + return Ok(Some(community)); + } + } + } + Ok(None) +} diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs index ab0a5f189..b693f48ad 100644 --- a/lemmy_apub/src/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -5,6 +5,8 @@ use crate::{ get_activity_to_and_cc, inbox_verify_http_signature, is_activity_already_known, + is_addressed_to_community_followers, + is_addressed_to_local_user, user_inbox::{user_receive_message, UserAcceptedActivities}, }, insert_activity, @@ -12,7 +14,7 @@ use crate::{ use activitystreams::{activity::ActorAndObject, prelude::*}; use actix_web::{web, HttpRequest, HttpResponse}; use anyhow::Context; -use lemmy_db::{community::Community, user::User_, DbPool}; +use lemmy_db::{community::Community, DbPool}; use lemmy_structs::blocking; use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; @@ -100,7 +102,10 @@ pub async fn shared_inbox( } // If to_and_cc contains followers collection of a community, pass to receive_user_message() - if is_addressed_to_community_followers(&to_and_cc, context.pool()).await? { + if is_addressed_to_community_followers(&to_and_cc, context.pool()) + .await? + .is_some() + { let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())? .context(location_info!())?; res = Some( @@ -146,40 +151,3 @@ async fn extract_local_community_from_destinations( } Ok(None) } - -/// Returns true if `to_and_cc` contains at least one local user. -async fn is_addressed_to_local_user(to_and_cc: &[Url], pool: &DbPool) -> Result { - for url in to_and_cc { - let url = url.to_string(); - let user = blocking(&pool, move |conn| User_::read_from_actor_id(&conn, &url)).await?; - if let Ok(u) = user { - if u.local { - return Ok(true); - } - } - } - Ok(false) -} - -/// Returns true if `to_and_cc` contains at least one followers collection of a remote community -/// (like `https://example.com/c/main/followers`) -async fn is_addressed_to_community_followers( - to_and_cc: &[Url], - pool: &DbPool, -) -> Result { - for url in to_and_cc { - let url = url.to_string(); - // TODO: extremely hacky, we should just store the followers url for each community in the db - if url.ends_with("/followers") { - let community_url = url.replace("/followers", ""); - let community = blocking(&pool, move |conn| { - Community::read_from_actor_id(&conn, &community_url) - }) - .await??; - if !community.local { - return Ok(true); - } - } - } - Ok(false) -} diff --git a/lemmy_apub/src/inbox/user_inbox.rs b/lemmy_apub/src/inbox/user_inbox.rs index f28df83af..dfcb2d618 100644 --- a/lemmy_apub/src/inbox/user_inbox.rs +++ b/lemmy_apub/src/inbox/user_inbox.rs @@ -23,6 +23,8 @@ use crate::{ get_activity_to_and_cc, inbox_verify_http_signature, is_activity_already_known, + is_addressed_to_community_followers, + is_addressed_to_local_user, is_addressed_to_public, receive_for_community::{ receive_create_for_community, @@ -99,6 +101,7 @@ pub async fn user_inbox( }) .await??; let to_and_cc = get_activity_to_and_cc(&activity)?; + // TODO: we should also accept activities that are sent to community followers if !to_and_cc.contains(&&user.actor_id()?) { return Err(anyhow!("Activity delivered to wrong user").into()); } @@ -130,9 +133,7 @@ pub(crate) async fn user_receive_message( context: &LemmyContext, request_counter: &mut i32, ) -> Result { - // TODO: must be addressed to one or more local users, or to followers of a remote community - - // TODO: if it is addressed to community followers, check that at least one local user is following it + is_for_user_inbox(context, &activity).await?; let any_base = activity.clone().into_any_base()?; let kind = activity.kind().context(location_info!())?; @@ -162,6 +163,41 @@ pub(crate) async fn user_receive_message( Ok(HttpResponse::Ok().finish()) } +/// Returns true if the activity is addressed directly to one or more local users, or if it is +/// addressed to the followers collection of a remote community, and at least one local user follows +/// it. +async fn is_for_user_inbox( + context: &LemmyContext, + activity: &UserAcceptedActivities, +) -> Result<(), LemmyError> { + let to_and_cc = get_activity_to_and_cc(activity)?; + // Check if it is addressed directly to any local user + if is_addressed_to_local_user(&to_and_cc, context.pool()).await? { + return Ok(()); + } + + // Check if it is addressed to any followers collection of a remote community, and that the + // community has local followers. + let community = is_addressed_to_community_followers(&to_and_cc, context.pool()).await?; + if let Some(c) = community { + let community_id = c.id; + let has_local_followers = blocking(&context.pool(), move |conn| { + CommunityFollower::has_local_followers(conn, community_id) + }) + .await??; + if c.local { + return Err( + anyhow!("Remote activity cant be addressed to followers of local community").into(), + ); + } + if has_local_followers { + return Ok(()); + } + } + + Err(anyhow!("Not addressed for any local user").into()) +} + /// Handle accepted follows. async fn receive_accept( context: &LemmyContext, diff --git a/lemmy_db/src/community.rs b/lemmy_db/src/community.rs index b04743986..5f76d5143 100644 --- a/lemmy_db/src/community.rs +++ b/lemmy_db/src/community.rs @@ -333,6 +333,15 @@ impl Followable for CommunityFollower { ) .execute(conn) } + // TODO: this function name only makes sense if you call it with a remote community. for a local + // community, it will also return true if only remote followers exist + fn has_local_followers(conn: &PgConnection, community_id_: i32) -> Result { + use crate::schema::community_follower::dsl::*; + diesel::select(exists( + community_follower.filter(community_id.eq(community_id_)), + )) + .get_result(conn) + } } #[cfg(test)] diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index 655ba68b0..608632061 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -64,6 +64,7 @@ pub trait Followable { fn unfollow(conn: &PgConnection, form: &T) -> Result where Self: Sized; + fn has_local_followers(conn: &PgConnection, community_id: i32) -> Result; } pub trait Joinable { From 8b8d47f6f54cb2d6cb29e9a019fd863e58158062 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Thu, 12 Nov 2020 11:54:31 -0600 Subject: [PATCH 11/25] Version v0.8.4 --- ansible/VERSION | 2 +- docker/dev/docker-compose.yml | 2 +- docker/federation/docker-compose.yml | 10 +++++----- docker/prod/docker-compose.yml | 4 ++-- docker/travis/docker_push.sh | 4 ++-- lemmy_api/src/version.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index e18f84527..5b63634d0 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.8.3 +v0.8.4 diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 1bd2c3c99..bf25c2592 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -16,7 +16,7 @@ services: - postgres - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 ports: - "1235:1234" restart: always diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index 002cd4e93..a96c89fc9 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -29,7 +29,7 @@ services: - ./volumes/pictrs_alpha:/mnt lemmy-alpha-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 environment: - LEMMY_INTERNAL_HOST=lemmy-alpha:8541 - LEMMY_EXTERNAL_HOST=localhost:8541 @@ -68,7 +68,7 @@ services: - ./volumes/postgres_alpha:/var/lib/postgresql/data lemmy-beta-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 environment: - LEMMY_INTERNAL_HOST=lemmy-beta:8551 - LEMMY_EXTERNAL_HOST=localhost:8551 @@ -107,7 +107,7 @@ services: - ./volumes/postgres_beta:/var/lib/postgresql/data lemmy-gamma-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 environment: - LEMMY_INTERNAL_HOST=lemmy-gamma:8561 - LEMMY_EXTERNAL_HOST=localhost:8561 @@ -147,7 +147,7 @@ services: # An instance with only an allowlist for beta lemmy-delta-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 environment: - LEMMY_INTERNAL_HOST=lemmy-delta:8571 - LEMMY_EXTERNAL_HOST=localhost:8571 @@ -187,7 +187,7 @@ services: # An instance who has a blocklist, with lemmy-alpha blocked lemmy-epsilon-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 environment: - LEMMY_INTERNAL_HOST=lemmy-epsilon:8581 - LEMMY_EXTERNAL_HOST=localhost:8581 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index faf822a99..7f5c61bf3 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.8.3 + image: dessalines/lemmy:v0.8.4 ports: - "127.0.0.1:8536:8536" restart: always @@ -26,7 +26,7 @@ services: - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.3 + image: dessalines/lemmy-ui:v0.8.4 ports: - "1235:1234" restart: always diff --git a/docker/travis/docker_push.sh b/docker/travis/docker_push.sh index 914b0333a..a881758cf 100644 --- a/docker/travis/docker_push.sh +++ b/docker/travis/docker_push.sh @@ -1,5 +1,5 @@ #!/bin/sh echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin docker tag dessalines/lemmy:travis \ - dessalines/lemmy:v0.8.3 -docker push dessalines/lemmy:v0.8.3 + dessalines/lemmy:v0.8.4 +docker push dessalines/lemmy:v0.8.4 diff --git a/lemmy_api/src/version.rs b/lemmy_api/src/version.rs index 62080fd61..bc5be0c09 100644 --- a/lemmy_api/src/version.rs +++ b/lemmy_api/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.8.3"; +pub const VERSION: &str = "v0.8.4"; From d6493f31d91c36b248343e89443c16dd6ad70e1e Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 16 Nov 2020 16:44:04 +0100 Subject: [PATCH 12/25] Reduce visibility of some structs and methods (replaces #1266) --- lemmy_api/src/community.rs | 2 +- lemmy_api/src/lib.rs | 14 +++++++------- lemmy_apub/src/activities/mod.rs | 4 ++-- lemmy_apub/src/activities/send/mod.rs | 10 +++++----- lemmy_apub/src/extensions/mod.rs | 6 +++--- lemmy_apub/src/fetcher.rs | 2 +- lemmy_apub/src/objects/mod.rs | 10 +++++----- lemmy_rate_limit/src/rate_limiter.rs | 6 +++--- src/routes/feeds.rs | 2 +- src/routes/images.rs | 6 +++--- src/routes/nodeinfo.rs | 12 ++++++------ src/routes/webfinger.rs | 2 +- 12 files changed, 38 insertions(+), 38 deletions(-) diff --git a/lemmy_api/src/community.rs b/lemmy_api/src/community.rs index 861433a86..762420202 100644 --- a/lemmy_api/src/community.rs +++ b/lemmy_api/src/community.rs @@ -843,7 +843,7 @@ impl Perform for TransferCommunity { } } -pub fn send_community_websocket( +fn send_community_websocket( res: &CommunityResponse, context: &Data, websocket_id: Option, diff --git a/lemmy_api/src/lib.rs b/lemmy_api/src/lib.rs index 7c5c5e208..1f678e62c 100644 --- a/lemmy_api/src/lib.rs +++ b/lemmy_api/src/lib.rs @@ -34,7 +34,7 @@ pub trait Perform { ) -> Result; } -pub(in crate) async fn is_mod_or_admin( +pub(crate) async fn is_mod_or_admin( pool: &DbPool, user_id: i32, community_id: i32, @@ -56,14 +56,14 @@ pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { Ok(()) } -pub(in crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result { +pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result { match blocking(pool, move |conn| Post::read(conn, post_id)).await? { Ok(post) => Ok(post), Err(_e) => Err(APIError::err("couldnt_find_post").into()), } } -pub(in crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result { +pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result { let claims = match Claims::decode(&jwt) { Ok(claims) => claims.claims, Err(_e) => return Err(APIError::err("not_logged_in").into()), @@ -77,7 +77,7 @@ pub(in crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result, pool: &DbPool, ) -> Result, LemmyError> { @@ -87,7 +87,7 @@ pub(in crate) async fn get_user_from_jwt_opt( } } -pub(in crate) async fn check_community_ban( +pub(crate) async fn check_community_ban( user_id: i32, community_id: i32, pool: &DbPool, @@ -125,7 +125,7 @@ pub(in crate) async fn collect_moderated_communities( } } -pub(in crate) fn check_optional_url(item: &Option>) -> Result<(), LemmyError> { +pub(crate) fn check_optional_url(item: &Option>) -> Result<(), LemmyError> { if let Some(Some(item)) = &item { if Url::parse(item).is_err() { return Err(APIError::err("invalid_url").into()); @@ -134,7 +134,7 @@ pub(in crate) fn check_optional_url(item: &Option>) -> Result<(), Ok(()) } -pub(in crate) async fn linked_instances(pool: &DbPool) -> Result, LemmyError> { +pub(crate) async fn linked_instances(pool: &DbPool) -> Result, LemmyError> { let mut instances: Vec = Vec::new(); if Settings::get().federation.enabled { diff --git a/lemmy_apub/src/activities/mod.rs b/lemmy_apub/src/activities/mod.rs index afea56e22..8e25b5128 100644 --- a/lemmy_apub/src/activities/mod.rs +++ b/lemmy_apub/src/activities/mod.rs @@ -1,2 +1,2 @@ -pub mod receive; -pub mod send; +pub(crate) mod receive; +pub(crate) mod send; diff --git a/lemmy_apub/src/activities/send/mod.rs b/lemmy_apub/src/activities/send/mod.rs index 537103b6a..166855e20 100644 --- a/lemmy_apub/src/activities/send/mod.rs +++ b/lemmy_apub/src/activities/send/mod.rs @@ -2,11 +2,11 @@ use lemmy_utils::settings::Settings; use url::{ParseError, Url}; use uuid::Uuid; -pub mod comment; -pub mod community; -pub mod post; -pub mod private_message; -pub mod user; +pub(crate) mod comment; +pub(crate) mod community; +pub(crate) mod post; +pub(crate) mod private_message; +pub(crate) mod user; /// Generate a unique ID for an activity, in the format: /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36` diff --git a/lemmy_apub/src/extensions/mod.rs b/lemmy_apub/src/extensions/mod.rs index fdd06e5b7..f4723a947 100644 --- a/lemmy_apub/src/extensions/mod.rs +++ b/lemmy_apub/src/extensions/mod.rs @@ -1,3 +1,3 @@ -pub mod group_extensions; -pub mod page_extension; -pub mod signatures; +pub(crate) mod group_extensions; +pub(crate) mod page_extension; +pub(crate) mod signatures; diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index acf94ec9e..b4598ea37 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -87,7 +87,7 @@ where /// The types of ActivityPub objects that can be fetched directly by searching for their ID. #[serde(untagged)] #[derive(serde::Deserialize, Debug)] -pub enum SearchAcceptedObjects { +enum SearchAcceptedObjects { Person(Box), Group(Box), Page(Box), diff --git a/lemmy_apub/src/objects/mod.rs b/lemmy_apub/src/objects/mod.rs index 56d02607c..8fd0e5679 100644 --- a/lemmy_apub/src/objects/mod.rs +++ b/lemmy_apub/src/objects/mod.rs @@ -9,11 +9,11 @@ use chrono::NaiveDateTime; use lemmy_utils::{location_info, utils::convert_datetime, LemmyError}; use url::Url; -pub mod comment; -pub mod community; -pub mod post; -pub mod private_message; -pub mod user; +pub(crate) mod comment; +pub(crate) mod community; +pub(crate) mod post; +pub(crate) mod private_message; +pub(crate) mod user; /// Updated is actually the deletion time fn create_tombstone( diff --git a/lemmy_rate_limit/src/rate_limiter.rs b/lemmy_rate_limit/src/rate_limiter.rs index 1c355acf3..bd089febd 100644 --- a/lemmy_rate_limit/src/rate_limiter.rs +++ b/lemmy_rate_limit/src/rate_limiter.rs @@ -4,13 +4,13 @@ use std::{collections::HashMap, time::SystemTime}; use strum::IntoEnumIterator; #[derive(Debug, Clone)] -pub struct RateLimitBucket { +struct RateLimitBucket { last_checked: SystemTime, allowance: f64, } #[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone, AsRefStr)] -pub enum RateLimitType { +pub(crate) enum RateLimitType { Message, Register, Post, @@ -20,7 +20,7 @@ pub enum RateLimitType { /// Rate limiting based on rate type and IP addr #[derive(Debug, Clone)] pub struct RateLimiter { - pub buckets: HashMap>, + buckets: HashMap>, } impl Default for RateLimiter { diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index f9111169a..303a824b1 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -22,7 +22,7 @@ use std::str::FromStr; use strum::ParseError; #[derive(Deserialize)] -pub struct Params { +struct Params { sort: Option, } diff --git a/src/routes/images.rs b/src/routes/images.rs index 98b13f995..deaf11ebf 100644 --- a/src/routes/images.rs +++ b/src/routes/images.rs @@ -24,19 +24,19 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) { } #[derive(Debug, Serialize, Deserialize)] -pub struct Image { +struct Image { file: String, delete_token: String, } #[derive(Debug, Serialize, Deserialize)] -pub struct Images { +struct Images { msg: String, files: Option>, } #[derive(Deserialize)] -pub struct PictrsParams { +struct PictrsParams { format: Option, thumbnail: Option, } diff --git a/src/routes/nodeinfo.rs b/src/routes/nodeinfo.rs index c41be0e94..1d9525ef2 100644 --- a/src/routes/nodeinfo.rs +++ b/src/routes/nodeinfo.rs @@ -59,18 +59,18 @@ async fn node_info(context: web::Data) -> Result, @@ -78,14 +78,14 @@ pub struct NodeInfo { } #[derive(Serialize, Deserialize, Debug)] -pub struct NodeInfoSoftware { +struct NodeInfoSoftware { pub name: String, pub version: String, } #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct NodeInfoUsage { +struct NodeInfoUsage { pub users: NodeInfoUsers, pub local_posts: i64, pub local_comments: i64, @@ -93,6 +93,6 @@ pub struct NodeInfoUsage { } #[derive(Serialize, Deserialize, Debug)] -pub struct NodeInfoUsers { +struct NodeInfoUsers { pub total: i64, } diff --git a/src/routes/webfinger.rs b/src/routes/webfinger.rs index bef94e6f9..ba687abdf 100644 --- a/src/routes/webfinger.rs +++ b/src/routes/webfinger.rs @@ -12,7 +12,7 @@ use lemmy_websocket::LemmyContext; use serde::Deserialize; #[derive(Deserialize)] -pub struct Params { +struct Params { resource: String, } From 11fdef56db95b0e60ba49c54676d7ff4fa98eab3 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 16 Nov 2020 21:41:47 +0100 Subject: [PATCH 13/25] Dont handle activities twice in inbox --- lemmy_apub/src/inbox/shared_inbox.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lemmy_apub/src/inbox/shared_inbox.rs b/lemmy_apub/src/inbox/shared_inbox.rs index b693f48ad..dfd583662 100644 --- a/lemmy_apub/src/inbox/shared_inbox.rs +++ b/lemmy_apub/src/inbox/shared_inbox.rs @@ -65,10 +65,12 @@ pub async fn shared_inbox( let activity_any_base = activity.clone().into_any_base()?; let mut res: Option = None; let to_and_cc = get_activity_to_and_cc(&activity)?; - // If to_and_cc contains a local community, pass to receive_community_message() // Handle community first, so in case the sender is banned by the community, it will error out. // If we handled the user receive first, the activity would be inserted to the database before the // community could check for bans. + // Note that an activity can be addressed to a community and to a user (or multiple users) at the + // same time. In this case we still only handle it once, to avoid duplicate websocket + // notifications. let community = extract_local_community_from_destinations(&to_and_cc, context.pool()).await?; if let Some(community) = community { let community_activity = CommunityAcceptedActivities::from_any_base(activity_any_base.clone())? @@ -83,10 +85,7 @@ pub async fn shared_inbox( ) .await?, ); - } - - // If to_and_cc contains a local user, pass to receive_user_message() - if is_addressed_to_local_user(&to_and_cc, context.pool()).await? { + } else if is_addressed_to_local_user(&to_and_cc, context.pool()).await? { let user_activity = UserAcceptedActivities::from_any_base(activity_any_base.clone())? .context(location_info!())?; // `to_user` is only used for follow activities (which we dont receive here), so no need to pass @@ -99,10 +98,7 @@ pub async fn shared_inbox( request_counter, ) .await?; - } - - // If to_and_cc contains followers collection of a community, pass to receive_user_message() - if is_addressed_to_community_followers(&to_and_cc, context.pool()) + } else if is_addressed_to_community_followers(&to_and_cc, context.pool()) .await? .is_some() { From 250dcc26bea77748ebf36000feab41deeb8be334 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 17 Nov 2020 18:05:25 +0100 Subject: [PATCH 14/25] For community_name API parameters, only search locally (fixes #1271) --- docs/src/contributing_websocket_http_api.md | 7 +++++++ lemmy_db/src/comment_view.rs | 4 +++- lemmy_db/src/post_view.rs | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/src/contributing_websocket_http_api.md b/docs/src/contributing_websocket_http_api.md index a3b678eb9..bb9b063a5 100644 --- a/docs/src/contributing_websocket_http_api.md +++ b/docs/src/contributing_websocket_http_api.md @@ -492,6 +492,9 @@ These expire after 10 minutes. `GET /user/get_captcha` #### Get User Details + +`username` can only be used for local users. To get details for a federated user, pass `user_id` instead. + ##### Request ```rust { @@ -1602,6 +1605,8 @@ The main / frontpage community is `community_id: 0`. Post listing types are `All, Subscribed, Community` +`community_name` can only be used for local communities. To get posts for a federated community, pass `community_id` instead. + ##### Request ```rust { @@ -2049,6 +2054,8 @@ Only a mod or admin can remove the comment. Comment listing types are `All, Subscribed, Community` +`community_name` can only be used for local communities. To get posts for a federated community, pass `community_id` instead. + ##### Request ```rust { diff --git a/lemmy_db/src/comment_view.rs b/lemmy_db/src/comment_view.rs index e1299cb14..4b6dc1924 100644 --- a/lemmy_db/src/comment_view.rs +++ b/lemmy_db/src/comment_view.rs @@ -239,7 +239,9 @@ impl<'a> CommentQueryBuilder<'a> { } if let Some(for_community_name) = self.for_community_name { - query = query.filter(community_name.eq(for_community_name)); + query = query + .filter(community_name.eq(for_community_name)) + .filter(local.eq(true)); } if let Some(for_post_id) = self.for_post_id { diff --git a/lemmy_db/src/post_view.rs b/lemmy_db/src/post_view.rs index 38d9a0211..0ec4a9797 100644 --- a/lemmy_db/src/post_view.rs +++ b/lemmy_db/src/post_view.rs @@ -280,6 +280,7 @@ impl<'a> PostQueryBuilder<'a> { if let Some(for_community_name) = self.for_community_name { query = query .filter(community_name.eq(for_community_name)) + .filter(local.eq(true)) .then_order_by(stickied.desc()); } From f3eebb1dfc25d488195a159fcc07e508a4ab654d Mon Sep 17 00:00:00 2001 From: Dessalines Date: Tue, 17 Nov 2020 15:11:58 -0600 Subject: [PATCH 15/25] Version v0.8.5 --- ansible/VERSION | 2 +- docker/dev/docker-compose.yml | 2 +- docker/federation/docker-compose.yml | 10 +++++----- docker/prod/docker-compose.yml | 4 ++-- docker/travis/docker_push.sh | 4 ++-- lemmy_api/src/version.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index 5b63634d0..9fbdd82bd 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.8.4 +v0.8.5 diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index bf25c2592..11be6b373 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -16,7 +16,7 @@ services: - postgres - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 ports: - "1235:1234" restart: always diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index a96c89fc9..ce006e41e 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -29,7 +29,7 @@ services: - ./volumes/pictrs_alpha:/mnt lemmy-alpha-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 environment: - LEMMY_INTERNAL_HOST=lemmy-alpha:8541 - LEMMY_EXTERNAL_HOST=localhost:8541 @@ -68,7 +68,7 @@ services: - ./volumes/postgres_alpha:/var/lib/postgresql/data lemmy-beta-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 environment: - LEMMY_INTERNAL_HOST=lemmy-beta:8551 - LEMMY_EXTERNAL_HOST=localhost:8551 @@ -107,7 +107,7 @@ services: - ./volumes/postgres_beta:/var/lib/postgresql/data lemmy-gamma-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 environment: - LEMMY_INTERNAL_HOST=lemmy-gamma:8561 - LEMMY_EXTERNAL_HOST=localhost:8561 @@ -147,7 +147,7 @@ services: # An instance with only an allowlist for beta lemmy-delta-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 environment: - LEMMY_INTERNAL_HOST=lemmy-delta:8571 - LEMMY_EXTERNAL_HOST=localhost:8571 @@ -187,7 +187,7 @@ services: # An instance who has a blocklist, with lemmy-alpha blocked lemmy-epsilon-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 environment: - LEMMY_INTERNAL_HOST=lemmy-epsilon:8581 - LEMMY_EXTERNAL_HOST=localhost:8581 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 7f5c61bf3..5eafd099a 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.8.4 + image: dessalines/lemmy:v0.8.5 ports: - "127.0.0.1:8536:8536" restart: always @@ -26,7 +26,7 @@ services: - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.4 + image: dessalines/lemmy-ui:v0.8.5 ports: - "1235:1234" restart: always diff --git a/docker/travis/docker_push.sh b/docker/travis/docker_push.sh index a881758cf..68346af8e 100644 --- a/docker/travis/docker_push.sh +++ b/docker/travis/docker_push.sh @@ -1,5 +1,5 @@ #!/bin/sh echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin docker tag dessalines/lemmy:travis \ - dessalines/lemmy:v0.8.4 -docker push dessalines/lemmy:v0.8.4 + dessalines/lemmy:v0.8.5 +docker push dessalines/lemmy:v0.8.5 diff --git a/lemmy_api/src/version.rs b/lemmy_api/src/version.rs index bc5be0c09..dc149d5b6 100644 --- a/lemmy_api/src/version.rs +++ b/lemmy_api/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.8.4"; +pub const VERSION: &str = "v0.8.5"; From 7fe4558beebdf839c10cd543120023a836cc74c6 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 18 Nov 2020 17:04:35 +0100 Subject: [PATCH 16/25] Create empty outbox for user (ref #1220) --- lemmy_apub/src/http/user.rs | 24 +++++++++++++++++++++++- lemmy_apub/src/objects/user.rs | 12 +++++++----- src/routes/federation.rs | 3 ++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs index 85a5f22cc..f983ab6f9 100644 --- a/lemmy_apub/src/http/user.rs +++ b/lemmy_apub/src/http/user.rs @@ -1,10 +1,15 @@ -use crate::{http::create_apub_response, ToApub}; +use crate::{http::create_apub_response, ActorType, ToApub}; +use activitystreams::{ + base::BaseExt, + collection::{CollectionExt, OrderedCollection}, +}; use actix_web::{body::Body, web, HttpResponse}; use lemmy_db::user::User_; use lemmy_structs::blocking; use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use serde::Deserialize; +use url::Url; #[derive(Deserialize)] pub struct UserQuery { @@ -24,3 +29,20 @@ pub async fn get_apub_user_http( let u = user.to_apub(context.pool()).await?; Ok(create_apub_response(&u)) } + +pub async fn get_apub_user_outbox( + info: web::Path, + context: web::Data, +) -> Result, LemmyError> { + let user = blocking(context.pool(), move |conn| { + User_::read_from_name(&conn, &info.user_name) + }) + .await??; + let mut collection = OrderedCollection::new(); + collection + .set_many_items(Vec::::new()) + .set_context(activitystreams::context()) + .set_id(user.get_outbox_url()?) + .set_total_items(0_u64); + Ok(create_apub_response(&collection)) +} diff --git a/lemmy_apub/src/objects/user.rs b/lemmy_apub/src/objects/user.rs index 3c41b5584..49b7c9e5c 100644 --- a/lemmy_apub/src/objects/user.rs +++ b/lemmy_apub/src/objects/user.rs @@ -55,11 +55,13 @@ impl ToApub for User_ { } let mut ap_actor = ApActor::new(self.get_inbox_url()?, person); - ap_actor.set_preferred_username(self.name.to_owned()); - ap_actor.set_endpoints(Endpoints { - shared_inbox: Some(self.get_shared_inbox_url()?), - ..Default::default() - }); + ap_actor + .set_preferred_username(self.name.to_owned()) + .set_outbox(self.get_outbox_url()?) + .set_endpoints(Endpoints { + shared_inbox: Some(self.get_shared_inbox_url()?), + ..Default::default() + }); Ok(Ext1::new(ap_actor, self.get_public_key_ext()?)) } diff --git a/src/routes/federation.rs b/src/routes/federation.rs index 96b380e61..4d03de770 100644 --- a/src/routes/federation.rs +++ b/src/routes/federation.rs @@ -6,7 +6,7 @@ use lemmy_apub::{ community::{get_apub_community_followers, get_apub_community_http, get_apub_community_outbox}, get_activity, post::get_apub_post, - user::get_apub_user_http, + user::{get_apub_user_http, get_apub_user_outbox}, }, inbox::{community_inbox::community_inbox, shared_inbox::shared_inbox, user_inbox::user_inbox}, APUB_JSON_CONTENT_TYPE, @@ -42,6 +42,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { web::get().to(get_apub_community_outbox), ) .route("/u/{user_name}", web::get().to(get_apub_user_http)) + .route("/u/{user_name}/outbox", web::get().to(get_apub_user_outbox)) .route("/post/{post_id}", web::get().to(get_apub_post)) .route("/comment/{comment_id}", web::get().to(get_apub_comment)) .route("/activities/{type_}/{id}", web::get().to(get_activity)), From ac75304a09ba730982a562f9fe34963bd99c0c36 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 18 Nov 2020 13:12:24 -0600 Subject: [PATCH 17/25] Version v0.8.6 --- ansible/VERSION | 2 +- docker/dev/docker-compose.yml | 2 +- docker/federation/docker-compose.yml | 10 +++++----- docker/prod/docker-compose.yml | 4 ++-- docker/travis/docker_push.sh | 4 ++-- lemmy_api/src/version.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index 9fbdd82bd..85c3002dc 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.8.5 +v0.8.6 diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 11be6b373..f825ecf58 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -16,7 +16,7 @@ services: - postgres - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 ports: - "1235:1234" restart: always diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index ce006e41e..5b78bcbff 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -29,7 +29,7 @@ services: - ./volumes/pictrs_alpha:/mnt lemmy-alpha-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 environment: - LEMMY_INTERNAL_HOST=lemmy-alpha:8541 - LEMMY_EXTERNAL_HOST=localhost:8541 @@ -68,7 +68,7 @@ services: - ./volumes/postgres_alpha:/var/lib/postgresql/data lemmy-beta-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 environment: - LEMMY_INTERNAL_HOST=lemmy-beta:8551 - LEMMY_EXTERNAL_HOST=localhost:8551 @@ -107,7 +107,7 @@ services: - ./volumes/postgres_beta:/var/lib/postgresql/data lemmy-gamma-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 environment: - LEMMY_INTERNAL_HOST=lemmy-gamma:8561 - LEMMY_EXTERNAL_HOST=localhost:8561 @@ -147,7 +147,7 @@ services: # An instance with only an allowlist for beta lemmy-delta-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 environment: - LEMMY_INTERNAL_HOST=lemmy-delta:8571 - LEMMY_EXTERNAL_HOST=localhost:8571 @@ -187,7 +187,7 @@ services: # An instance who has a blocklist, with lemmy-alpha blocked lemmy-epsilon-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 environment: - LEMMY_INTERNAL_HOST=lemmy-epsilon:8581 - LEMMY_EXTERNAL_HOST=localhost:8581 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 5eafd099a..09d442542 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.8.5 + image: dessalines/lemmy:v0.8.6 ports: - "127.0.0.1:8536:8536" restart: always @@ -26,7 +26,7 @@ services: - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.5 + image: dessalines/lemmy-ui:v0.8.6 ports: - "1235:1234" restart: always diff --git a/docker/travis/docker_push.sh b/docker/travis/docker_push.sh index 68346af8e..d11a6f53d 100644 --- a/docker/travis/docker_push.sh +++ b/docker/travis/docker_push.sh @@ -1,5 +1,5 @@ #!/bin/sh echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin docker tag dessalines/lemmy:travis \ - dessalines/lemmy:v0.8.5 -docker push dessalines/lemmy:v0.8.5 + dessalines/lemmy:v0.8.6 +docker push dessalines/lemmy:v0.8.6 diff --git a/lemmy_api/src/version.rs b/lemmy_api/src/version.rs index dc149d5b6..34ef05704 100644 --- a/lemmy_api/src/version.rs +++ b/lemmy_api/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.8.5"; +pub const VERSION: &str = "v0.8.6"; From 8fe578c958c1d60512186f07b5c7d50b83fbb5e5 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 18 Nov 2020 16:18:53 -0600 Subject: [PATCH 18/25] Version v0.8.7 --- ansible/VERSION | 2 +- docker/dev/docker-compose.yml | 2 +- docker/federation/docker-compose.yml | 10 +++++----- docker/prod/docker-compose.yml | 4 ++-- docker/travis/docker_push.sh | 4 ++-- lemmy_api/src/version.rs | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ansible/VERSION b/ansible/VERSION index 85c3002dc..060b61642 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.8.6 +v0.8.7 diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index f825ecf58..27f76b1bb 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -16,7 +16,7 @@ services: - postgres - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 ports: - "1235:1234" restart: always diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index 5b78bcbff..e9bd6652f 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -29,7 +29,7 @@ services: - ./volumes/pictrs_alpha:/mnt lemmy-alpha-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 environment: - LEMMY_INTERNAL_HOST=lemmy-alpha:8541 - LEMMY_EXTERNAL_HOST=localhost:8541 @@ -68,7 +68,7 @@ services: - ./volumes/postgres_alpha:/var/lib/postgresql/data lemmy-beta-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 environment: - LEMMY_INTERNAL_HOST=lemmy-beta:8551 - LEMMY_EXTERNAL_HOST=localhost:8551 @@ -107,7 +107,7 @@ services: - ./volumes/postgres_beta:/var/lib/postgresql/data lemmy-gamma-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 environment: - LEMMY_INTERNAL_HOST=lemmy-gamma:8561 - LEMMY_EXTERNAL_HOST=localhost:8561 @@ -147,7 +147,7 @@ services: # An instance with only an allowlist for beta lemmy-delta-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 environment: - LEMMY_INTERNAL_HOST=lemmy-delta:8571 - LEMMY_EXTERNAL_HOST=localhost:8571 @@ -187,7 +187,7 @@ services: # An instance who has a blocklist, with lemmy-alpha blocked lemmy-epsilon-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 environment: - LEMMY_INTERNAL_HOST=lemmy-epsilon:8581 - LEMMY_EXTERNAL_HOST=localhost:8581 diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 09d442542..0e87c31b9 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.8.6 + image: dessalines/lemmy:v0.8.7 ports: - "127.0.0.1:8536:8536" restart: always @@ -26,7 +26,7 @@ services: - iframely lemmy-ui: - image: dessalines/lemmy-ui:v0.8.6 + image: dessalines/lemmy-ui:v0.8.7 ports: - "1235:1234" restart: always diff --git a/docker/travis/docker_push.sh b/docker/travis/docker_push.sh index d11a6f53d..217166618 100644 --- a/docker/travis/docker_push.sh +++ b/docker/travis/docker_push.sh @@ -1,5 +1,5 @@ #!/bin/sh echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin docker tag dessalines/lemmy:travis \ - dessalines/lemmy:v0.8.6 -docker push dessalines/lemmy:v0.8.6 + dessalines/lemmy:v0.8.7 +docker push dessalines/lemmy:v0.8.7 diff --git a/lemmy_api/src/version.rs b/lemmy_api/src/version.rs index 34ef05704..450fd957f 100644 --- a/lemmy_api/src/version.rs +++ b/lemmy_api/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.8.6"; +pub const VERSION: &str = "v0.8.7"; From 8b5289d5c75b6abd9cac1b6b2da7d7a20b71d107 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Thu, 19 Nov 2020 13:50:43 +0100 Subject: [PATCH 19/25] Add TODO about populating user outbox --- lemmy_apub/src/http/user.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lemmy_apub/src/http/user.rs b/lemmy_apub/src/http/user.rs index f983ab6f9..31ed5d85a 100644 --- a/lemmy_apub/src/http/user.rs +++ b/lemmy_apub/src/http/user.rs @@ -38,6 +38,7 @@ pub async fn get_apub_user_outbox( User_::read_from_name(&conn, &info.user_name) }) .await??; + // TODO: populate the user outbox let mut collection = OrderedCollection::new(); collection .set_many_items(Vec::::new()) From a2d80d8f2e05db34b645a9f1d67a8e4fd745fd9a Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Fri, 20 Nov 2020 15:11:47 +0100 Subject: [PATCH 20/25] Generate valid RSS feed (fixes #1274) --- src/lib.rs | 3 ++- src/routes/feeds.rs | 52 ++++++++++++++++++++++++++------------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e5754d46..e6ea900ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![recursion_limit = "512"] - +#[macro_use] +extern crate lazy_static; pub mod code_migrations; pub mod routes; diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index 303a824b1..a9454be4d 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -16,9 +16,15 @@ use lemmy_db::{ use lemmy_structs::blocking; use lemmy_utils::{settings::Settings, utils::markdown_to_html, LemmyError}; use lemmy_websocket::LemmyContext; -use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder}; +use rss::{ + extension::dublincore::DublinCoreExtensionBuilder, + ChannelBuilder, + GuidBuilder, + Item, + ItemBuilder, +}; use serde::Deserialize; -use std::str::FromStr; +use std::{collections::HashMap, str::FromStr}; use strum::ParseError; #[derive(Deserialize)] @@ -39,6 +45,17 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route("/feeds/all.xml", web::get().to(get_all_feed)); } +lazy_static! { + static ref RSS_NAMESPACE: HashMap = { + let mut h = HashMap::new(); + h.insert( + "dc".to_string(), + rss::extension::dublincore::NAMESPACE.to_string(), + ); + h + }; +} + async fn get_all_feed( info: web::Query, context: web::Data, @@ -70,6 +87,7 @@ fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result Result) -> Result, LemmyError> { for p in posts { let mut i = ItemBuilder::default(); + let mut dc_extension = DublinCoreExtensionBuilder::default(); i.title(p.name); - let author_url = format!( - "{}/u/{}", - Settings::get().get_protocol_and_hostname(), - p.creator_name - ); - i.author(format!( - "/u/{} (link)", - p.creator_name, author_url - )); + dc_extension.creators(vec![p.creator_actor_id.to_owned()]); let dt = DateTime::::from_utc(p.published, Utc); i.pub_date(dt.to_rfc2822()); @@ -345,16 +360,8 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { p.community_name ); - let category = CategoryBuilder::default() - .name(format!( - "/c/{} (link)", - p.community_name, community_url - )) - .domain(Settings::get().hostname.to_owned()) - .build() - .map_err(|e| anyhow!(e))?; - - i.categories(vec![category]); + // TODO: for category we should just put the name of the category, but then we would have + // to read each community from the db if let Some(url) = p.url { i.link(url); @@ -362,7 +369,7 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { // TODO add images let mut description = format!("submitted by {} to {}
{} points | {} comments", - author_url, + p.creator_actor_id, p.creator_name, community_url, p.community_name, @@ -377,6 +384,7 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { i.description(description); + i.dublin_core_ext(dc_extension.build().map_err(|e| anyhow!(e))?); items.push(i.build().map_err(|e| anyhow!(e))?); } From 8bdbda1db97401adb812864f62df040e62bd1879 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 23 Nov 2020 18:48:20 +0100 Subject: [PATCH 21/25] Remove clap dependency --- Cargo.lock | 54 +----------------------------------------- lemmy_utils/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5f984783..99fcd4e24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,15 +397,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "anyhow" version = "1.0.33" @@ -777,21 +768,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "clap" -version = "2.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" -dependencies = [ - "ansi_term", - "atty", - "bitflags 1.2.1", - "strsim 0.8.0", - "textwrap", - "unicode-width", - "vec_map", -] - [[package]] name = "cloudabi" version = "0.1.0" @@ -813,7 +789,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d325e4f2ffff52ca77d995bb675494d5364aa332499d5f7c7fbb28c25e671f6" dependencies = [ - "clap", "entities", "lazy_static", "pest", @@ -980,7 +955,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.9.3", + "strsim", "syn", ] @@ -3166,12 +3141,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.9.3" @@ -3230,15 +3199,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.21" @@ -3565,12 +3525,6 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.1" @@ -3649,12 +3603,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.1.5" diff --git a/lemmy_utils/Cargo.toml b/lemmy_utils/Cargo.toml index 1d5a6b0d9..b0a0f3e6b 100644 --- a/lemmy_utils/Cargo.toml +++ b/lemmy_utils/Cargo.toml @@ -19,7 +19,7 @@ percent-encoding = "2.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["preserve_order"]} thiserror = "1.0" -comrak = "0.8" +comrak = { version = "0.8", default-features = false } lazy_static = "1.3" openssl = "0.10" url = { version = "2.1", features = ["serde"] } From d75f621152808fd9812404b69a30105a09073b05 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Tue, 24 Nov 2020 18:53:43 +0100 Subject: [PATCH 22/25] Populate `content` with HTML, and `source` with markdown (ref #1220) --- Cargo.lock | 4 +- docs/src/contributing_apub_api_outline.md | 25 ++++++- lemmy_apub/Cargo.toml | 2 +- lemmy_apub/src/activities/receive/comment.rs | 10 +-- .../src/activities/receive/comment_undo.rs | 7 +- .../src/activities/receive/private_message.rs | 7 +- lemmy_apub/src/fetcher.rs | 7 +- lemmy_apub/src/lib.rs | 9 +-- lemmy_apub/src/objects/comment.rs | 31 +++++---- lemmy_apub/src/objects/community.rs | 26 ++++--- lemmy_apub/src/objects/mod.rs | 68 ++++++++++++++++++- lemmy_apub/src/objects/post.rs | 22 +++--- lemmy_apub/src/objects/private_message.rs | 32 +++++---- lemmy_apub/src/objects/user.rs | 24 ++++--- 14 files changed, 184 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99fcd4e24..9becd72bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "activitystreams" -version = "0.7.0-alpha.4" +version = "0.7.0-alpha.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261b423734cca2a170d7a76936f1f0f9e6c6fc297d36cfc5ea6aa15f9017f996" +checksum = "0b1afe32371e466a791ced0d6ef6e6b97822bb1a279ee4cc41c4324e61cd0b2b" dependencies = [ "chrono", "mime", diff --git a/docs/src/contributing_apub_api_outline.md b/docs/src/contributing_apub_api_outline.md index f5c7a5951..4fac3cf8d 100644 --- a/docs/src/contributing_apub_api_outline.md +++ b/docs/src/contributing_apub_api_outline.md @@ -64,6 +64,10 @@ Receives activities from user: `Follow`, `Undo/Follow`, `Create`, `Update`, `Lik "https://enterprise.lemmy.ml/u/riker" ], "content": "Welcome to the default community!", + "source": { + "content": "Welcome to the default community!", + "mediaType": "text/markdown" + }, "icon": { "type": "Image", "url": "https://enterprise.lemmy.ml/pictrs/image/Z8pFFb21cl.png" @@ -123,7 +127,11 @@ Sends and receives activities from/to other users: `Create/Note`, `Update/Note`, "type": "Person", "preferredUsername": "picard", "name": "Jean-Luc Picard", - "summary": "The user bio", + "content": "The user bio", + "source": { + "content": "The user bio", + "mediaType": "text/markdown" + }, "icon": { "type": "Image", "url": "https://enterprise.lemmy.ml/pictrs/image/DS3q0colRA.jpg" @@ -150,7 +158,7 @@ Sends and receives activities from/to other users: `Create/Note`, `Update/Note`, |---|---|---| | `preferredUsername` | yes | Name of the actor | | `name` | no | The user's displayname | -| `summary` | no | User bio | +| `content` | no | User bio | | `icon` | no | The user's avatar, shown next to the username | | `image` | no | The user's banner, shown on top of the profile | | `inbox` | no | ActivityPub inbox URL | @@ -174,6 +182,10 @@ A page with title, and optional URL and text content. The URL often leads to an "to": "https://voyager.lemmy.ml/c/main", "summary": "Test thumbnail 2", "content": "blub blub", + "source": { + "content": "blub blub", + "mediaType": "text/markdown" + }, "url": "https://voyager.lemmy.ml:/pictrs/image/fzGwCsq7BJ.jpg", "image": { "type": "Image", @@ -213,6 +225,10 @@ A reply to a post, or reply to another comment. Contains only text (including re "attributedTo": "https://enterprise.lemmy.ml/u/picard", "to": "https://enterprise.lemmy.ml/c/main", "content": "mmmk", + "source": { + "content": "mmmk", + "mediaType": "text/markdown" + }, "inReplyTo": [ "https://enterprise.lemmy.ml/post/38", "https://voyager.lemmy.ml/comment/73" @@ -243,6 +259,11 @@ A direct message from one user to another. Can not include additional users. Thr "attributedTo": "https://enterprise.lemmy.ml/u/picard", "to": "https://voyager.lemmy.ml/u/janeway", "content": "test", + "source": { + "content": "test", + "mediaType": "text/markdown" + }, + "mediaType": "text/markdown", "published": "2020-10-08T19:10:46.542820+00:00", "updated": "2020-10-08T20:13:52.547156+00:00" } diff --git a/lemmy_apub/Cargo.toml b/lemmy_apub/Cargo.toml index 50bf62f02..6dd68bc8e 100644 --- a/lemmy_apub/Cargo.toml +++ b/lemmy_apub/Cargo.toml @@ -14,7 +14,7 @@ lemmy_db = { path = "../lemmy_db" } lemmy_structs = { path = "../lemmy_structs" } lemmy_websocket = { path = "../lemmy_websocket" } diesel = "1.4" -activitystreams = "0.7.0-alpha.4" +activitystreams = "0.7.0-alpha.6" activitystreams-ext = "0.1.0-alpha.2" bcrypt = "0.8" chrono = { version = "0.4", features = ["serde"] } diff --git a/lemmy_apub/src/activities/receive/comment.rs b/lemmy_apub/src/activities/receive/comment.rs index d104d5e19..bf8de1ebd 100644 --- a/lemmy_apub/src/activities/receive/comment.rs +++ b/lemmy_apub/src/activities/receive/comment.rs @@ -3,11 +3,11 @@ use crate::{ fetcher::get_or_fetch_and_insert_comment, ActorType, FromApub, + NoteExt, }; use activitystreams::{ activity::{ActorAndObjectRefExt, Create, Dislike, Like, Remove, Update}, base::ExtendsExt, - object::Note, }; use anyhow::{anyhow, Context}; use lemmy_db::{ @@ -27,7 +27,7 @@ pub(crate) async fn receive_create_comment( request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(&create, context, request_counter).await?; - let note = Note::from_any_base(create.object().to_owned().one().context(location_info!())?)? + let note = NoteExt::from_any_base(create.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; let comment = @@ -83,7 +83,7 @@ pub(crate) async fn receive_update_comment( context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = Note::from_any_base(update.object().to_owned().one().context(location_info!())?)? + let note = NoteExt::from_any_base(update.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; let user = get_actor_as_user(&update, context, request_counter).await?; @@ -140,7 +140,7 @@ pub(crate) async fn receive_like_comment( context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)? + let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; let user = get_actor_as_user(&like, context, request_counter).await?; @@ -191,7 +191,7 @@ pub(crate) async fn receive_dislike_comment( context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - let note = Note::from_any_base( + let note = NoteExt::from_any_base( dislike .object() .to_owned() diff --git a/lemmy_apub/src/activities/receive/comment_undo.rs b/lemmy_apub/src/activities/receive/comment_undo.rs index 709e8481c..f44604cc1 100644 --- a/lemmy_apub/src/activities/receive/comment_undo.rs +++ b/lemmy_apub/src/activities/receive/comment_undo.rs @@ -2,8 +2,9 @@ use crate::{ activities::receive::get_actor_as_user, fetcher::get_or_fetch_and_insert_comment, FromApub, + NoteExt, }; -use activitystreams::{activity::*, object::Note, prelude::*}; +use activitystreams::{activity::*, prelude::*}; use anyhow::Context; use lemmy_db::{ comment::{Comment, CommentForm, CommentLike}, @@ -20,7 +21,7 @@ pub(crate) async fn receive_undo_like_comment( request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(like, context, request_counter).await?; - let note = Note::from_any_base(like.object().to_owned().one().context(location_info!())?)? + let note = NoteExt::from_any_base(like.object().to_owned().one().context(location_info!())?)? .context(location_info!())?; let comment = CommentForm::from_apub(¬e, context, None, request_counter).await?; @@ -64,7 +65,7 @@ pub(crate) async fn receive_undo_dislike_comment( request_counter: &mut i32, ) -> Result<(), LemmyError> { let user = get_actor_as_user(dislike, context, request_counter).await?; - let note = Note::from_any_base( + let note = NoteExt::from_any_base( dislike .object() .to_owned() diff --git a/lemmy_apub/src/activities/receive/private_message.rs b/lemmy_apub/src/activities/receive/private_message.rs index 31037d1f6..8f1c95b9d 100644 --- a/lemmy_apub/src/activities/receive/private_message.rs +++ b/lemmy_apub/src/activities/receive/private_message.rs @@ -4,11 +4,12 @@ use crate::{ fetcher::get_or_fetch_and_upsert_user, inbox::get_activity_to_and_cc, FromApub, + NoteExt, }; use activitystreams::{ activity::{ActorAndObjectRefExt, Create, Delete, Undo, Update}, base::{AsBase, ExtendsExt}, - object::{AsObject, Note}, + object::AsObject, public, }; use anyhow::{anyhow, Context}; @@ -30,7 +31,7 @@ pub(crate) async fn receive_create_private_message( ) -> Result<(), LemmyError> { check_private_message_activity_valid(&create, context, request_counter).await?; - let note = Note::from_any_base( + let note = NoteExt::from_any_base( create .object() .as_one() @@ -79,7 +80,7 @@ pub(crate) async fn receive_update_private_message( .as_one() .context(location_info!())? .to_owned(); - let note = Note::from_any_base(object)?.context(location_info!())?; + let note = NoteExt::from_any_base(object)?.context(location_info!())?; let private_message_form = PrivateMessageForm::from_apub(¬e, context, Some(expected_domain), request_counter).await?; diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index b4598ea37..ec44bce17 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -3,11 +3,12 @@ use crate::{ ActorType, FromApub, GroupExt, + NoteExt, PageExt, PersonExt, APUB_JSON_CONTENT_TYPE, }; -use activitystreams::{base::BaseExt, collection::OrderedCollection, object::Note, prelude::*}; +use activitystreams::{base::BaseExt, collection::OrderedCollection, prelude::*}; use anyhow::{anyhow, Context}; use chrono::NaiveDateTime; use diesel::result::Error::NotFound; @@ -91,7 +92,7 @@ enum SearchAcceptedObjects { Person(Box), Group(Box), Page(Box), - Comment(Box), + Comment(Box), } /// Attempt to parse the query as URL, and fetch an ActivityPub object from it. @@ -488,7 +489,7 @@ pub(crate) async fn get_or_fetch_and_insert_comment( comment_ap_id ); let comment = - fetch_remote_object::(context.client(), comment_ap_id, recursion_counter).await?; + fetch_remote_object::(context.client(), comment_ap_id, recursion_counter).await?; let comment_form = CommentForm::from_apub( &comment, context, diff --git a/lemmy_apub/src/lib.rs b/lemmy_apub/src/lib.rs index 4894b036f..2e3f7bfc6 100644 --- a/lemmy_apub/src/lib.rs +++ b/lemmy_apub/src/lib.rs @@ -18,7 +18,7 @@ use activitystreams::{ activity::Follow, actor::{ApActor, Group, Person}, base::AnyBase, - object::{Page, Tombstone}, + object::{ApObject, Note, Page, Tombstone}, }; use activitystreams_ext::{Ext1, Ext2}; use anyhow::{anyhow, Context}; @@ -31,11 +31,12 @@ use std::net::IpAddr; use url::{ParseError, Url}; /// Activitystreams type for community -type GroupExt = Ext2, GroupExtension, PublicKeyExtension>; +type GroupExt = Ext2>, GroupExtension, PublicKeyExtension>; /// Activitystreams type for user -type PersonExt = Ext1, PublicKeyExtension>; +type PersonExt = Ext1>, PublicKeyExtension>; /// Activitystreams type for post -type PageExt = Ext1; +type PageExt = Ext1, PageExtension>; +type NoteExt = ApObject; pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; diff --git a/lemmy_apub/src/objects/comment.rs b/lemmy_apub/src/objects/comment.rs index ca0b0e850..c3d17e6e0 100644 --- a/lemmy_apub/src/objects/comment.rs +++ b/lemmy_apub/src/objects/comment.rs @@ -4,12 +4,18 @@ use crate::{ get_or_fetch_and_insert_post, get_or_fetch_and_upsert_user, }, - objects::{check_object_domain, create_tombstone}, + objects::{ + check_object_domain, + create_tombstone, + get_source_markdown_value, + set_content_and_source, + }, FromApub, + NoteExt, ToApub, }; use activitystreams::{ - object::{kind::NoteType, Note, Tombstone}, + object::{kind::NoteType, ApObject, Note, Tombstone}, prelude::*, }; use anyhow::Context; @@ -32,10 +38,10 @@ use url::Url; #[async_trait::async_trait(?Send)] impl ToApub for Comment { - type ApubType = Note; + type ApubType = NoteExt; - async fn to_apub(&self, pool: &DbPool) -> Result { - let mut comment = Note::new(); + async fn to_apub(&self, pool: &DbPool) -> Result { + let mut comment = ApObject::new(Note::new()); let creator_id = self.creator_id; let creator = blocking(pool, move |conn| User_::read(conn, creator_id)).await??; @@ -63,9 +69,10 @@ impl ToApub for Comment { .set_published(convert_datetime(self.published)) .set_to(community.actor_id) .set_many_in_reply_tos(in_reply_to_vec) - .set_content(self.content.to_owned()) .set_attributed_to(creator.actor_id); + set_content_and_source(&mut comment, &self.content)?; + if let Some(u) = self.updated { comment.set_updated(convert_datetime(u)); } @@ -80,13 +87,13 @@ impl ToApub for Comment { #[async_trait::async_trait(?Send)] impl FromApub for CommentForm { - type ApubType = Note; + type ApubType = NoteExt; /// Converts a `Note` to `CommentForm`. /// /// If the parent community, post and comment(s) are not known locally, these are also fetched. async fn from_apub( - note: &Note, + note: &NoteExt, context: &LemmyContext, expected_domain: Option, request_counter: &mut i32, @@ -124,12 +131,8 @@ impl FromApub for CommentForm { } None => None, }; - let content = note - .content() - .context(location_info!())? - .as_single_xsd_string() - .context(location_info!())? - .to_string(); + + let content = get_source_markdown_value(note)?.context(location_info!())?; let content_slurs_removed = remove_slurs(&content); Ok(CommentForm { diff --git a/lemmy_apub/src/objects/community.rs b/lemmy_apub/src/objects/community.rs index d697c70b0..c5a614bad 100644 --- a/lemmy_apub/src/objects/community.rs +++ b/lemmy_apub/src/objects/community.rs @@ -1,7 +1,12 @@ use crate::{ extensions::group_extensions::GroupExtension, fetcher::get_or_fetch_and_upsert_user, - objects::{check_object_domain, create_tombstone}, + objects::{ + check_object_domain, + create_tombstone, + get_source_markdown_value, + set_content_and_source, + }, ActorType, FromApub, GroupExt, @@ -10,7 +15,7 @@ use crate::{ use activitystreams::{ actor::{kind::GroupType, ApActor, Endpoints, Group}, base::BaseExt, - object::{Image, Tombstone}, + object::{ApObject, Image, Tombstone}, prelude::*, }; use activitystreams_ext::Ext2; @@ -46,7 +51,7 @@ impl ToApub for Community { .await??; let moderators: Vec = moderators.into_iter().map(|m| m.user_actor_id).collect(); - let mut group = Group::new(); + let mut group = ApObject::new(Group::new()); group .set_context(activitystreams::context()) .set_id(Url::parse(&self.actor_id)?) @@ -58,9 +63,7 @@ impl ToApub for Community { group.set_updated(convert_datetime(u)); } if let Some(d) = self.description.to_owned() { - // TODO: this should be html, also add source field with raw markdown - // -> same for post.content and others - group.set_content(d); + set_content_and_source(&mut group, &d)?; } if let Some(icon_url) = &self.icon { @@ -138,14 +141,9 @@ impl FromApub for CommunityForm { .as_xsd_string() .context(location_info!())? .to_string(); - // TODO: should be parsed as html and tags like