mirror of https://github.com/LemmyNet/lemmy.git
reports: initial reports api commit
parent
08b8e9999b
commit
6d43202efb
|
@ -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]]
|
||||
|
|
|
@ -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::<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 => {
|
||||
|
@ -292,6 +302,18 @@ pub async fn match_websocket_operation(
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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"] }
|
||||
|
|
|
@ -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<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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<T> {
|
|||
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>;
|
||||
}
|
||||
|
|
|
@ -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<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)
|
||||
}
|
||||
}
|
|
@ -81,6 +81,19 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -370,6 +383,21 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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,
|
||||
|
|
|
@ -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"] }
|
|
@ -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;
|
||||
|
|
|
@ -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<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,
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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";
|
|
@ -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;
|
|
@ -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::<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(
|
||||
|
@ -79,7 +82,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
.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(
|
||||
|
@ -95,7 +100,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
)
|
||||
.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(
|
||||
|
|
Loading…
Reference in New Issue