"pq-sys",
"r2d2",
"serde_json",
+ "uuid 0.6.5",
]
[[package]]
"strum",
"strum_macros",
"url",
+ "uuid 0.6.5",
]
[[package]]
"log",
"serde 1.0.117",
"serde_json",
+ "uuid 0.6.5",
]
[[package]]
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;
pub mod comment;
pub mod community;
pub mod post;
+pub mod report;
pub mod site;
pub mod user;
pub mod version;
do_websocket_operation::<CreatePostLike>(context, id, op, data).await
}
UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
+ UserOperation::CreatePostReport => {
+ do_websocket_operation::<CreatePostReport>(context, id, op, data).await
+ }
+ UserOperation::ListPostReports => {
+ do_websocket_operation::<ListPostReports>(context, id, op, data).await
+ }
+ UserOperation::ResolvePostReport => {
+ do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
+ }
// Comment ops
UserOperation::CreateComment => {
UserOperation::CreateCommentLike => {
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
}
+ UserOperation::CreateCommentReport => {
+ do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
+ },
+ UserOperation::ListCommentReports => {
+ do_websocket_operation::<ListCommentReports>(context, id, op, data).await
+ },
+ UserOperation::ResolveCommentReport => {
+ do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
+ }
+ UserOperation::GetReportCount => {
+ do_websocket_operation::<GetReportCount>(context, id, op, data).await
+ }
}
}
--- /dev/null
+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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<CommentReportResponse, LemmyError> {
+ 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<String> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<PostReportResponse, LemmyError> {
+ 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<String> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<GetReportCountResponse, LemmyError> {
+ 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<i32> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<ListCommentReportResponse, LemmyError> {
+ 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<i32> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<ListPostReportResponse, LemmyError> {
+ 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<i32> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<ResolveCommentReportResponse, LemmyError> {
+ 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<i32> = 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<LemmyContext>,
+ _websocket_id: Option<ConnectionId>,
+ ) -> Result<ResolvePostReportResponse, LemmyError> {
+ 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<i32> = 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,
+ })
+ }
+}
[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"]}
url = { version = "2.1", features = ["serde"] }
lazy_static = "1.3"
regex = "1.3"
+uuid = { version = "0.6.5", features = ["serde", "v4"] }
--- /dev/null
+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<Text>,
+ 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<String>,
+ 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<chrono::NaiveDateTime>,
+ pub reason: Option<String>,
+ pub resolved: Option<bool>,
+ pub user_id: i32,
+ pub comment_id: i32,
+ pub comment_text: String,
+ pub comment_time: chrono::NaiveDateTime,
+}
+
+impl Reportable<CommentReportForm> for CommentReport {
+ fn report(conn: &PgConnection, comment_report_form: &CommentReportForm) -> Result<Self, Error> {
+ use crate::schema::comment_report::dsl::*;
+ insert_into(comment_report)
+ .values(comment_report_form)
+ .get_result::<Self>(conn)
+ }
+
+ fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result<usize, Error> {
+ 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<String>,
+ 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<i32>,
+ page: Option<i64>,
+ limit: Option<i64>,
+ resolved: Option<bool>,
+}
+
+impl CommentReportView {
+ pub fn read(conn: &PgConnection, report_id: &uuid::Uuid) -> Result<Self, Error> {
+ use super::comment_report::comment_report_view::dsl::*;
+ comment_report_view
+ .filter(id.eq(report_id))
+ .first::<Self>(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<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+ self.for_community_id = community_id.get_optional();
+ self
+ }
+
+ pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+ self.page = page.get_optional();
+ self
+ }
+
+ pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+ self.limit = limit.get_optional();
+ self
+ }
+
+ pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+ self.resolved = resolved.get_optional();
+ self
+ }
+
+ pub fn list(self) -> Result<Vec<CommentReportView>, 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::<CommentReportView>(self.conn)
+ }
+
+ pub fn count(self) -> Result<usize, 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));
+ }
+
+ query.execute(self.conn)
+ }
+}
+
+
pub mod activity;
pub mod category;
pub mod comment;
+pub mod comment_report;
pub mod comment_view;
pub mod community;
pub mod community_view;
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;
Self: Sized;
}
+pub trait Reportable<T> {
+ fn report(conn: &PgConnection, form: &T) -> Result<Self, Error>
+ where
+ Self: Sized;
+ fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result<usize, Error>
+ where
+ Self: Sized;
+}
+
pub trait MaybeOptional<T> {
fn get_optional(self) -> Option<T>;
}
--- /dev/null
+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<Text>,
+ resolved -> Bool,
+ user_id -> Int4,
+ post_id -> Int4,
+ post_name -> Varchar,
+ post_url -> Nullable<Text>,
+ post_body -> Nullable<Text>,
+ 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<String>,
+ pub resolved: bool,
+ pub user_id: i32,
+ pub post_id: i32,
+ pub post_name: String,
+ pub post_url: Option<String>,
+ pub post_body: Option<String>,
+ pub post_time: chrono::NaiveDateTime,
+}
+
+#[derive(Insertable, AsChangeset, Clone)]
+#[table_name = "post_report"]
+pub struct PostReportForm {
+ pub time: Option<chrono::NaiveDateTime>,
+ pub reason: Option<String>,
+ pub resolved: Option<bool>,
+ pub user_id: i32,
+ pub post_id: i32,
+ pub post_name: String,
+ pub post_url: Option<String>,
+ pub post_body: Option<String>,
+ pub post_time: chrono::NaiveDateTime,
+}
+
+impl Reportable<PostReportForm> for PostReport {
+ fn report(conn: &PgConnection, post_report_form: &PostReportForm) -> Result<Self, Error> {
+ use crate::schema::post_report::dsl::*;
+ insert_into(post_report)
+ .values(post_report_form)
+ .get_result::<Self>(conn)
+ }
+
+ fn resolve(conn: &PgConnection, report_id: &uuid::Uuid) -> Result<usize, Error> {
+ 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<String>,
+ pub resolved: bool,
+ pub user_id: i32,
+ pub post_id: i32,
+ pub post_name: String,
+ pub post_url: Option<String>,
+ pub post_body: Option<String>,
+ 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<Self, Error> {
+ use super::post_report::post_report_view::dsl::*;
+ post_report_view
+ .filter(id.eq(report_id))
+ .first::<Self>(conn)
+ }
+}
+
+pub struct PostReportQueryBuilder<'a> {
+ conn: &'a PgConnection,
+ query: post_report_view::BoxedQuery<'a, Pg>,
+ for_community_id: Option<i32>,
+ page: Option<i64>,
+ limit: Option<i64>,
+ resolved: Option<bool>,
+}
+
+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<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self {
+ self.for_community_id = community_id.get_optional();
+ self
+ }
+
+ pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
+ self.page = page.get_optional();
+ self
+ }
+
+ pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
+ self.limit = limit.get_optional();
+ self
+ }
+
+ pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self {
+ self.resolved = resolved.get_optional();
+ self
+ }
+
+ pub fn list(self) -> Result<Vec<PostReportView>, 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::<PostReportView>(self.conn)
+ }
+
+ pub fn count(self) -> Result<usize, 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));
+ }
+
+ query.execute(self.conn)
+ }
+}
}
}
+table! {
+ comment_report (id) {
+ id -> Uuid,
+ time -> Timestamp,
+ reason -> Nullable<Text>,
+ resolved -> Bool,
+ user_id -> Int4,
+ comment_id -> Int4,
+ comment_text -> Text,
+ comment_time -> Timestamp,
+ }
+}
+
table! {
comment_saved (id) {
id -> Int4,
}
}
+table! {
+ post_report (id) {
+ id -> Uuid,
+ time -> Timestamp,
+ reason -> Nullable<Text>,
+ resolved -> Bool,
+ user_id -> Int4,
+ post_id -> Int4,
+ post_name -> Varchar,
+ post_url -> Nullable<Text>,
+ post_body -> Nullable<Text>,
+ post_time -> Timestamp,
+ }
+}
+
table! {
post_saved (id) {
id -> Int4,
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));
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));
comment,
comment_aggregates_fast,
comment_like,
+ comment_report,
comment_saved,
community,
community_aggregates_fast,
post_aggregates_fast,
post_like,
post_read,
+ post_report,
post_saved,
private_message,
site,
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
pub mod comment;
pub mod community;
pub mod post;
+pub mod report;
pub mod site;
pub mod user;
pub mod websocket;
--- /dev/null
+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<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<String>,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct PostReportResponse {
+ pub success: bool,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListCommentReports {
+ pub page: Option<i64>,
+ pub limit: Option<i64>,
+ pub community: i32,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListCommentReportResponse {
+ pub reports: Vec<CommentReportView>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListPostReports {
+ pub page: Option<i64>,
+ pub limit: Option<i64>,
+ pub community: i32,
+ pub auth: String,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct ListPostReportResponse {
+ pub reports: Vec<PostReportView>,
+}
+
+#[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,
+}
MarkCommentAsRead,
SaveComment,
CreateCommentLike,
+ CreateCommentReport,
+ ListCommentReports,
+ ResolveCommentReport,
GetPosts,
CreatePostLike,
EditPost,
LockPost,
StickyPost,
SavePost,
+ CreatePostReport,
+ ListPostReports,
+ ResolvePostReport,
EditCommunity,
DeleteCommunity,
RemoveCommunity,
GetUserMentions,
MarkUserMentionAsRead,
GetModlog,
+ GetReportCount,
BanFromCommunity,
AddModToCommunity,
CreateSite,
--- /dev/null
+drop view comment_report_view;
+drop view post_report_view;
+drop table comment_report;
+drop table post_report;
+drop extension "uuid-ossp";
--- /dev/null
+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;
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;
.route("/transfer", web::post().to(route_post::<TransferCommunity>))
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
.route("/mod", web::post().to(route_post::<AddModToCommunity>))
- .route("/join", web::post().to(route_post::<CommunityJoin>)),
+ .route("/join", web::post().to(route_post::<CommunityJoin>))
+ .route("/comment_reports",web::get().to(route_get::<ListCommentReports>))
+ .route("/post_reports", web::get().to(route_get::<ListPostReports>))
+ .route("/reports", web::get().to(route_get::<GetReportCount>)),
)
// Post
.service(
.route("/list", web::get().to(route_get::<GetPosts>))
.route("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>))
- .route("/join", web::post().to(route_post::<PostJoin>)),
+ .route("/join", web::post().to(route_post::<PostJoin>))
+ .route("/report", web::put().to(route_post::<CreatePostReport>))
+ .route("/resolve_report",web::post().to(route_post::<ResolvePostReport>)),
)
// Comment
.service(
)
.route("/like", web::post().to(route_post::<CreateCommentLike>))
.route("/save", web::put().to(route_post::<SaveComment>))
- .route("/list", web::get().to(route_get::<GetComments>)),
+ .route("/list", web::get().to(route_get::<GetComments>))
+ .route("/report", web::put().to(route_post::<CreateCommentReport>))
+ .route("/resolve_report",web::post().to(route_post::<ResolveCommentReport>)),
)
// Private Message
.service(