Implement reports for private messages (#2433)
* Implement reports for private messages * finish private message report view + test * implement api for pm reports * merge list report api calls into one, move report count to site * fix compile error * Revert "merge list report api calls into one, move report count to site" This reverts commit 3bf3b06a705c6bcf2bf20d07e2819b81298790f3. * add websocket messages for pm report created/resolved * remove private_message_report_view * add joinable private_message_report -> person_alias_1 * Address review comments
This commit is contained in:
parent
09246a20fb
commit
004efd5d94
32 changed files with 734 additions and 90 deletions
|
@ -1,4 +1,4 @@
|
||||||
use crate::Perform;
|
use crate::{check_report_reason, Perform};
|
||||||
use activitypub_federation::core::object_id::ObjectId;
|
use activitypub_federation::core::object_id::ObjectId;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
@ -29,14 +29,8 @@ impl Perform for CreateCommentReport {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
// check size of report and check for whitespace
|
let reason = self.reason.trim();
|
||||||
let reason = data.reason.trim();
|
check_report_reason(reason, context)?;
|
||||||
if reason.is_empty() {
|
|
||||||
return Err(LemmyError::from_message("report_reason_required"));
|
|
||||||
}
|
|
||||||
if reason.chars().count() > 1000 {
|
|
||||||
return Err(LemmyError::from_message("report_too_long"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
|
@ -51,7 +45,7 @@ impl Perform for CreateCommentReport {
|
||||||
creator_id: person_id,
|
creator_id: person_id,
|
||||||
comment_id,
|
comment_id,
|
||||||
original_comment_text: comment_view.comment.content,
|
original_comment_text: comment_view.comment.content,
|
||||||
reason: data.reason.to_owned(),
|
reason: reason.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let report = blocking(context.pool(), move |conn| {
|
let report = blocking(context.pool(), move |conn| {
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
use actix_web::{web, web::Data};
|
use actix_web::{web, web::Data};
|
||||||
use captcha::Captcha;
|
use captcha::Captcha;
|
||||||
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
use lemmy_api_common::{
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
comment::*,
|
||||||
|
community::*,
|
||||||
|
person::*,
|
||||||
|
post::*,
|
||||||
|
private_message::*,
|
||||||
|
site::*,
|
||||||
|
websocket::*,
|
||||||
|
};
|
||||||
|
use lemmy_utils::{error::LemmyError, utils::check_slurs, ConnectionId};
|
||||||
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
|
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -12,6 +20,7 @@ mod local_user;
|
||||||
mod post;
|
mod post;
|
||||||
mod post_report;
|
mod post_report;
|
||||||
mod private_message;
|
mod private_message;
|
||||||
|
mod private_message_report;
|
||||||
mod site;
|
mod site;
|
||||||
mod websocket;
|
mod websocket;
|
||||||
|
|
||||||
|
@ -98,6 +107,15 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::MarkPrivateMessageAsRead => {
|
UserOperation::MarkPrivateMessageAsRead => {
|
||||||
do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
|
do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
|
||||||
}
|
}
|
||||||
|
UserOperation::CreatePrivateMessageReport => {
|
||||||
|
do_websocket_operation::<CreatePrivateMessageReport>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
UserOperation::ResolvePrivateMessageReport => {
|
||||||
|
do_websocket_operation::<ResolvePrivateMessageReport>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
UserOperation::ListPrivateMessageReports => {
|
||||||
|
do_websocket_operation::<ListPrivateMessageReports>(context, id, op, data).await
|
||||||
|
}
|
||||||
|
|
||||||
// Site ops
|
// Site ops
|
||||||
UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
|
UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
|
||||||
|
@ -208,6 +226,18 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
|
||||||
base64::encode(concat_letters)
|
base64::encode(concat_letters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check size of report and remove whitespace
|
||||||
|
pub(crate) fn check_report_reason(reason: &str, context: &LemmyContext) -> Result<(), LemmyError> {
|
||||||
|
check_slurs(reason, &context.settings().slur_regex())?;
|
||||||
|
if reason.is_empty() {
|
||||||
|
return Err(LemmyError::from_message("report_reason_required"));
|
||||||
|
}
|
||||||
|
if reason.chars().count() > 1000 {
|
||||||
|
return Err(LemmyError::from_message("report_too_long"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use lemmy_api_common::utils::check_validator_time;
|
use lemmy_api_common::utils::check_validator_time;
|
||||||
|
|
|
@ -4,7 +4,7 @@ use lemmy_api_common::{
|
||||||
person::{GetReportCount, GetReportCountResponse},
|
person::{GetReportCount, GetReportCountResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_db_views::structs::{CommentReportView, PostReportView};
|
use lemmy_db_views::structs::{CommentReportView, PostReportView, PrivateMessageReportView};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
@ -36,10 +36,22 @@ impl Perform for GetReportCount {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
let private_message_reports = if admin && community_id.is_none() {
|
||||||
|
Some(
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageReportView::get_report_count(conn)
|
||||||
|
})
|
||||||
|
.await??,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let res = GetReportCountResponse {
|
let res = GetReportCountResponse {
|
||||||
community_id,
|
community_id,
|
||||||
comment_reports,
|
comment_reports,
|
||||||
post_reports,
|
post_reports,
|
||||||
|
private_message_reports,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::Perform;
|
use crate::{check_report_reason, Perform};
|
||||||
use activitypub_federation::core::object_id::ObjectId;
|
use activitypub_federation::core::object_id::ObjectId;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
|
@ -29,14 +29,8 @@ impl Perform for CreatePostReport {
|
||||||
let local_user_view =
|
let local_user_view =
|
||||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
// check size of report and check for whitespace
|
let reason = self.reason.trim();
|
||||||
let reason = data.reason.trim();
|
check_report_reason(reason, context)?;
|
||||||
if reason.is_empty() {
|
|
||||||
return Err(LemmyError::from_message("report_reason_required"));
|
|
||||||
}
|
|
||||||
if reason.chars().count() > 1000 {
|
|
||||||
return Err(LemmyError::from_message("report_too_long"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let person_id = local_user_view.person.id;
|
let person_id = local_user_view.person.id;
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
|
@ -53,7 +47,7 @@ impl Perform for CreatePostReport {
|
||||||
original_post_name: post_view.post.name,
|
original_post_name: post_view.post.name,
|
||||||
original_post_url: post_view.post.url,
|
original_post_url: post_view.post.url,
|
||||||
original_post_body: post_view.post.body,
|
original_post_body: post_view.post.body,
|
||||||
reason: data.reason.to_owned(),
|
reason: reason.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let report = blocking(context.pool(), move |conn| {
|
let report = blocking(context.pool(), move |conn| {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::Perform;
|
use crate::Perform;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
private_message::{MarkPrivateMessageAsRead, PrivateMessageResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
|
use lemmy_db_schema::{source::private_message::PrivateMessage, traits::Crud};
|
||||||
|
|
75
crates/api/src/private_message_report/create.rs
Normal file
75
crates/api/src/private_message_report/create.rs
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
use crate::{check_report_reason, Perform};
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
private_message::{CreatePrivateMessageReport, PrivateMessageReportResponse},
|
||||||
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::CommunityId,
|
||||||
|
source::{
|
||||||
|
private_message::PrivateMessage,
|
||||||
|
private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
|
||||||
|
},
|
||||||
|
traits::{Crud, Reportable},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::PrivateMessageReportView;
|
||||||
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for CreatePrivateMessageReport {
|
||||||
|
type Response = PrivateMessageReportResponse;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context, websocket_id))]
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&self.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
let reason = self.reason.trim();
|
||||||
|
check_report_reason(reason, context)?;
|
||||||
|
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let private_message_id = self.private_message_id;
|
||||||
|
let private_message = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessage::read(conn, private_message_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let report_form = PrivateMessageReportForm {
|
||||||
|
creator_id: person_id,
|
||||||
|
private_message_id,
|
||||||
|
original_pm_text: private_message.content,
|
||||||
|
reason: reason.to_owned(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let report = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageReport::report(conn, &report_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_report"))?;
|
||||||
|
|
||||||
|
let private_message_report_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageReportView::read(conn, report.id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageReportResponse {
|
||||||
|
private_message_report_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::CreatePrivateMessageReport,
|
||||||
|
response: res.clone(),
|
||||||
|
community_id: CommunityId(0),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: consider federating this
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
46
crates/api/src/private_message_report/list.rs
Normal file
46
crates/api/src/private_message_report/list.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
private_message::{ListPrivateMessageReports, ListPrivateMessageReportsResponse},
|
||||||
|
utils::{blocking, get_local_user_view_from_jwt, is_admin},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::private_message_report_view::PrivateMessageReportQuery;
|
||||||
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ListPrivateMessageReports {
|
||||||
|
type Response = ListPrivateMessageReportsResponse;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context, _websocket_id))]
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
_websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&self.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let unresolved_only = self.unresolved_only;
|
||||||
|
let page = self.page;
|
||||||
|
let limit = self.limit;
|
||||||
|
let private_message_reports = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageReportQuery::builder()
|
||||||
|
.conn(conn)
|
||||||
|
.unresolved_only(unresolved_only)
|
||||||
|
.page(page)
|
||||||
|
.limit(limit)
|
||||||
|
.build()
|
||||||
|
.list()
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = ListPrivateMessageReportsResponse {
|
||||||
|
private_message_reports,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
3
crates/api/src/private_message_report/mod.rs
Normal file
3
crates/api/src/private_message_report/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
mod create;
|
||||||
|
mod list;
|
||||||
|
mod resolve;
|
64
crates/api/src/private_message_report/resolve.rs
Normal file
64
crates/api/src/private_message_report/resolve.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use crate::Perform;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use lemmy_api_common::{
|
||||||
|
private_message::{PrivateMessageReportResponse, ResolvePrivateMessageReport},
|
||||||
|
utils::{blocking, get_local_user_view_from_jwt, is_admin},
|
||||||
|
};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::CommunityId,
|
||||||
|
source::private_message_report::PrivateMessageReport,
|
||||||
|
traits::Reportable,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::PrivateMessageReportView;
|
||||||
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
|
use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
|
||||||
|
|
||||||
|
#[async_trait::async_trait(?Send)]
|
||||||
|
impl Perform for ResolvePrivateMessageReport {
|
||||||
|
type Response = PrivateMessageReportResponse;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context, websocket_id))]
|
||||||
|
async fn perform(
|
||||||
|
&self,
|
||||||
|
context: &Data<LemmyContext>,
|
||||||
|
websocket_id: Option<ConnectionId>,
|
||||||
|
) -> Result<Self::Response, LemmyError> {
|
||||||
|
let local_user_view =
|
||||||
|
get_local_user_view_from_jwt(&self.auth, context.pool(), context.secret()).await?;
|
||||||
|
|
||||||
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
|
let resolved = self.resolved;
|
||||||
|
let report_id = self.report_id;
|
||||||
|
let person_id = local_user_view.person.id;
|
||||||
|
let resolve_fn = move |conn: &'_ _| {
|
||||||
|
if resolved {
|
||||||
|
PrivateMessageReport::resolve(conn, report_id, person_id)
|
||||||
|
} else {
|
||||||
|
PrivateMessageReport::unresolve(conn, report_id, person_id)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
blocking(context.pool(), resolve_fn)
|
||||||
|
.await?
|
||||||
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_resolve_report"))?;
|
||||||
|
|
||||||
|
let private_message_report_view = blocking(context.pool(), move |conn| {
|
||||||
|
PrivateMessageReportView::read(conn, report_id)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let res = PrivateMessageReportResponse {
|
||||||
|
private_message_report_view,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.chat_server().do_send(SendModRoomMessage {
|
||||||
|
op: UserOperation::ResolvePrivateMessageReport,
|
||||||
|
response: res.clone(),
|
||||||
|
community_id: CommunityId(0),
|
||||||
|
websocket_id,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
|
pub mod private_message;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod sensitive;
|
pub mod sensitive;
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_views::structs::{CommentView, PostView, PrivateMessageView};
|
use lemmy_db_schema::{
|
||||||
|
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
|
||||||
|
CommentSortType,
|
||||||
|
SortType,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::{CommentView, PostView};
|
||||||
use lemmy_db_views_actor::structs::{
|
use lemmy_db_views_actor::structs::{
|
||||||
CommentReplyView,
|
CommentReplyView,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
|
@ -13,18 +18,6 @@ pub struct Login {
|
||||||
pub username_or_email: Sensitive<String>,
|
pub username_or_email: Sensitive<String>,
|
||||||
pub password: Sensitive<String>,
|
pub password: Sensitive<String>,
|
||||||
}
|
}
|
||||||
use lemmy_db_schema::{
|
|
||||||
newtypes::{
|
|
||||||
CommentReplyId,
|
|
||||||
CommunityId,
|
|
||||||
LanguageId,
|
|
||||||
PersonId,
|
|
||||||
PersonMentionId,
|
|
||||||
PrivateMessageId,
|
|
||||||
},
|
|
||||||
CommentSortType,
|
|
||||||
SortType,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct Register {
|
pub struct Register {
|
||||||
|
@ -249,52 +242,6 @@ pub struct PasswordChangeAfterReset {
|
||||||
pub password_verify: Sensitive<String>,
|
pub password_verify: Sensitive<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct CreatePrivateMessage {
|
|
||||||
pub content: String,
|
|
||||||
pub recipient_id: PersonId,
|
|
||||||
pub auth: Sensitive<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct EditPrivateMessage {
|
|
||||||
pub private_message_id: PrivateMessageId,
|
|
||||||
pub content: String,
|
|
||||||
pub auth: Sensitive<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct DeletePrivateMessage {
|
|
||||||
pub private_message_id: PrivateMessageId,
|
|
||||||
pub deleted: bool,
|
|
||||||
pub auth: Sensitive<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct MarkPrivateMessageAsRead {
|
|
||||||
pub private_message_id: PrivateMessageId,
|
|
||||||
pub read: bool,
|
|
||||||
pub auth: Sensitive<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
|
||||||
pub struct GetPrivateMessages {
|
|
||||||
pub unread_only: Option<bool>,
|
|
||||||
pub page: Option<i64>,
|
|
||||||
pub limit: Option<i64>,
|
|
||||||
pub auth: Sensitive<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PrivateMessagesResponse {
|
|
||||||
pub private_messages: Vec<PrivateMessageView>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct PrivateMessageResponse {
|
|
||||||
pub private_message_view: PrivateMessageView,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct GetReportCount {
|
pub struct GetReportCount {
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
|
@ -306,6 +253,7 @@ pub struct GetReportCountResponse {
|
||||||
pub community_id: Option<CommunityId>,
|
pub community_id: Option<CommunityId>,
|
||||||
pub comment_reports: i64,
|
pub comment_reports: i64,
|
||||||
pub post_reports: i64,
|
pub post_reports: i64,
|
||||||
|
pub private_message_reports: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
|
83
crates/api_common/src/private_message.rs
Normal file
83
crates/api_common/src/private_message.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use crate::sensitive::Sensitive;
|
||||||
|
use lemmy_db_schema::newtypes::{PersonId, PrivateMessageId, PrivateMessageReportId};
|
||||||
|
use lemmy_db_views::structs::{PrivateMessageReportView, PrivateMessageView};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct CreatePrivateMessage {
|
||||||
|
pub content: String,
|
||||||
|
pub recipient_id: PersonId,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct EditPrivateMessage {
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub content: String,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct DeletePrivateMessage {
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub deleted: bool,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct MarkPrivateMessageAsRead {
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub read: bool,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct GetPrivateMessages {
|
||||||
|
pub unread_only: Option<bool>,
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessagesResponse {
|
||||||
|
pub private_messages: Vec<PrivateMessageView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessageResponse {
|
||||||
|
pub private_message_view: PrivateMessageView,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct CreatePrivateMessageReport {
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub reason: String,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessageReportResponse {
|
||||||
|
pub private_message_report_view: PrivateMessageReportView,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct ResolvePrivateMessageReport {
|
||||||
|
pub report_id: PrivateMessageReportId,
|
||||||
|
pub resolved: bool,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
pub struct ListPrivateMessageReports {
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
/// Only shows the unresolved reports
|
||||||
|
pub unresolved_only: Option<bool>,
|
||||||
|
pub auth: Sensitive<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct ListPrivateMessageReportsResponse {
|
||||||
|
pub private_message_reports: Vec<PrivateMessageReportView>,
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::{web, web::Data};
|
use actix_web::{web, web::Data};
|
||||||
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*};
|
use lemmy_api_common::{comment::*, community::*, person::*, post::*, private_message::*, site::*};
|
||||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||||
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperationCrud};
|
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperationCrud};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{CreatePrivateMessage, PrivateMessageResponse},
|
private_message::{CreatePrivateMessage, PrivateMessageResponse},
|
||||||
utils::{
|
utils::{
|
||||||
blocking,
|
blocking,
|
||||||
check_person_block,
|
check_person_block,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{DeletePrivateMessage, PrivateMessageResponse},
|
private_message::{DeletePrivateMessage, PrivateMessageResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_apub::activities::deletion::send_apub_delete_private_message;
|
use lemmy_apub::activities::deletion::send_apub_delete_private_message;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{GetPrivateMessages, PrivateMessagesResponse},
|
private_message::{GetPrivateMessages, PrivateMessagesResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::traits::DeleteableOrRemoveable;
|
use lemmy_db_schema::traits::DeleteableOrRemoveable;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::PerformCrud;
|
use crate::PerformCrud;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
person::{EditPrivateMessage, PrivateMessageResponse},
|
private_message::{EditPrivateMessage, PrivateMessageResponse},
|
||||||
utils::{blocking, get_local_user_view_from_jwt},
|
utils::{blocking, get_local_user_view_from_jwt},
|
||||||
};
|
};
|
||||||
use lemmy_apub::protocol::activities::{
|
use lemmy_apub::protocol::activities::{
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub mod person_mention;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
pub mod private_message_report;
|
||||||
pub mod registration_application;
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
62
crates/db_schema/src/impls/private_message_report.rs
Normal file
62
crates/db_schema/src/impls/private_message_report.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use crate::{
|
||||||
|
newtypes::{PersonId, PrivateMessageReportId},
|
||||||
|
source::private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
|
||||||
|
traits::Reportable,
|
||||||
|
utils::naive_now,
|
||||||
|
};
|
||||||
|
use diesel::{dsl::*, result::Error, *};
|
||||||
|
|
||||||
|
impl Reportable for PrivateMessageReport {
|
||||||
|
type Form = PrivateMessageReportForm;
|
||||||
|
type IdType = PrivateMessageReportId;
|
||||||
|
/// creates a comment report and returns it
|
||||||
|
///
|
||||||
|
/// * `conn` - the postgres connection
|
||||||
|
/// * `comment_report_form` - the filled CommentReportForm to insert
|
||||||
|
fn report(conn: &PgConnection, pm_report_form: &PrivateMessageReportForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::private_message_report::dsl::*;
|
||||||
|
insert_into(private_message_report)
|
||||||
|
.values(pm_report_form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// resolve a pm report
|
||||||
|
///
|
||||||
|
/// * `conn` - the postgres connection
|
||||||
|
/// * `report_id` - the id of the report to resolve
|
||||||
|
/// * `by_resolver_id` - the id of the user resolving the report
|
||||||
|
fn resolve(
|
||||||
|
conn: &PgConnection,
|
||||||
|
report_id: Self::IdType,
|
||||||
|
by_resolver_id: PersonId,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
use crate::schema::private_message_report::dsl::*;
|
||||||
|
update(private_message_report.find(report_id))
|
||||||
|
.set((
|
||||||
|
resolved.eq(true),
|
||||||
|
resolver_id.eq(by_resolver_id),
|
||||||
|
updated.eq(naive_now()),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// unresolve a comment report
|
||||||
|
///
|
||||||
|
/// * `conn` - the postgres connection
|
||||||
|
/// * `report_id` - the id of the report to unresolve
|
||||||
|
/// * `by_resolver_id` - the id of the user unresolving the report
|
||||||
|
fn unresolve(
|
||||||
|
conn: &PgConnection,
|
||||||
|
report_id: Self::IdType,
|
||||||
|
by_resolver_id: PersonId,
|
||||||
|
) -> Result<usize, Error> {
|
||||||
|
use crate::schema::private_message_report::dsl::*;
|
||||||
|
update(private_message_report.find(report_id))
|
||||||
|
.set((
|
||||||
|
resolved.eq(false),
|
||||||
|
resolver_id.eq(by_resolver_id),
|
||||||
|
updated.eq(naive_now()),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,6 +69,10 @@ pub struct CommentReportId(i32);
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
pub struct PostReportId(i32);
|
pub struct PostReportId(i32);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
|
pub struct PrivateMessageReportId(i32);
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
|
||||||
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
#[cfg_attr(feature = "full", derive(DieselNewType))]
|
||||||
pub struct LanguageId(pub i32);
|
pub struct LanguageId(pub i32);
|
||||||
|
|
|
@ -454,6 +454,20 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
private_message_report (id) {
|
||||||
|
id -> Int4,
|
||||||
|
creator_id -> Int4,
|
||||||
|
private_message_id -> Int4,
|
||||||
|
original_pm_text -> Text,
|
||||||
|
reason -> Text,
|
||||||
|
resolved -> Bool,
|
||||||
|
resolver_id -> Nullable<Int4>,
|
||||||
|
published -> Timestamp,
|
||||||
|
updated -> Nullable<Timestamp>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
site (id) {
|
site (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -667,9 +681,11 @@ joinable!(person_mention -> person_alias_1 (recipient_id));
|
||||||
joinable!(comment_reply -> person_alias_1 (recipient_id));
|
joinable!(comment_reply -> person_alias_1 (recipient_id));
|
||||||
joinable!(post -> person_alias_1 (creator_id));
|
joinable!(post -> person_alias_1 (creator_id));
|
||||||
joinable!(comment -> person_alias_1 (creator_id));
|
joinable!(comment -> person_alias_1 (creator_id));
|
||||||
|
joinable!(private_message_report -> person_alias_1 (resolver_id));
|
||||||
|
|
||||||
joinable!(post_report -> person_alias_2 (resolver_id));
|
joinable!(post_report -> person_alias_2 (resolver_id));
|
||||||
joinable!(comment_report -> person_alias_2 (resolver_id));
|
joinable!(comment_report -> person_alias_2 (resolver_id));
|
||||||
|
joinable!(private_message_report -> person_alias_2 (resolver_id));
|
||||||
|
|
||||||
joinable!(person_block -> person (person_id));
|
joinable!(person_block -> person (person_id));
|
||||||
joinable!(person_block -> person_alias_1 (target_id));
|
joinable!(person_block -> person_alias_1 (target_id));
|
||||||
|
@ -733,6 +749,7 @@ joinable!(post -> language (language_id));
|
||||||
joinable!(comment -> language (language_id));
|
joinable!(comment -> language (language_id));
|
||||||
joinable!(local_user_language -> language (language_id));
|
joinable!(local_user_language -> language (language_id));
|
||||||
joinable!(local_user_language -> local_user (local_user_id));
|
joinable!(local_user_language -> local_user (local_user_id));
|
||||||
|
joinable!(private_message_report -> private_message (private_message_id));
|
||||||
|
|
||||||
joinable!(admin_purge_comment -> person (admin_person_id));
|
joinable!(admin_purge_comment -> person (admin_person_id));
|
||||||
joinable!(admin_purge_comment -> post (post_id));
|
joinable!(admin_purge_comment -> post (post_id));
|
||||||
|
@ -780,6 +797,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
post_report,
|
post_report,
|
||||||
post_saved,
|
post_saved,
|
||||||
private_message,
|
private_message,
|
||||||
|
private_message_report,
|
||||||
site,
|
site,
|
||||||
site_aggregates,
|
site_aggregates,
|
||||||
person_alias_1,
|
person_alias_1,
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub mod person_mention;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_report;
|
pub mod post_report;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
pub mod private_message_report;
|
||||||
pub mod registration_application;
|
pub mod registration_application;
|
||||||
pub mod secret;
|
pub mod secret;
|
||||||
pub mod site;
|
pub mod site;
|
||||||
|
|
34
crates/db_schema/src/source/private_message_report.rs
Normal file
34
crates/db_schema/src/source/private_message_report.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
use crate::newtypes::{PersonId, PrivateMessageId, PrivateMessageReportId};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
use crate::schema::private_message_report;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "full",
|
||||||
|
belongs_to(crate::source::private_message::PrivateMessage)
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "full", table_name = "private_message_report")]
|
||||||
|
pub struct PrivateMessageReport {
|
||||||
|
pub id: PrivateMessageReportId,
|
||||||
|
pub creator_id: PersonId,
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub original_pm_text: String,
|
||||||
|
pub reason: String,
|
||||||
|
pub resolved: bool,
|
||||||
|
pub resolver_id: Option<PersonId>,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
pub updated: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", table_name = "private_message_report")]
|
||||||
|
pub struct PrivateMessageReportForm {
|
||||||
|
pub creator_id: PersonId,
|
||||||
|
pub private_message_id: PrivateMessageId,
|
||||||
|
pub original_pm_text: String,
|
||||||
|
pub reason: String,
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ pub mod post_report_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod post_view;
|
pub mod post_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
#[cfg(feature = "full")]
|
||||||
|
pub mod private_message_report_view;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod private_message_view;
|
pub mod private_message_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod registration_application_view;
|
pub mod registration_application_view;
|
||||||
|
|
223
crates/db_views/src/private_message_report_view.rs
Normal file
223
crates/db_views/src/private_message_report_view.rs
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
use crate::structs::PrivateMessageReportView;
|
||||||
|
use diesel::{result::Error, *};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
newtypes::PrivateMessageReportId,
|
||||||
|
schema::{person, person_alias_1, person_alias_2, private_message, private_message_report},
|
||||||
|
source::{
|
||||||
|
person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
|
||||||
|
private_message::PrivateMessage,
|
||||||
|
private_message_report::PrivateMessageReport,
|
||||||
|
},
|
||||||
|
traits::{ToSafe, ViewToVec},
|
||||||
|
utils::limit_and_offset,
|
||||||
|
};
|
||||||
|
use typed_builder::TypedBuilder;
|
||||||
|
|
||||||
|
type PrivateMessageReportViewTuple = (
|
||||||
|
PrivateMessageReport,
|
||||||
|
PrivateMessage,
|
||||||
|
PersonSafe,
|
||||||
|
PersonSafeAlias1,
|
||||||
|
Option<PersonSafeAlias2>,
|
||||||
|
);
|
||||||
|
|
||||||
|
impl PrivateMessageReportView {
|
||||||
|
/// returns the PrivateMessageReportView for the provided report_id
|
||||||
|
///
|
||||||
|
/// * `report_id` - the report id to obtain
|
||||||
|
pub fn read(conn: &PgConnection, report_id: PrivateMessageReportId) -> Result<Self, Error> {
|
||||||
|
let (private_message_report, private_message, private_message_creator, creator, resolver) =
|
||||||
|
private_message_report::table
|
||||||
|
.find(report_id)
|
||||||
|
.inner_join(private_message::table)
|
||||||
|
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
|
||||||
|
.inner_join(
|
||||||
|
person_alias_1::table.on(private_message_report::creator_id.eq(person_alias_1::id)),
|
||||||
|
)
|
||||||
|
.left_join(
|
||||||
|
person_alias_2::table
|
||||||
|
.on(private_message_report::resolver_id.eq(person_alias_2::id.nullable())),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
private_message_report::all_columns,
|
||||||
|
private_message::all_columns,
|
||||||
|
Person::safe_columns_tuple(),
|
||||||
|
PersonAlias1::safe_columns_tuple(),
|
||||||
|
PersonAlias2::safe_columns_tuple().nullable(),
|
||||||
|
))
|
||||||
|
.first::<PrivateMessageReportViewTuple>(conn)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
private_message_report,
|
||||||
|
private_message,
|
||||||
|
private_message_creator,
|
||||||
|
creator,
|
||||||
|
resolver,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the current unresolved post report count for the communities you mod
|
||||||
|
pub fn get_report_count(conn: &PgConnection) -> Result<i64, Error> {
|
||||||
|
use diesel::dsl::*;
|
||||||
|
|
||||||
|
private_message_report::table
|
||||||
|
.inner_join(private_message::table)
|
||||||
|
.filter(private_message_report::resolved.eq(false))
|
||||||
|
.into_boxed()
|
||||||
|
.select(count(private_message_report::id))
|
||||||
|
.first::<i64>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(TypedBuilder)]
|
||||||
|
#[builder(field_defaults(default))]
|
||||||
|
pub struct PrivateMessageReportQuery<'a> {
|
||||||
|
#[builder(!default)]
|
||||||
|
conn: &'a PgConnection,
|
||||||
|
page: Option<i64>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
unresolved_only: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PrivateMessageReportQuery<'a> {
|
||||||
|
pub fn list(self) -> Result<Vec<PrivateMessageReportView>, Error> {
|
||||||
|
let mut query = private_message_report::table
|
||||||
|
.inner_join(private_message::table)
|
||||||
|
.inner_join(person::table.on(private_message::creator_id.eq(person::id)))
|
||||||
|
.inner_join(
|
||||||
|
person_alias_1::table.on(private_message_report::creator_id.eq(person_alias_1::id)),
|
||||||
|
)
|
||||||
|
.left_join(
|
||||||
|
person_alias_2::table
|
||||||
|
.on(private_message_report::resolver_id.eq(person_alias_2::id.nullable())),
|
||||||
|
)
|
||||||
|
.select((
|
||||||
|
private_message_report::all_columns,
|
||||||
|
private_message::all_columns,
|
||||||
|
Person::safe_columns_tuple(),
|
||||||
|
PersonAlias1::safe_columns_tuple(),
|
||||||
|
PersonAlias2::safe_columns_tuple().nullable(),
|
||||||
|
))
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
if self.unresolved_only.unwrap_or(true) {
|
||||||
|
query = query.filter(private_message_report::resolved.eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (limit, offset) = limit_and_offset(self.page, self.limit)?;
|
||||||
|
|
||||||
|
query = query
|
||||||
|
.order_by(private_message::published.desc())
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset);
|
||||||
|
|
||||||
|
let res = query.load::<PrivateMessageReportViewTuple>(self.conn)?;
|
||||||
|
|
||||||
|
Ok(PrivateMessageReportView::from_tuple_to_vec(res))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewToVec for PrivateMessageReportView {
|
||||||
|
type DbTuple = PrivateMessageReportViewTuple;
|
||||||
|
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||||
|
items
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| Self {
|
||||||
|
private_message_report: a.0,
|
||||||
|
private_message: a.1,
|
||||||
|
private_message_creator: a.2,
|
||||||
|
creator: a.3,
|
||||||
|
resolver: a.4,
|
||||||
|
})
|
||||||
|
.collect::<Vec<Self>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::private_message_report_view::PrivateMessageReportQuery;
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
person::{Person, PersonForm},
|
||||||
|
private_message::{PrivateMessage, PrivateMessageForm},
|
||||||
|
private_message_report::{PrivateMessageReport, PrivateMessageReportForm},
|
||||||
|
},
|
||||||
|
traits::{Crud, Reportable},
|
||||||
|
utils::establish_unpooled_connection,
|
||||||
|
};
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_crud() {
|
||||||
|
let conn = establish_unpooled_connection();
|
||||||
|
|
||||||
|
let new_person_1 = PersonForm {
|
||||||
|
name: "timmy_mrv".into(),
|
||||||
|
public_key: Some("pubkey".to_string()),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
let inserted_timmy = Person::create(&conn, &new_person_1).unwrap();
|
||||||
|
|
||||||
|
let new_person_2 = PersonForm {
|
||||||
|
name: "jessica_mrv".into(),
|
||||||
|
public_key: Some("pubkey".to_string()),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
let inserted_jessica = Person::create(&conn, &new_person_2).unwrap();
|
||||||
|
|
||||||
|
// timmy sends private message to jessica
|
||||||
|
let pm_form = PrivateMessageForm {
|
||||||
|
creator_id: inserted_timmy.id,
|
||||||
|
recipient_id: inserted_jessica.id,
|
||||||
|
content: "something offensive".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let pm = PrivateMessage::create(&conn, &pm_form).unwrap();
|
||||||
|
|
||||||
|
// jessica reports private message
|
||||||
|
let pm_report_form = PrivateMessageReportForm {
|
||||||
|
creator_id: inserted_jessica.id,
|
||||||
|
original_pm_text: pm.content.clone(),
|
||||||
|
private_message_id: pm.id,
|
||||||
|
reason: "its offensive".to_string(),
|
||||||
|
};
|
||||||
|
let pm_report = PrivateMessageReport::report(&conn, &pm_report_form).unwrap();
|
||||||
|
|
||||||
|
let reports = PrivateMessageReportQuery::builder()
|
||||||
|
.conn(&conn)
|
||||||
|
.build()
|
||||||
|
.list()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(1, reports.len());
|
||||||
|
assert!(!reports[0].private_message_report.resolved);
|
||||||
|
assert_eq!(inserted_timmy.name, reports[0].private_message_creator.name);
|
||||||
|
assert_eq!(inserted_jessica.name, reports[0].creator.name);
|
||||||
|
assert_eq!(pm_report.reason, reports[0].private_message_report.reason);
|
||||||
|
assert_eq!(pm.content, reports[0].private_message.content);
|
||||||
|
|
||||||
|
let new_person_3 = PersonForm {
|
||||||
|
name: "admin_mrv".into(),
|
||||||
|
public_key: Some("pubkey".to_string()),
|
||||||
|
..PersonForm::default()
|
||||||
|
};
|
||||||
|
let inserted_admin = Person::create(&conn, &new_person_3).unwrap();
|
||||||
|
|
||||||
|
// admin resolves the report (after taking appropriate action)
|
||||||
|
PrivateMessageReport::resolve(&conn, pm_report.id, inserted_admin.id).unwrap();
|
||||||
|
|
||||||
|
let reports = PrivateMessageReportQuery::builder()
|
||||||
|
.conn(&conn)
|
||||||
|
.unresolved_only(Some(false))
|
||||||
|
.build()
|
||||||
|
.list()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(1, reports.len());
|
||||||
|
assert!(reports[0].private_message_report.resolved);
|
||||||
|
assert!(reports[0].resolver.is_some());
|
||||||
|
assert_eq!(
|
||||||
|
inserted_admin.name,
|
||||||
|
reports[0].resolver.as_ref().unwrap().name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@ use lemmy_db_schema::{
|
||||||
post::Post,
|
post::Post,
|
||||||
post_report::PostReport,
|
post_report::PostReport,
|
||||||
private_message::PrivateMessage,
|
private_message::PrivateMessage,
|
||||||
|
private_message_report::PrivateMessageReport,
|
||||||
registration_application::RegistrationApplication,
|
registration_application::RegistrationApplication,
|
||||||
site::Site,
|
site::Site,
|
||||||
},
|
},
|
||||||
|
@ -94,6 +95,15 @@ pub struct PrivateMessageView {
|
||||||
pub recipient: PersonSafeAlias1,
|
pub recipient: PersonSafeAlias1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct PrivateMessageReportView {
|
||||||
|
pub private_message_report: PrivateMessageReport,
|
||||||
|
pub private_message: PrivateMessage,
|
||||||
|
pub private_message_creator: PersonSafe,
|
||||||
|
pub creator: PersonSafeAlias1,
|
||||||
|
pub resolver: Option<PersonSafeAlias2>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
pub struct RegistrationApplicationView {
|
pub struct RegistrationApplicationView {
|
||||||
pub registration_application: RegistrationApplication,
|
pub registration_application: RegistrationApplication,
|
||||||
|
|
|
@ -134,6 +134,9 @@ pub enum UserOperation {
|
||||||
PasswordReset,
|
PasswordReset,
|
||||||
PasswordChange,
|
PasswordChange,
|
||||||
MarkPrivateMessageAsRead,
|
MarkPrivateMessageAsRead,
|
||||||
|
CreatePrivateMessageReport,
|
||||||
|
ResolvePrivateMessageReport,
|
||||||
|
ListPrivateMessageReports,
|
||||||
UserJoin,
|
UserJoin,
|
||||||
PostJoin,
|
PostJoin,
|
||||||
CommunityJoin,
|
CommunityJoin,
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub struct SendUserRoomMessage<OP: ToString, Response> {
|
||||||
pub websocket_id: Option<ConnectionId>,
|
pub websocket_id: Option<ConnectionId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send message to all users viewing the given community.
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct SendCommunityRoomMessage<OP: ToString, Response> {
|
pub struct SendCommunityRoomMessage<OP: ToString, Response> {
|
||||||
|
@ -64,6 +65,7 @@ pub struct SendCommunityRoomMessage<OP: ToString, Response> {
|
||||||
pub websocket_id: Option<ConnectionId>,
|
pub websocket_id: Option<ConnectionId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Send message to mods of a given community. Set community_id = 0 to send to site admins.
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "()")]
|
#[rtype(result = "()")]
|
||||||
pub struct SendModRoomMessage<Response> {
|
pub struct SendModRoomMessage<Response> {
|
||||||
|
|
|
@ -6,8 +6,8 @@ use crate::{
|
||||||
use lemmy_api_common::{
|
use lemmy_api_common::{
|
||||||
comment::CommentResponse,
|
comment::CommentResponse,
|
||||||
community::CommunityResponse,
|
community::CommunityResponse,
|
||||||
person::PrivateMessageResponse,
|
|
||||||
post::PostResponse,
|
post::PostResponse,
|
||||||
|
private_message::PrivateMessageResponse,
|
||||||
utils::{blocking, check_person_block, get_interface_language, send_email_to_user},
|
utils::{blocking, check_person_block, get_interface_language, send_email_to_user},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
|
1
migrations/2022-09-07-114618_pm-reports/down.sql
Normal file
1
migrations/2022-09-07-114618_pm-reports/down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
drop table private_message_report;
|
12
migrations/2022-09-07-114618_pm-reports/up.sql
Normal file
12
migrations/2022-09-07-114618_pm-reports/up.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
create table private_message_report (
|
||||||
|
id serial primary key,
|
||||||
|
creator_id int references person on update cascade on delete cascade not null, -- user reporting comment
|
||||||
|
private_message_id int references private_message on update cascade on delete cascade not null, -- comment being reported
|
||||||
|
original_pm_text text not null,
|
||||||
|
reason text not null,
|
||||||
|
resolved bool not null default false,
|
||||||
|
resolver_id int references person on update cascade on delete cascade, -- user resolving report
|
||||||
|
published timestamp not null default now(),
|
||||||
|
updated timestamp null,
|
||||||
|
unique(private_message_id, creator_id) -- users should only be able to report a pm once
|
||||||
|
);
|
|
@ -1,6 +1,14 @@
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use lemmy_api::Perform;
|
use lemmy_api::Perform;
|
||||||
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
|
use lemmy_api_common::{
|
||||||
|
comment::*,
|
||||||
|
community::*,
|
||||||
|
person::*,
|
||||||
|
post::*,
|
||||||
|
private_message::*,
|
||||||
|
site::*,
|
||||||
|
websocket::*,
|
||||||
|
};
|
||||||
use lemmy_api_crud::PerformCrud;
|
use lemmy_api_crud::PerformCrud;
|
||||||
use lemmy_utils::rate_limit::RateLimit;
|
use lemmy_utils::rate_limit::RateLimit;
|
||||||
use lemmy_websocket::{routes::chat_route, LemmyContext};
|
use lemmy_websocket::{routes::chat_route, LemmyContext};
|
||||||
|
@ -148,6 +156,18 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
.route(
|
.route(
|
||||||
"/mark_as_read",
|
"/mark_as_read",
|
||||||
web::post().to(route_post::<MarkPrivateMessageAsRead>),
|
web::post().to(route_post::<MarkPrivateMessageAsRead>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/report",
|
||||||
|
web::post().to(route_post::<CreatePrivateMessageReport>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/report/resolve",
|
||||||
|
web::put().to(route_post::<ResolvePrivateMessageReport>),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/report/list",
|
||||||
|
web::get().to(route_get::<ListPrivateMessageReports>),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// User
|
// User
|
||||||
|
|
Loading…
Reference in a new issue