reports: split post/comment out again, add some other fixes

This commit is contained in:
eiknat 2020-10-24 22:59:13 -04:00 committed by eiknat
parent e8e0890341
commit 2cd2a4df45
18 changed files with 710 additions and 601 deletions

View file

@ -5,11 +5,13 @@ use crate::{
get_user_from_jwt_opt, get_user_from_jwt_opt,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
collect_moderated_communities,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_apub::{ApubLikeableType, ApubObjectType}; use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_db::{ use lemmy_db::{
comment::*, comment::*,
comment_report::*,
comment_view::*, comment_view::*,
moderator::*, moderator::*,
post::*, post::*,
@ -20,6 +22,7 @@ use lemmy_db::{
ListingType, ListingType,
Saveable, Saveable,
SortType, SortType,
Reportable,
}; };
use lemmy_structs::{blocking, comment::*, send_local_notifs}; use lemmy_structs::{blocking, comment::*, send_local_notifs};
use lemmy_utils::{ use lemmy_utils::{
@ -29,7 +32,7 @@ use lemmy_utils::{
ConnectionId, ConnectionId,
LemmyError, LemmyError,
}; };
use lemmy_websocket::{messages::SendComment, LemmyContext, UserOperation}; use lemmy_websocket::{messages::{SendComment, SendUserRoomMessage}, LemmyContext, UserOperation};
use std::str::FromStr; use std::str::FromStr;
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
@ -682,3 +685,153 @@ impl Perform for GetComments {
Ok(GetCommentsResponse { comments }) Ok(GetCommentsResponse { comments })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for CreateCommentReport {
type Response = CreateCommentReportResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<CreateCommentReportResponse, 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 = 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<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<ResolveCommentReportResponse, LemmyError> {
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<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ListCommentReportsResponse, LemmyError> {
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)
}
}

View file

@ -1,14 +1,14 @@
use crate::claims::Claims; use crate::claims::Claims;
use actix_web::{web, web::Data}; use actix_web::{web, web::Data};
use lemmy_db::{ use lemmy_db::{
community::Community, community::{Community, CommunityModerator},
community_view::CommunityUserBanView, community_view::CommunityUserBanView,
post::Post, post::Post,
user::User_, user::User_,
Crud, Crud,
DbPool, 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_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize; use serde::Deserialize;
@ -19,7 +19,6 @@ pub mod claims;
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod post; pub mod post;
pub mod report;
pub mod site; pub mod site;
pub mod user; pub mod user;
pub mod version; 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<i32>,
pool: &DbPool,
) -> Result<Vec<i32>, 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<Option<String>>) -> Result<(), LemmyError> { pub(in crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
if let Some(Some(item)) = &item { if let Some(Some(item)) = &item {
if Url::parse(item).is_err() { if Url::parse(item).is_err() {
@ -182,6 +198,9 @@ pub async fn match_websocket_operation(
UserOperation::SaveUserSettings => { UserOperation::SaveUserSettings => {
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
} }
UserOperation::GetReportCount => {
do_websocket_operation::<GetReportCount>(context, id, op, data).await
}
// Private Message ops // Private Message ops
UserOperation::CreatePrivateMessage => { UserOperation::CreatePrivateMessage => {
@ -267,6 +286,15 @@ pub async fn match_websocket_operation(
do_websocket_operation::<CreatePostLike>(context, id, op, data).await do_websocket_operation::<CreatePostLike>(context, id, op, data).await
} }
UserOperation::SavePost => do_websocket_operation::<SavePost>(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 // Comment ops
UserOperation::CreateComment => { UserOperation::CreateComment => {
@ -293,19 +321,14 @@ pub async fn match_websocket_operation(
UserOperation::CreateCommentLike => { UserOperation::CreateCommentLike => {
do_websocket_operation::<CreateCommentLike>(context, id, op, data).await do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
} }
UserOperation::CreateCommentReport => {
// report ops do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
UserOperation::CreateReport => {
do_websocket_operation::<CreateReport>(context, id, op, data).await
} }
UserOperation::ListReports => { UserOperation::ListCommentReports => {
do_websocket_operation::<ListReports>(context, id, op, data).await do_websocket_operation::<ListCommentReports>(context, id, op, data).await
} }
UserOperation::ResolveReport => { UserOperation::ResolveCommentReport => {
do_websocket_operation::<ResolveReport>(context, id, op, data).await do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
}
UserOperation::GetReportCount => {
do_websocket_operation::<GetReportCount>(context, id, op, data).await
} }
} }
} }

View file

@ -5,6 +5,7 @@ use crate::{
get_user_from_jwt_opt, get_user_from_jwt_opt,
is_mod_or_admin, is_mod_or_admin,
Perform, Perform,
collect_moderated_communities,
}; };
use actix_web::web::Data; use actix_web::web::Data;
use lemmy_apub::{ApubLikeableType, ApubObjectType}; use lemmy_apub::{ApubLikeableType, ApubObjectType};
@ -14,6 +15,7 @@ use lemmy_db::{
moderator::*, moderator::*,
naive_now, naive_now,
post::*, post::*,
post_report::*,
post_view::*, post_view::*,
site_view::*, site_view::*,
Crud, Crud,
@ -21,6 +23,7 @@ use lemmy_db::{
ListingType, ListingType,
Saveable, Saveable,
SortType, SortType,
Reportable,
}; };
use lemmy_structs::{blocking, post::*}; use lemmy_structs::{blocking, post::*};
use lemmy_utils::{ use lemmy_utils::{
@ -32,7 +35,7 @@ use lemmy_utils::{
LemmyError, LemmyError,
}; };
use lemmy_websocket::{ use lemmy_websocket::{
messages::{GetPostUsersOnline, JoinPostRoom, SendPost}, messages::{GetPostUsersOnline, JoinPostRoom, SendPost, SendUserRoomMessage},
LemmyContext, LemmyContext,
UserOperation, UserOperation,
}; };
@ -741,3 +744,154 @@ impl Perform for PostJoin {
Ok(PostJoinResponse { joined: true }) Ok(PostJoinResponse { joined: true })
} }
} }
#[async_trait::async_trait(?Send)]
impl Perform for CreatePostReport {
type Response = CreatePostReportResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<CreatePostReportResponse, 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 = 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<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<ResolvePostReportResponse, LemmyError> {
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<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ListPostReportsResponse, LemmyError> {
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)
}
}

View file

@ -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<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<CreateReportResponse, LemmyError> {
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<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 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<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ListReportsResponse, LemmyError> {
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<LemmyContext>,
websocket_id: Option<ConnectionId>,
) -> Result<ResolveReportResponse, LemmyError> {
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<LemmyContext>,
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<LemmyContext>,
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<LemmyContext>,
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<LemmyContext>,
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(())
}

View file

@ -6,6 +6,7 @@ use crate::{
get_user_from_jwt_opt, get_user_from_jwt_opt,
is_admin, is_admin,
Perform, Perform,
collect_moderated_communities
}; };
use actix_web::web::Data; use actix_web::web::Data;
use anyhow::Context; use anyhow::Context;
@ -15,6 +16,7 @@ use chrono::Duration;
use lemmy_apub::ApubObjectType; use lemmy_apub::ApubObjectType;
use lemmy_db::{ use lemmy_db::{
comment::*, comment::*,
comment_report::CommentReportView,
comment_view::*, comment_view::*,
community::*, community::*,
community_view::*, community_view::*,
@ -23,6 +25,7 @@ use lemmy_db::{
naive_now, naive_now,
password_reset_request::*, password_reset_request::*,
post::*, post::*,
post_report::PostReportView,
post_view::*, post_view::*,
private_message::*, private_message::*,
private_message_view::*, private_message_view::*,
@ -1294,3 +1297,54 @@ impl Perform for UserJoin {
Ok(UserJoinResponse { joined: true }) Ok(UserJoinResponse { joined: 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 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)
}
}

View file

@ -77,7 +77,7 @@ impl Reportable<CommentReportForm> for CommentReport {
} }
#[derive( #[derive(
Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize, Clone,
)] )]
#[table_name = "comment_report_view"] #[table_name = "comment_report_view"]
pub struct CommentReportView { pub struct CommentReportView {
@ -100,7 +100,7 @@ pub struct CommentReportView {
pub struct CommentReportQueryBuilder<'a> { pub struct CommentReportQueryBuilder<'a> {
conn: &'a PgConnection, conn: &'a PgConnection,
query: comment_report_view::BoxedQuery<'a, Pg>, query: comment_report_view::BoxedQuery<'a, Pg>,
for_community_id: Option<i32>, for_community_ids: Option<Vec<i32>>,
page: Option<i64>, page: Option<i64>,
limit: Option<i64>, limit: Option<i64>,
resolved: Option<bool>, resolved: Option<bool>,
@ -113,6 +113,14 @@ impl CommentReportView {
.find(report_id) .find(report_id)
.first::<Self>(conn) .first::<Self>(conn)
} }
pub fn get_report_count(conn: &PgConnection, community_ids: &Vec<i32>) -> Result<i32, Error> {
use super::comment_report::comment_report_view::dsl::*;
comment_report_view
.filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
.select(sql::<sql_types::Integer>("COUNT(*)"))
.first::<i32>(conn)
}
} }
impl<'a> CommentReportQueryBuilder<'a> { impl<'a> CommentReportQueryBuilder<'a> {
@ -124,15 +132,15 @@ impl<'a> CommentReportQueryBuilder<'a> {
CommentReportQueryBuilder { CommentReportQueryBuilder {
conn, conn,
query, query,
for_community_id: None, for_community_ids: None,
page: None, page: None,
limit: None, limit: None,
resolved: Some(false), resolved: Some(false),
} }
} }
pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self { pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
self.for_community_id = community_id.get_optional(); self.for_community_ids = community_ids.get_optional();
self self
} }
@ -156,8 +164,8 @@ impl<'a> CommentReportQueryBuilder<'a> {
let mut query = self.query; let mut query = self.query;
if let Some(comm_id) = self.for_community_id { if let Some(comm_ids) = self.for_community_ids {
query = query.filter(community_id.eq(comm_id)); query = query.filter(community_id.eq_any(comm_ids));
} }
if let Some(resolved_flag) = self.resolved { if let Some(resolved_flag) = self.resolved {
@ -172,21 +180,4 @@ impl<'a> CommentReportQueryBuilder<'a> {
.offset(offset) .offset(offset)
.load::<CommentReportView>(self.conn) .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)
}
} }

View file

@ -224,6 +224,14 @@ impl CommunityModerator {
use crate::schema::community_moderator::dsl::*; use crate::schema::community_moderator::dsl::*;
diesel::delete(community_moderator.filter(community_id.eq(for_community_id))).execute(conn) 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<Vec<i32>, Error> {
use crate::schema::community_moderator::dsl::*;
community_moderator
.filter(user_id.eq(for_user_id))
.select(community_id)
.load::<i32>(conn)
}
} }
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug)] #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]

View file

@ -173,12 +173,6 @@ pub enum SearchType {
Url, Url,
} }
#[derive(EnumString, ToString, Debug, Serialize, Deserialize)]
pub enum ReportType {
Comment,
Post,
}
pub fn fuzzy_search(q: &str) -> String { pub fn fuzzy_search(q: &str) -> String {
let replaced = q.replace(" ", "%"); let replaced = q.replace(" ", "%");
format!("%{}%", replaced) format!("%{}%", replaced)

View file

@ -110,12 +110,20 @@ impl PostReportView {
.find(report_id) .find(report_id)
.first::<Self>(conn) .first::<Self>(conn)
} }
pub fn get_report_count(conn: &PgConnection, community_ids: &Vec<i32>) -> Result<i32, Error> {
use super::post_report::post_report_view::dsl::*;
post_report_view
.filter(resolved.eq(false).and(community_id.eq_any(community_ids)))
.select(sql::<sql_types::Integer>("COUNT(*)"))
.first::<i32>(conn)
}
} }
pub struct PostReportQueryBuilder<'a> { pub struct PostReportQueryBuilder<'a> {
conn: &'a PgConnection, conn: &'a PgConnection,
query: post_report_view::BoxedQuery<'a, Pg>, query: post_report_view::BoxedQuery<'a, Pg>,
for_community_id: Option<i32>, for_community_ids: Option<Vec<i32>>,
page: Option<i64>, page: Option<i64>,
limit: Option<i64>, limit: Option<i64>,
resolved: Option<bool>, resolved: Option<bool>,
@ -130,15 +138,15 @@ impl<'a> PostReportQueryBuilder<'a> {
PostReportQueryBuilder { PostReportQueryBuilder {
conn, conn,
query, query,
for_community_id: None, for_community_ids: None,
page: None, page: None,
limit: None, limit: None,
resolved: Some(false), resolved: Some(false),
} }
} }
pub fn community_id<T: MaybeOptional<i32>>(mut self, community_id: T) -> Self { pub fn community_ids<T: MaybeOptional<Vec<i32>>>(mut self, community_ids: T) -> Self {
self.for_community_id = community_id.get_optional(); self.for_community_ids = community_ids.get_optional();
self self
} }
@ -162,8 +170,8 @@ impl<'a> PostReportQueryBuilder<'a> {
let mut query = self.query; let mut query = self.query;
if let Some(comm_id) = self.for_community_id { if let Some(comm_ids) = self.for_community_ids {
query = query.filter(community_id.eq(comm_id)); query = query.filter(community_id.eq_any(comm_ids));
} }
if let Some(resolved_flag) = self.resolved { if let Some(resolved_flag) = self.resolved {
@ -178,19 +186,4 @@ impl<'a> PostReportQueryBuilder<'a> {
.offset(offset) .offset(offset)
.load::<PostReportView>(self.conn) .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)
}
} }

View file

@ -1,4 +1,7 @@
use lemmy_db::comment_view::CommentView; use lemmy_db::{
comment_view::CommentView,
comment_report::CommentReportView,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Deserialize)] #[derive(Deserialize)]
@ -76,3 +79,41 @@ pub struct GetComments {
pub struct GetCommentsResponse { pub struct GetCommentsResponse {
pub comments: Vec<CommentView>, pub comments: Vec<CommentView>,
} }
#[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<i64>,
pub limit: Option<i64>,
pub community: Option<i32>,
pub auth: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ListCommentReportsResponse {
pub comments: Vec<CommentReportView>,
}

View file

@ -1,7 +1,6 @@
pub mod comment; pub mod comment;
pub mod community; pub mod community;
pub mod post; pub mod post;
pub mod report;
pub mod site; pub mod site;
pub mod user; pub mod user;
pub mod websocket; pub mod websocket;

View file

@ -1,6 +1,7 @@
use lemmy_db::{ use lemmy_db::{
comment_view::CommentView, comment_view::CommentView,
community_view::{CommunityModeratorView, CommunityView}, community_view::{CommunityModeratorView, CommunityView},
post_report::PostReportView,
post_view::PostView, post_view::PostView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -113,3 +114,41 @@ pub struct PostJoin {
pub struct PostJoinResponse { pub struct PostJoinResponse {
pub joined: bool, 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<i64>,
pub limit: Option<i64>,
pub community: Option<i32>,
pub auth: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ListPostReportsResponse {
pub posts: Vec<PostReportView>,
}

View file

@ -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<i64>,
pub limit: Option<i64>,
pub community: i32,
pub auth: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ListReportsResponse {
pub posts: Vec<PostReportView>,
pub comments: Vec<CommentReportView>,
}
#[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,
}

View file

@ -237,3 +237,16 @@ pub struct UserJoin {
pub struct UserJoinResponse { pub struct UserJoinResponse {
pub joined: bool, pub joined: bool,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct GetReportCount {
pub community: Option<i32>,
pub auth: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct GetReportCountResponse {
pub community: Option<i32>,
pub comment_reports: i32,
pub post_reports: i32,
}

View file

@ -97,6 +97,9 @@ pub enum UserOperation {
MarkCommentAsRead, MarkCommentAsRead,
SaveComment, SaveComment,
CreateCommentLike, CreateCommentLike,
CreateCommentReport,
ResolveCommentReport,
ListCommentReports,
GetPosts, GetPosts,
CreatePostLike, CreatePostLike,
EditPost, EditPost,
@ -105,9 +108,9 @@ pub enum UserOperation {
LockPost, LockPost,
StickyPost, StickyPost,
SavePost, SavePost,
CreateReport, CreatePostReport,
ResolveReport, ResolvePostReport,
ListReports, ListPostReports,
GetReportCount, GetReportCount,
EditCommunity, EditCommunity,
DeleteCommunity, DeleteCommunity,

View file

@ -1,7 +1,7 @@
use actix_web::{error::ErrorBadRequest, *}; use actix_web::{error::ErrorBadRequest, *};
use lemmy_api::Perform; use lemmy_api::Perform;
use lemmy_rate_limit::RateLimit; 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 lemmy_websocket::LemmyContext;
use serde::Deserialize; use serde::Deserialize;
@ -57,7 +57,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.route("/transfer", web::post().to(route_post::<TransferCommunity>)) .route("/transfer", web::post().to(route_post::<TransferCommunity>))
.route("/ban_user", web::post().to(route_post::<BanFromCommunity>)) .route("/ban_user", web::post().to(route_post::<BanFromCommunity>))
.route("/mod", web::post().to(route_post::<AddModToCommunity>)) .route("/mod", web::post().to(route_post::<AddModToCommunity>))
.route("/join", web::post().to(route_post::<CommunityJoin>)) .route("/join", web::post().to(route_post::<CommunityJoin>)),
) )
// Post // Post
.service( .service(
@ -80,6 +80,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.route("/like", web::post().to(route_post::<CreatePostLike>)) .route("/like", web::post().to(route_post::<CreatePostLike>))
.route("/save", web::put().to(route_post::<SavePost>)) .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::post().to(route_post::<CreatePostReport>))
.route("/report/resolve", web::put().to(route_post::<ResolvePostReport>))
.route("/report/list", web::get().to(route_get::<ListPostReports>))
) )
// Comment // Comment
.service( .service(
@ -96,6 +99,9 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.route("/like", web::post().to(route_post::<CreateCommentLike>)) .route("/like", web::post().to(route_post::<CreateCommentLike>))
.route("/save", web::put().to(route_post::<SaveComment>)) .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::post().to(route_post::<CreateCommentReport>))
.route("/report/resolve", web::put().to(route_post::<ResolveCommentReport>))
.route("/report/list", web::get().to(route_get::<ListCommentReports>))
) )
// Private Message // Private Message
.service( .service(
@ -163,6 +169,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
.route( .route(
"/save_user_settings", "/save_user_settings",
web::put().to(route_post::<SaveUserSettings>), web::put().to(route_post::<SaveUserSettings>),
)
.route(
"/report_count",
web::get().to(route_get::<GetReportCount>)
), ),
) )
// Admin Actions // Admin Actions
@ -170,15 +180,6 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
web::resource("/admin/add") web::resource("/admin/add")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route(web::post().to(route_post::<AddAdmin>)), .route(web::post().to(route_post::<AddAdmin>)),
)
// Reports
.service(
web::scope("/report")
.wrap(rate_limit.message())
.route("", web::get().to(route_get::<GetReportCount>))
.route("",web::post().to(route_post::<CreateReport>))
.route("/resolve",web::put().to(route_post::<ResolveReport>))
.route("/list", web::get().to(route_get::<ListReports>))
), ),
); );
} }