Clean up reporting (#1776)

* First untested pass at reporting.

* Adding unit tests for post and comment report views

* Fix clippy

* Adding counts, creator_banned, and unresolved_only

* Adding my_vote to report views

* Fixing unit tests.
add_user_agent
Dessalines 2021-09-28 06:36:17 -04:00 committed by GitHub
parent 240de006db
commit b18c744f63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 976 additions and 270 deletions

View File

@ -3,7 +3,6 @@ use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
collect_moderated_communities,
comment::*, comment::*,
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
is_mod_or_admin, is_mod_or_admin,
@ -15,22 +14,18 @@ use lemmy_db_views::{
comment_view::CommentView, comment_view::CommentView,
}; };
use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{ use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
messages::{SendModRoomMessage, SendUserRoomMessage},
LemmyContext,
UserOperation,
};
/// Creates a comment report and notifies the moderators of the community /// Creates a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for CreateCommentReport { impl Perform for CreateCommentReport {
type Response = CreateCommentReportResponse; type Response = CommentReportResponse;
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CreateCommentReportResponse, LemmyError> { ) -> Result<CommentReportResponse, LemmyError> {
let data: &CreateCommentReport = self; let data: &CreateCommentReport = self;
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?;
@ -66,18 +61,18 @@ impl Perform for CreateCommentReport {
.await? .await?
.map_err(|_| ApiError::err("couldnt_create_report"))?; .map_err(|_| ApiError::err("couldnt_create_report"))?;
let res = CreateCommentReportResponse { success: true }; let comment_report_view = blocking(context.pool(), move |conn| {
CommentReportView::read(conn, report.id, person_id)
})
.await??;
context.chat_server().do_send(SendUserRoomMessage { let res = CommentReportResponse {
op: UserOperation::CreateCommentReport, comment_report_view,
response: res.clone(), };
local_recipient_id: local_user_view.local_user.id,
websocket_id,
});
context.chat_server().do_send(SendModRoomMessage { context.chat_server().do_send(SendModRoomMessage {
op: UserOperation::CreateCommentReport, op: UserOperation::CreateCommentReport,
response: report, response: res.clone(),
community_id: comment_view.community.id, community_id: comment_view.community.id,
websocket_id, websocket_id,
}); });
@ -89,20 +84,21 @@ impl Perform for CreateCommentReport {
/// Resolves or unresolves a comment report and notifies the moderators of the community /// Resolves or unresolves a comment report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for ResolveCommentReport { impl Perform for ResolveCommentReport {
type Response = ResolveCommentReportResponse; type Response = CommentReportResponse;
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ResolveCommentReportResponse, LemmyError> { ) -> Result<CommentReportResponse, LemmyError> {
let data: &ResolveCommentReport = self; let data: &ResolveCommentReport = self;
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?;
let report_id = data.report_id; let report_id = data.report_id;
let person_id = local_user_view.person.id;
let report = blocking(context.pool(), move |conn| { let report = blocking(context.pool(), move |conn| {
CommentReportView::read(conn, report_id) CommentReportView::read(conn, report_id, person_id)
}) })
.await??; .await??;
@ -123,9 +119,13 @@ impl Perform for ResolveCommentReport {
}; };
let report_id = data.report_id; let report_id = data.report_id;
let res = ResolveCommentReportResponse { let comment_report_view = blocking(context.pool(), move |conn| {
report_id, CommentReportView::read(conn, report_id, person_id)
resolved, })
.await??;
let res = CommentReportResponse {
comment_report_view,
}; };
context.chat_server().do_send(SendModRoomMessage { context.chat_server().do_send(SendModRoomMessage {
@ -148,36 +148,29 @@ impl Perform for ListCommentReports {
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<ListCommentReportsResponse, LemmyError> { ) -> Result<ListCommentReportsResponse, LemmyError> {
let data: &ListCommentReports = self; let data: &ListCommentReports = self;
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?;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let community_id = data.community; let community_id = data.community_id;
let community_ids = let unresolved_only = data.unresolved_only;
collect_moderated_communities(person_id, community_id, context.pool()).await?;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let comments = blocking(context.pool(), move |conn| { let comment_reports = blocking(context.pool(), move |conn| {
CommentReportQueryBuilder::create(conn) CommentReportQueryBuilder::create(conn, person_id)
.community_ids(community_ids) .community_id(community_id)
.unresolved_only(unresolved_only)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
}) })
.await??; .await??;
let res = ListCommentReportsResponse { comments }; let res = ListCommentReportsResponse { comment_reports };
context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::ListCommentReports,
response: res.clone(),
local_recipient_id: local_user_view.local_user.id,
websocket_id,
});
Ok(res) Ok(res)
} }

View File

@ -6,7 +6,6 @@ use captcha::{gen, Difficulty};
use chrono::Duration; use chrono::Duration;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
collect_moderated_communities,
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
is_admin, is_admin,
password_length_check, password_length_check,
@ -67,7 +66,7 @@ use lemmy_utils::{
LemmyError, LemmyError,
}; };
use lemmy_websocket::{ use lemmy_websocket::{
messages::{CaptchaItem, SendAllMessage, SendUserRoomMessage}, messages::{CaptchaItem, SendAllMessage},
LemmyContext, LemmyContext,
UserOperation, UserOperation,
}; };
@ -816,52 +815,31 @@ impl Perform for GetReportCount {
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<GetReportCountResponse, LemmyError> { ) -> Result<GetReportCountResponse, LemmyError> {
let data: &GetReportCount = self; let data: &GetReportCount = self;
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?;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let community_id = data.community; let community_id = data.community_id;
let community_ids =
collect_moderated_communities(person_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| { let comment_reports = blocking(context.pool(), move |conn| {
CommentReportView::get_report_count(conn, &ids) CommentReportView::get_report_count(conn, person_id, community_id)
}) })
.await??; .await??;
let ids = community_ids.clone();
let post_reports = blocking(context.pool(), move |conn| { let post_reports = blocking(context.pool(), move |conn| {
PostReportView::get_report_count(conn, &ids) PostReportView::get_report_count(conn, person_id, community_id)
}) })
.await??; .await??;
GetReportCountResponse { let res = GetReportCountResponse {
community: data.community, community_id,
comment_reports, comment_reports,
post_reports, post_reports,
}
}
}; };
context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::GetReportCount,
response: res.clone(),
local_recipient_id: local_user_view.local_user.id,
websocket_id,
});
Ok(res) Ok(res)
} }
} }

View File

@ -3,16 +3,14 @@ use actix_web::web::Data;
use lemmy_api_common::{ use lemmy_api_common::{
blocking, blocking,
check_community_ban, check_community_ban,
collect_moderated_communities,
get_local_user_view_from_jwt, get_local_user_view_from_jwt,
is_mod_or_admin, is_mod_or_admin,
post::{ post::{
CreatePostReport, CreatePostReport,
CreatePostReportResponse,
ListPostReports, ListPostReports,
ListPostReportsResponse, ListPostReportsResponse,
PostReportResponse,
ResolvePostReport, ResolvePostReport,
ResolvePostReportResponse,
}, },
}; };
use lemmy_db_queries::Reportable; use lemmy_db_queries::Reportable;
@ -22,22 +20,18 @@ use lemmy_db_views::{
post_view::PostView, post_view::PostView,
}; };
use lemmy_utils::{ApiError, ConnectionId, LemmyError}; use lemmy_utils::{ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{ use lemmy_websocket::{messages::SendModRoomMessage, LemmyContext, UserOperation};
messages::{SendModRoomMessage, SendUserRoomMessage},
LemmyContext,
UserOperation,
};
/// Creates a post report and notifies the moderators of the community /// Creates a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for CreatePostReport { impl Perform for CreatePostReport {
type Response = CreatePostReportResponse; type Response = PostReportResponse;
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<CreatePostReportResponse, LemmyError> { ) -> Result<PostReportResponse, LemmyError> {
let data: &CreatePostReport = self; let data: &CreatePostReport = self;
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?;
@ -75,18 +69,16 @@ impl Perform for CreatePostReport {
.await? .await?
.map_err(|_| ApiError::err("couldnt_create_report"))?; .map_err(|_| ApiError::err("couldnt_create_report"))?;
let res = CreatePostReportResponse { success: true }; let post_report_view = blocking(context.pool(), move |conn| {
PostReportView::read(conn, report.id, person_id)
})
.await??;
context.chat_server().do_send(SendUserRoomMessage { let res = PostReportResponse { post_report_view };
op: UserOperation::CreatePostReport,
response: res.clone(),
local_recipient_id: local_user_view.local_user.id,
websocket_id,
});
context.chat_server().do_send(SendModRoomMessage { context.chat_server().do_send(SendModRoomMessage {
op: UserOperation::CreatePostReport, op: UserOperation::CreatePostReport,
response: report, response: res.clone(),
community_id: post_view.community.id, community_id: post_view.community.id,
websocket_id, websocket_id,
}); });
@ -98,20 +90,21 @@ impl Perform for CreatePostReport {
/// Resolves or unresolves a post report and notifies the moderators of the community /// Resolves or unresolves a post report and notifies the moderators of the community
#[async_trait::async_trait(?Send)] #[async_trait::async_trait(?Send)]
impl Perform for ResolvePostReport { impl Perform for ResolvePostReport {
type Response = ResolvePostReportResponse; type Response = PostReportResponse;
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, websocket_id: Option<ConnectionId>,
) -> Result<ResolvePostReportResponse, LemmyError> { ) -> Result<PostReportResponse, LemmyError> {
let data: &ResolvePostReport = self; let data: &ResolvePostReport = self;
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?;
let report_id = data.report_id; let report_id = data.report_id;
let person_id = local_user_view.person.id;
let report = blocking(context.pool(), move |conn| { let report = blocking(context.pool(), move |conn| {
PostReportView::read(conn, report_id) PostReportView::read(conn, report_id, person_id)
}) })
.await??; .await??;
@ -127,15 +120,17 @@ impl Perform for ResolvePostReport {
} }
}; };
let res = ResolvePostReportResponse {
report_id,
resolved: true,
};
if blocking(context.pool(), resolve_fun).await?.is_err() { if blocking(context.pool(), resolve_fun).await?.is_err() {
return Err(ApiError::err("couldnt_resolve_report").into()); return Err(ApiError::err("couldnt_resolve_report").into());
}; };
let post_report_view = blocking(context.pool(), move |conn| {
PostReportView::read(conn, report_id, person_id)
})
.await??;
let res = PostReportResponse { post_report_view };
context.chat_server().do_send(SendModRoomMessage { context.chat_server().do_send(SendModRoomMessage {
op: UserOperation::ResolvePostReport, op: UserOperation::ResolvePostReport,
response: res.clone(), response: res.clone(),
@ -156,36 +151,29 @@ impl Perform for ListPostReports {
async fn perform( async fn perform(
&self, &self,
context: &Data<LemmyContext>, context: &Data<LemmyContext>,
websocket_id: Option<ConnectionId>, _websocket_id: Option<ConnectionId>,
) -> Result<ListPostReportsResponse, LemmyError> { ) -> Result<ListPostReportsResponse, LemmyError> {
let data: &ListPostReports = self; let data: &ListPostReports = self;
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?;
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let community_id = data.community; let community_id = data.community_id;
let community_ids = let unresolved_only = data.unresolved_only;
collect_moderated_communities(person_id, community_id, context.pool()).await?;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
let posts = blocking(context.pool(), move |conn| { let post_reports = blocking(context.pool(), move |conn| {
PostReportQueryBuilder::create(conn) PostReportQueryBuilder::create(conn, person_id)
.community_ids(community_ids) .community_id(community_id)
.unresolved_only(unresolved_only)
.page(page) .page(page)
.limit(limit) .limit(limit)
.list() .list()
}) })
.await??; .await??;
let res = ListPostReportsResponse { posts }; let res = ListPostReportsResponse { post_reports };
context.chat_server().do_send(SendUserRoomMessage {
op: UserOperation::ListPostReports,
response: res.clone(),
local_recipient_id: local_user_view.local_user.id,
websocket_id,
});
Ok(res) Ok(res)
} }

View File

@ -1,4 +1,4 @@
use lemmy_db_schema::{CommentId, CommunityId, LocalUserId, PostId}; use lemmy_db_schema::{CommentId, CommentReportId, CommunityId, LocalUserId, PostId};
use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView}; use lemmy_db_views::{comment_report_view::CommentReportView, comment_view::CommentView};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -79,42 +79,37 @@ pub struct GetCommentsResponse {
pub comments: Vec<CommentView>, pub comments: Vec<CommentView>,
} }
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
pub struct CreateCommentReport { pub struct CreateCommentReport {
pub comment_id: CommentId, pub comment_id: CommentId,
pub reason: String, pub reason: String,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Clone)]
pub struct CreateCommentReportResponse { pub struct CommentReportResponse {
pub success: bool, pub comment_report_view: CommentReportView,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Deserialize)]
pub struct ResolveCommentReport { pub struct ResolveCommentReport {
pub report_id: i32, pub report_id: CommentReportId,
pub resolved: bool, pub resolved: bool,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Deserialize)]
pub struct ResolveCommentReportResponse {
// TODO this should probably return the view
pub report_id: i32,
pub resolved: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ListCommentReports { pub struct ListCommentReports {
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
/// Only shows the unresolved reports
pub unresolved_only: Option<bool>,
/// if no community is given, it returns reports for all communities moderated by the auth user /// if no community is given, it returns reports for all communities moderated by the auth user
pub community: Option<CommunityId>, pub community_id: Option<CommunityId>,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Clone, Debug)] #[derive(Serialize)]
pub struct ListCommentReportsResponse { pub struct ListCommentReportsResponse {
pub comments: Vec<CommentReportView>, pub comment_reports: Vec<CommentReportView>,
} }

View File

@ -8,11 +8,7 @@ pub mod websocket;
use crate::site::FederatedInstances; use crate::site::FederatedInstances;
use diesel::PgConnection; use diesel::PgConnection;
use lemmy_db_queries::{ use lemmy_db_queries::{
source::{ source::{community::Community_, person_block::PersonBlock_, site::Site_},
community::{CommunityModerator_, Community_},
person_block::PersonBlock_,
site::Site_,
},
Crud, Crud,
DbPool, DbPool,
Readable, Readable,
@ -20,7 +16,7 @@ use lemmy_db_queries::{
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
comment::Comment, comment::Comment,
community::{Community, CommunityModerator}, community::Community,
person::Person, person::Person,
person_block::PersonBlock, person_block::PersonBlock,
person_mention::{PersonMention, PersonMentionForm}, person_mention::{PersonMention, PersonMentionForm},
@ -384,31 +380,6 @@ pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), Le
Ok(()) Ok(())
} }
/// Returns a list of communities that the user moderates
/// or if a community_id is supplied validates the user is a moderator
/// of that community and returns the community id in a vec
///
/// * `person_id` - the person id of the moderator
/// * `community_id` - optional community id to check for moderator privileges
/// * `pool` - the diesel db pool
pub async fn collect_moderated_communities(
person_id: PersonId,
community_id: Option<CommunityId>,
pool: &DbPool,
) -> Result<Vec<CommunityId>, 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, person_id, community_id).await?;
Ok(vec![community_id])
} else {
let ids = blocking(pool, move |conn: &'_ _| {
CommunityModerator::get_person_moderated_communities(conn, person_id)
})
.await??;
Ok(ids)
}
}
pub async fn build_federated_instances( pub async fn build_federated_instances(
pool: &DbPool, pool: &DbPool,
federation_config: &FederationConfig, federation_config: &FederationConfig,

View File

@ -253,15 +253,15 @@ pub struct PrivateMessageResponse {
pub private_message_view: PrivateMessageView, pub private_message_view: PrivateMessageView,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Deserialize)]
pub struct GetReportCount { pub struct GetReportCount {
pub community: Option<CommunityId>, pub community_id: Option<CommunityId>,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Clone)]
pub struct GetReportCountResponse { pub struct GetReportCountResponse {
pub community: Option<CommunityId>, pub community_id: Option<CommunityId>,
pub comment_reports: i64, pub comment_reports: i64,
pub post_reports: i64, pub post_reports: i64,
} }

View File

@ -1,4 +1,4 @@
use lemmy_db_schema::{CommunityId, PostId}; use lemmy_db_schema::{CommunityId, PostId, PostReportId};
use lemmy_db_views::{ use lemmy_db_views::{
comment_view::CommentView, comment_view::CommentView,
post_report_view::PostReportView, post_report_view::PostReportView,
@ -112,42 +112,39 @@ pub struct SavePost {
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize)] #[derive(Deserialize)]
pub struct CreatePostReport { pub struct CreatePostReport {
pub post_id: PostId, pub post_id: PostId,
pub reason: String, pub reason: String,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Clone)]
pub struct CreatePostReportResponse { pub struct PostReportResponse {
pub success: bool, pub post_report_view: PostReportView,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Deserialize)]
pub struct ResolvePostReport { pub struct ResolvePostReport {
pub report_id: i32, pub report_id: PostReportId,
pub resolved: bool, pub resolved: bool,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Deserialize)]
pub struct ResolvePostReportResponse {
pub report_id: i32,
pub resolved: bool,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ListPostReports { pub struct ListPostReports {
pub page: Option<i64>, pub page: Option<i64>,
pub limit: Option<i64>, pub limit: Option<i64>,
pub community: Option<CommunityId>, /// Only shows the unresolved reports
pub unresolved_only: Option<bool>,
/// if no community is given, it returns reports for all communities moderated by the auth user
pub community_id: Option<CommunityId>,
pub auth: String, pub auth: String,
} }
#[derive(Serialize, Clone, Debug)] #[derive(Serialize)]
pub struct ListPostReportsResponse { pub struct ListPostReportsResponse {
pub posts: Vec<PostReportView>, pub post_reports: Vec<PostReportView>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]

View File

@ -131,13 +131,22 @@ pub trait Readable {
pub trait Reportable { pub trait Reportable {
type Form; type Form;
type IdType;
fn report(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> fn report(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error>
where where
Self: Sized; Self: Sized;
fn resolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error> fn resolve(
conn: &PgConnection,
report_id: Self::IdType,
resolver_id: PersonId,
) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
fn unresolve(conn: &PgConnection, report_id: i32, resolver_id: PersonId) -> Result<usize, Error> fn unresolve(
conn: &PgConnection,
report_id: Self::IdType,
resolver_id: PersonId,
) -> Result<usize, Error>
where where
Self: Sized; Self: Sized;
} }

View File

@ -3,11 +3,13 @@ use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{ use lemmy_db_schema::{
naive_now, naive_now,
source::comment_report::{CommentReport, CommentReportForm}, source::comment_report::{CommentReport, CommentReportForm},
CommentReportId,
PersonId, PersonId,
}; };
impl Reportable for CommentReport { impl Reportable for CommentReport {
type Form = CommentReportForm; type Form = CommentReportForm;
type IdType = CommentReportId;
/// creates a comment report and returns it /// creates a comment report and returns it
/// ///
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
@ -26,7 +28,7 @@ impl Reportable for CommentReport {
/// * `by_resolver_id` - the id of the user resolving the report /// * `by_resolver_id` - the id of the user resolving the report
fn resolve( fn resolve(
conn: &PgConnection, conn: &PgConnection,
report_id: i32, report_id: Self::IdType,
by_resolver_id: PersonId, by_resolver_id: PersonId,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment_report::dsl::*; use lemmy_db_schema::schema::comment_report::dsl::*;
@ -46,7 +48,7 @@ impl Reportable for CommentReport {
/// * `by_resolver_id` - the id of the user unresolving the report /// * `by_resolver_id` - the id of the user unresolving the report
fn unresolve( fn unresolve(
conn: &PgConnection, conn: &PgConnection,
report_id: i32, report_id: Self::IdType,
by_resolver_id: PersonId, by_resolver_id: PersonId,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::comment_report::dsl::*; use lemmy_db_schema::schema::comment_report::dsl::*;

View File

@ -1,9 +1,11 @@
use crate::Reportable; use crate::Reportable;
use diesel::{dsl::*, result::Error, *}; use diesel::{dsl::*, result::Error, *};
use lemmy_db_schema::{naive_now, source::post_report::*, PersonId}; use lemmy_db_schema::{naive_now, source::post_report::*, PersonId, PostReportId};
impl Reportable for PostReport { impl Reportable for PostReport {
type Form = PostReportForm; type Form = PostReportForm;
type IdType = PostReportId;
/// creates a post report and returns it /// creates a post report and returns it
/// ///
/// * `conn` - the postgres connection /// * `conn` - the postgres connection
@ -22,7 +24,7 @@ impl Reportable for PostReport {
/// * `by_resolver_id` - the id of the user resolving the report /// * `by_resolver_id` - the id of the user resolving the report
fn resolve( fn resolve(
conn: &PgConnection, conn: &PgConnection,
report_id: i32, report_id: Self::IdType,
by_resolver_id: PersonId, by_resolver_id: PersonId,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::post_report::dsl::*; use lemmy_db_schema::schema::post_report::dsl::*;
@ -42,7 +44,7 @@ impl Reportable for PostReport {
/// * `by_resolver_id` - the id of the user unresolving the report /// * `by_resolver_id` - the id of the user unresolving the report
fn unresolve( fn unresolve(
conn: &PgConnection, conn: &PgConnection,
report_id: i32, report_id: Self::IdType,
by_resolver_id: PersonId, by_resolver_id: PersonId,
) -> Result<usize, Error> { ) -> Result<usize, Error> {
use lemmy_db_schema::schema::post_report::dsl::*; use lemmy_db_schema::schema::post_report::dsl::*;

View File

@ -73,6 +73,12 @@ pub struct PersonBlockId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommunityBlockId(i32); pub struct CommunityBlockId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct CommentReportId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, DieselNewType)]
pub struct PostReportId(i32);
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)] #[derive(Clone, PartialEq, Serialize, Deserialize, Debug, AsExpression, FromSqlRow)]
#[sql_type = "Text"] #[sql_type = "Text"]

View File

@ -1,4 +1,10 @@
use crate::{schema::comment_report, source::comment::Comment, CommentId, PersonId}; use crate::{
schema::comment_report,
source::comment::Comment,
CommentId,
CommentReportId,
PersonId,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive( #[derive(
@ -7,7 +13,7 @@ use serde::{Deserialize, Serialize};
#[belongs_to(Comment)] #[belongs_to(Comment)]
#[table_name = "comment_report"] #[table_name = "comment_report"]
pub struct CommentReport { pub struct CommentReport {
pub id: i32, pub id: CommentReportId,
pub creator_id: PersonId, pub creator_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub original_comment_text: String, pub original_comment_text: String,

View File

@ -1,4 +1,4 @@
use crate::{schema::post_report, source::post::Post, DbUrl, PersonId, PostId}; use crate::{schema::post_report, source::post::Post, DbUrl, PersonId, PostId, PostReportId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive( #[derive(
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
#[belongs_to(Post)] #[belongs_to(Post)]
#[table_name = "post_report"] #[table_name = "post_report"]
pub struct PostReport { pub struct PostReport {
pub id: i32, pub id: PostReportId,
pub creator_id: PersonId, pub creator_id: PersonId,
pub post_id: PostId, pub post_id: PostId,
pub original_post_name: String, pub original_post_name: String,

View File

@ -1,15 +1,35 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_queries::{limit_and_offset, MaybeOptional, ToSafe, ViewToVec}; use lemmy_db_queries::{
aggregates::comment_aggregates::CommentAggregates,
limit_and_offset,
MaybeOptional,
ToSafe,
ViewToVec,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
schema::{comment, comment_report, community, person, person_alias_1, person_alias_2, post}, schema::{
comment,
comment_aggregates,
comment_like,
comment_report,
community,
community_moderator,
community_person_ban,
person,
person_alias_1,
person_alias_2,
post,
},
source::{ source::{
comment::Comment, comment::Comment,
comment_report::CommentReport, comment_report::CommentReport,
community::{Community, CommunitySafe}, community::{Community, CommunityPersonBan, CommunitySafe},
person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2}, person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
post::Post, post::Post,
}, },
CommentReportId,
CommunityId, CommunityId,
PersonId,
}; };
use serde::Serialize; use serde::Serialize;
@ -21,6 +41,9 @@ pub struct CommentReportView {
pub community: CommunitySafe, pub community: CommunitySafe,
pub creator: PersonSafe, pub creator: PersonSafe,
pub comment_creator: PersonSafeAlias1, pub comment_creator: PersonSafeAlias1,
pub counts: CommentAggregates,
pub creator_banned_from_community: bool, // Left Join to CommunityPersonBan
pub my_vote: Option<i16>, // Left join to CommentLike
pub resolver: Option<PersonSafeAlias2>, pub resolver: Option<PersonSafeAlias2>,
} }
@ -31,6 +54,9 @@ type CommentReportViewTuple = (
CommunitySafe, CommunitySafe,
PersonSafe, PersonSafe,
PersonSafeAlias1, PersonSafeAlias1,
CommentAggregates,
Option<CommunityPersonBan>,
Option<i16>,
Option<PersonSafeAlias2>, Option<PersonSafeAlias2>,
); );
@ -38,15 +64,46 @@ impl CommentReportView {
/// returns the CommentReportView for the provided report_id /// returns the CommentReportView for the provided report_id
/// ///
/// * `report_id` - the report id to obtain /// * `report_id` - the report id to obtain
pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> { pub fn read(
let (comment_report, comment, post, community, creator, comment_creator, resolver) = conn: &PgConnection,
comment_report::table report_id: CommentReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
let (
comment_report,
comment,
post,
community,
creator,
comment_creator,
counts,
creator_banned_from_community,
comment_like,
resolver,
) = comment_report::table
.find(report_id) .find(report_id)
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(post::table.on(comment::post_id.eq(post::id)))
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id)))
.inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id)))
.inner_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(my_person_id)),
),
)
.left_join( .left_join(
person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())), person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())),
) )
@ -57,10 +114,19 @@ impl CommentReportView {
Community::safe_columns_tuple(), Community::safe_columns_tuple(),
Person::safe_columns_tuple(), Person::safe_columns_tuple(),
PersonAlias1::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
comment_like::score.nullable(),
PersonAlias2::safe_columns_tuple().nullable(), PersonAlias2::safe_columns_tuple().nullable(),
)) ))
.first::<CommentReportViewTuple>(conn)?; .first::<CommentReportViewTuple>(conn)?;
let my_vote = if comment_like.is_none() {
None
} else {
comment_like
};
Ok(Self { Ok(Self {
comment_report, comment_report,
comment, comment,
@ -68,6 +134,9 @@ impl CommentReportView {
community, community,
creator, creator,
comment_creator, comment_creator,
counts,
creator_banned_from_community: creator_banned_from_community.is_some(),
my_vote,
resolver, resolver,
}) })
} }
@ -76,46 +145,60 @@ impl CommentReportView {
/// ///
/// * `community_ids` - a Vec<i32> of community_ids to get a count for /// * `community_ids` - a Vec<i32> of community_ids to get a count for
/// TODO this eq_any is a bad way to do this, would be better to join to communitymoderator /// TODO this eq_any is a bad way to do this, would be better to join to communitymoderator
/// TODO FIX THIS NOW
/// for a person id /// for a person id
pub fn get_report_count( pub fn get_report_count(
conn: &PgConnection, conn: &PgConnection,
community_ids: &[CommunityId], my_person_id: PersonId,
community_id: Option<CommunityId>,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
use diesel::dsl::*; use diesel::dsl::*;
comment_report::table
let mut query = comment_report::table
.inner_join(comment::table) .inner_join(comment::table)
.inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(post::table.on(comment::post_id.eq(post::id)))
.filter( // Test this join
comment_report::resolved .inner_join(
.eq(false) community_moderator::table.on(
.and(post::community_id.eq_any(community_ids)), community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person_id)),
),
) )
.select(count(comment_report::id)) .filter(comment_report::resolved.eq(false))
.first::<i64>(conn) .into_boxed();
if let Some(community_id) = community_id {
query = query.filter(post::community_id.eq(community_id))
}
query.select(count(comment_report::id)).first::<i64>(conn)
} }
} }
pub struct CommentReportQueryBuilder<'a> { pub struct CommentReportQueryBuilder<'a> {
conn: &'a PgConnection, conn: &'a PgConnection,
community_ids: Option<Vec<CommunityId>>, // TODO bad way to do this my_person_id: PersonId,
community_id: Option<CommunityId>,
page: Option<i64>, page: Option<i64>,
limit: Option<i64>, limit: Option<i64>,
resolved: Option<bool>, unresolved_only: Option<bool>,
} }
impl<'a> CommentReportQueryBuilder<'a> { impl<'a> CommentReportQueryBuilder<'a> {
pub fn create(conn: &'a PgConnection) -> Self { pub fn create(conn: &'a PgConnection, my_person_id: PersonId) -> Self {
CommentReportQueryBuilder { CommentReportQueryBuilder {
conn, conn,
community_ids: None, my_person_id,
community_id: None,
page: None, page: None,
limit: None, limit: None,
resolved: Some(false), unresolved_only: Some(true),
} }
} }
pub fn community_ids<T: MaybeOptional<Vec<CommunityId>>>(mut self, community_ids: T) -> Self { pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
self.community_ids = community_ids.get_optional(); self.community_id = community_id.get_optional();
self self
} }
@ -129,8 +212,8 @@ impl<'a> CommentReportQueryBuilder<'a> {
self self
} }
pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self { pub fn unresolved_only<T: MaybeOptional<bool>>(mut self, unresolved_only: T) -> Self {
self.resolved = resolved.get_optional(); self.unresolved_only = unresolved_only.get_optional();
self self
} }
@ -141,6 +224,31 @@ impl<'a> CommentReportQueryBuilder<'a> {
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id)))
.inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id)))
// Test this join
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(self.my_person_id)),
),
)
.inner_join(
comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)),
)
.left_join(
community_person_ban::table.on(
community::id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(comment::creator_id)),
),
)
.left_join(
comment_like::table.on(
comment::id
.eq(comment_like::comment_id)
.and(comment_like::person_id.eq(self.my_person_id)),
),
)
.left_join( .left_join(
person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())), person_alias_2::table.on(comment_report::resolver_id.eq(person_alias_2::id.nullable())),
) )
@ -151,16 +259,19 @@ impl<'a> CommentReportQueryBuilder<'a> {
Community::safe_columns_tuple(), Community::safe_columns_tuple(),
Person::safe_columns_tuple(), Person::safe_columns_tuple(),
PersonAlias1::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(),
comment_aggregates::all_columns,
community_person_ban::all_columns.nullable(),
comment_like::score.nullable(),
PersonAlias2::safe_columns_tuple().nullable(), PersonAlias2::safe_columns_tuple().nullable(),
)) ))
.into_boxed(); .into_boxed();
if let Some(comm_ids) = self.community_ids { if let Some(community_id) = self.community_id {
query = query.filter(post::community_id.eq_any(comm_ids)); query = query.filter(post::community_id.eq(community_id));
} }
if let Some(resolved_flag) = self.resolved { if self.unresolved_only.unwrap_or(false) {
query = query.filter(comment_report::resolved.eq(resolved_flag)); query = query.filter(comment_report::resolved.eq(false));
} }
let (limit, offset) = limit_and_offset(self.page, self.limit); let (limit, offset) = limit_and_offset(self.page, self.limit);
@ -187,8 +298,281 @@ impl ViewToVec for CommentReportView {
community: a.3.to_owned(), community: a.3.to_owned(),
creator: a.4.to_owned(), creator: a.4.to_owned(),
comment_creator: a.5.to_owned(), comment_creator: a.5.to_owned(),
resolver: a.6.to_owned(), counts: a.6.to_owned(),
creator_banned_from_community: a.7.is_some(),
my_vote: a.8,
resolver: a.9.to_owned(),
}) })
.collect::<Vec<Self>>() .collect::<Vec<Self>>()
} }
} }
#[cfg(test)]
mod tests {
use crate::comment_report_view::{CommentReportQueryBuilder, CommentReportView};
use lemmy_db_queries::{
aggregates::comment_aggregates::CommentAggregates,
establish_unpooled_connection,
Crud,
Joinable,
Reportable,
};
use lemmy_db_schema::source::{comment::*, comment_report::*, community::*, person::*, post::*};
use serial_test::serial;
#[test]
#[serial]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_person = PersonForm {
name: "timmy_crv".into(),
..PersonForm::default()
};
let inserted_timmy = Person::create(&conn, &new_person).unwrap();
let new_person_2 = PersonForm {
name: "sara_crv".into(),
..PersonForm::default()
};
let inserted_sara = Person::create(&conn, &new_person_2).unwrap();
// Add a third person, since new ppl can only report something once.
let new_person_3 = PersonForm {
name: "jessica_crv".into(),
..PersonForm::default()
};
let inserted_jessica = Person::create(&conn, &new_person_3).unwrap();
let new_community = CommunityForm {
name: "test community crv".to_string(),
title: "nada".to_owned(),
..CommunityForm::default()
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
// Make timmy a mod
let timmy_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: inserted_timmy.id,
};
let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap();
let new_post = PostForm {
name: "A test post crv".into(),
creator_id: inserted_timmy.id,
community_id: inserted_community.id,
..PostForm::default()
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
let comment_form = CommentForm {
content: "A test comment 32".into(),
creator_id: inserted_timmy.id,
post_id: inserted_post.id,
..CommentForm::default()
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
// sara reports
let sara_report_form = CommentReportForm {
creator_id: inserted_sara.id,
comment_id: inserted_comment.id,
original_comment_text: "this was it at time of creation".into(),
reason: "from sara".into(),
};
let inserted_sara_report = CommentReport::report(&conn, &sara_report_form).unwrap();
// jessica reports
let jessica_report_form = CommentReportForm {
creator_id: inserted_jessica.id,
comment_id: inserted_comment.id,
original_comment_text: "this was it at time of creation".into(),
reason: "from jessica".into(),
};
let inserted_jessica_report = CommentReport::report(&conn, &jessica_report_form).unwrap();
let agg = CommentAggregates::read(&conn, inserted_comment.id).unwrap();
let read_jessica_report_view =
CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let expected_jessica_report_view = CommentReportView {
comment_report: inserted_jessica_report.to_owned(),
comment: inserted_comment.to_owned(),
post: inserted_post,
community: CommunitySafe {
id: inserted_community.id,
name: inserted_community.name,
icon: None,
removed: false,
deleted: false,
nsfw: false,
actor_id: inserted_community.actor_id.to_owned(),
local: true,
title: inserted_community.title,
description: None,
updated: None,
banner: None,
published: inserted_community.published,
},
creator: PersonSafe {
id: inserted_jessica.id,
name: inserted_jessica.name,
display_name: None,
published: inserted_jessica.published,
avatar: None,
actor_id: inserted_jessica.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_jessica.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
},
comment_creator: PersonSafeAlias1 {
id: inserted_timmy.id,
name: inserted_timmy.name.to_owned(),
display_name: None,
published: inserted_timmy.published,
avatar: None,
actor_id: inserted_timmy.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_timmy.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
},
creator_banned_from_community: false,
counts: CommentAggregates {
id: agg.id,
comment_id: inserted_comment.id,
score: 0,
upvotes: 0,
downvotes: 0,
published: agg.published,
},
my_vote: None,
resolver: None,
};
assert_eq!(read_jessica_report_view, expected_jessica_report_view);
let mut expected_sara_report_view = expected_jessica_report_view.clone();
expected_sara_report_view.comment_report = inserted_sara_report;
expected_sara_report_view.creator = PersonSafe {
id: inserted_sara.id,
name: inserted_sara.name,
display_name: None,
published: inserted_sara.published,
avatar: None,
actor_id: inserted_sara.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_sara.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
};
// Do a batch read of timmys reports
let reports = CommentReportQueryBuilder::create(&conn, inserted_timmy.id)
.list()
.unwrap();
assert_eq!(
reports,
[
expected_sara_report_view.to_owned(),
expected_jessica_report_view.to_owned()
]
);
// Make sure the counts are correct
let report_count = CommentReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap();
assert_eq!(2, report_count);
// Try to resolve the report
CommentReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let read_jessica_report_view_after_resolve =
CommentReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
expected_jessica_report_view_after_resolve
.comment_report
.resolved = true;
expected_jessica_report_view_after_resolve
.comment_report
.resolver_id = Some(inserted_timmy.id);
expected_jessica_report_view_after_resolve
.comment_report
.updated = read_jessica_report_view_after_resolve
.comment_report
.updated;
expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 {
id: inserted_timmy.id,
name: inserted_timmy.name.to_owned(),
display_name: None,
published: inserted_timmy.published,
avatar: None,
actor_id: inserted_timmy.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_timmy.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
});
assert_eq!(
read_jessica_report_view_after_resolve,
expected_jessica_report_view_after_resolve
);
// Do a batch read of timmys reports
// It should only show saras, which is unresolved
let reports_after_resolve = CommentReportQueryBuilder::create(&conn, inserted_timmy.id)
.list()
.unwrap();
assert_eq!(reports_after_resolve[0], expected_sara_report_view);
// Make sure the counts are correct
let report_count_after_resolved =
CommentReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap();
assert_eq!(1, report_count_after_resolved);
Person::delete(&conn, inserted_timmy.id).unwrap();
Person::delete(&conn, inserted_sara.id).unwrap();
Person::delete(&conn, inserted_jessica.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
}
}

View File

@ -1,14 +1,33 @@
use diesel::{result::Error, *}; use diesel::{result::Error, *};
use lemmy_db_queries::{limit_and_offset, MaybeOptional, ToSafe, ViewToVec}; use lemmy_db_queries::{
aggregates::post_aggregates::PostAggregates,
limit_and_offset,
MaybeOptional,
ToSafe,
ViewToVec,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
schema::{community, person, person_alias_1, person_alias_2, post, post_report}, schema::{
community,
community_moderator,
community_person_ban,
person,
person_alias_1,
person_alias_2,
post,
post_aggregates,
post_like,
post_report,
},
source::{ source::{
community::{Community, CommunitySafe}, community::{Community, CommunityPersonBan, CommunitySafe},
person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2}, person::{Person, PersonAlias1, PersonAlias2, PersonSafe, PersonSafeAlias1, PersonSafeAlias2},
post::Post, post::Post,
post_report::PostReport, post_report::PostReport,
}, },
CommunityId, CommunityId,
PersonId,
PostReportId,
}; };
use serde::Serialize; use serde::Serialize;
@ -19,6 +38,9 @@ pub struct PostReportView {
pub community: CommunitySafe, pub community: CommunitySafe,
pub creator: PersonSafe, pub creator: PersonSafe,
pub post_creator: PersonSafeAlias1, pub post_creator: PersonSafeAlias1,
pub creator_banned_from_community: bool,
pub my_vote: Option<i16>,
pub counts: PostAggregates,
pub resolver: Option<PersonSafeAlias2>, pub resolver: Option<PersonSafeAlias2>,
} }
@ -28,6 +50,9 @@ type PostReportViewTuple = (
CommunitySafe, CommunitySafe,
PersonSafe, PersonSafe,
PersonSafeAlias1, PersonSafeAlias1,
Option<CommunityPersonBan>,
Option<i16>,
PostAggregates,
Option<PersonSafeAlias2>, Option<PersonSafeAlias2>,
); );
@ -35,13 +60,42 @@ impl PostReportView {
/// returns the PostReportView for the provided report_id /// returns the PostReportView for the provided report_id
/// ///
/// * `report_id` - the report id to obtain /// * `report_id` - the report id to obtain
pub fn read(conn: &PgConnection, report_id: i32) -> Result<Self, Error> { pub fn read(
let (post_report, post, community, creator, post_creator, resolver) = post_report::table conn: &PgConnection,
report_id: PostReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
let (
post_report,
post,
community,
creator,
post_creator,
creator_banned_from_community,
post_like,
counts,
resolver,
) = post_report::table
.find(report_id) .find(report_id)
.inner_join(post::table) .inner_join(post::table)
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id)))
.inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id)))
.left_join(
community_person_ban::table.on(
post::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(post::creator_id)),
),
)
.left_join(
post_like::table.on(
post::id
.eq(post_like::post_id)
.and(post_like::person_id.eq(my_person_id)),
),
)
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
.left_join( .left_join(
person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())), person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())),
) )
@ -51,16 +105,24 @@ impl PostReportView {
Community::safe_columns_tuple(), Community::safe_columns_tuple(),
Person::safe_columns_tuple(), Person::safe_columns_tuple(),
PersonAlias1::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(),
community_person_ban::all_columns.nullable(),
post_like::score.nullable(),
post_aggregates::all_columns,
PersonAlias2::safe_columns_tuple().nullable(), PersonAlias2::safe_columns_tuple().nullable(),
)) ))
.first::<PostReportViewTuple>(conn)?; .first::<PostReportViewTuple>(conn)?;
let my_vote = if post_like.is_none() { None } else { post_like };
Ok(Self { Ok(Self {
post_report, post_report,
post, post,
community, community,
creator, creator,
post_creator, post_creator,
creator_banned_from_community: creator_banned_from_community.is_some(),
my_vote,
counts,
resolver, resolver,
}) })
} }
@ -72,42 +134,54 @@ impl PostReportView {
/// for a person id /// for a person id
pub fn get_report_count( pub fn get_report_count(
conn: &PgConnection, conn: &PgConnection,
community_ids: &[CommunityId], my_person_id: PersonId,
community_id: Option<CommunityId>,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
use diesel::dsl::*; use diesel::dsl::*;
post_report::table let mut query = post_report::table
.inner_join(post::table) .inner_join(post::table)
.filter( // Test this join
post_report::resolved .inner_join(
.eq(false) community_moderator::table.on(
.and(post::community_id.eq_any(community_ids)), community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(my_person_id)),
),
) )
.select(count(post_report::id)) .filter(post_report::resolved.eq(false))
.first::<i64>(conn) .into_boxed();
if let Some(community_id) = community_id {
query = query.filter(post::community_id.eq(community_id))
}
query.select(count(post_report::id)).first::<i64>(conn)
} }
} }
pub struct PostReportQueryBuilder<'a> { pub struct PostReportQueryBuilder<'a> {
conn: &'a PgConnection, conn: &'a PgConnection,
community_ids: Option<Vec<CommunityId>>, // TODO bad way to do this my_person_id: PersonId,
community_id: Option<CommunityId>,
page: Option<i64>, page: Option<i64>,
limit: Option<i64>, limit: Option<i64>,
resolved: Option<bool>, unresolved_only: Option<bool>,
} }
impl<'a> PostReportQueryBuilder<'a> { impl<'a> PostReportQueryBuilder<'a> {
pub fn create(conn: &'a PgConnection) -> Self { pub fn create(conn: &'a PgConnection, my_person_id: PersonId) -> Self {
PostReportQueryBuilder { PostReportQueryBuilder {
conn, conn,
community_ids: None, my_person_id,
community_id: None,
page: None, page: None,
limit: None, limit: None,
resolved: Some(false), unresolved_only: Some(true),
} }
} }
pub fn community_ids<T: MaybeOptional<Vec<CommunityId>>>(mut self, community_ids: T) -> Self { pub fn community_id<T: MaybeOptional<CommunityId>>(mut self, community_id: T) -> Self {
self.community_ids = community_ids.get_optional(); self.community_id = community_id.get_optional();
self self
} }
@ -121,8 +195,8 @@ impl<'a> PostReportQueryBuilder<'a> {
self self
} }
pub fn resolved<T: MaybeOptional<bool>>(mut self, resolved: T) -> Self { pub fn unresolved_only<T: MaybeOptional<bool>>(mut self, unresolved_only: T) -> Self {
self.resolved = resolved.get_optional(); self.unresolved_only = unresolved_only.get_optional();
self self
} }
@ -132,6 +206,29 @@ impl<'a> PostReportQueryBuilder<'a> {
.inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(community::table.on(post::community_id.eq(community::id)))
.inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id)))
.inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id))) .inner_join(person_alias_1::table.on(post::creator_id.eq(person_alias_1::id)))
// Test this join
.inner_join(
community_moderator::table.on(
community_moderator::community_id
.eq(post::community_id)
.and(community_moderator::person_id.eq(self.my_person_id)),
),
)
.left_join(
community_person_ban::table.on(
post::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(post::creator_id)),
),
)
.left_join(
post_like::table.on(
post::id
.eq(post_like::post_id)
.and(post_like::person_id.eq(self.my_person_id)),
),
)
.inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id)))
.left_join( .left_join(
person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())), person_alias_2::table.on(post_report::resolver_id.eq(person_alias_2::id.nullable())),
) )
@ -141,16 +238,19 @@ impl<'a> PostReportQueryBuilder<'a> {
Community::safe_columns_tuple(), Community::safe_columns_tuple(),
Person::safe_columns_tuple(), Person::safe_columns_tuple(),
PersonAlias1::safe_columns_tuple(), PersonAlias1::safe_columns_tuple(),
community_person_ban::all_columns.nullable(),
post_like::score.nullable(),
post_aggregates::all_columns,
PersonAlias2::safe_columns_tuple().nullable(), PersonAlias2::safe_columns_tuple().nullable(),
)) ))
.into_boxed(); .into_boxed();
if let Some(comm_ids) = self.community_ids { if let Some(community_id) = self.community_id {
query = query.filter(post::community_id.eq_any(comm_ids)); query = query.filter(post::community_id.eq(community_id));
} }
if let Some(resolved_flag) = self.resolved { if self.unresolved_only.unwrap_or(false) {
query = query.filter(post_report::resolved.eq(resolved_flag)); query = query.filter(post_report::resolved.eq(false));
} }
let (limit, offset) = limit_and_offset(self.page, self.limit); let (limit, offset) = limit_and_offset(self.page, self.limit);
@ -176,8 +276,283 @@ impl ViewToVec for PostReportView {
community: a.2.to_owned(), community: a.2.to_owned(),
creator: a.3.to_owned(), creator: a.3.to_owned(),
post_creator: a.4.to_owned(), post_creator: a.4.to_owned(),
resolver: a.5.to_owned(), creator_banned_from_community: a.5.is_some(),
my_vote: a.6,
counts: a.7.to_owned(),
resolver: a.8.to_owned(),
}) })
.collect::<Vec<Self>>() .collect::<Vec<Self>>()
} }
} }
#[cfg(test)]
mod tests {
use crate::post_report_view::{PostReportQueryBuilder, PostReportView};
use lemmy_db_queries::{
aggregates::post_aggregates::PostAggregates,
establish_unpooled_connection,
Crud,
Joinable,
Reportable,
};
use lemmy_db_schema::source::{
community::*,
person::*,
post::*,
post_report::{PostReport, PostReportForm},
};
use serial_test::serial;
#[test]
#[serial]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_person = PersonForm {
name: "timmy_prv".into(),
..PersonForm::default()
};
let inserted_timmy = Person::create(&conn, &new_person).unwrap();
let new_person_2 = PersonForm {
name: "sara_prv".into(),
..PersonForm::default()
};
let inserted_sara = Person::create(&conn, &new_person_2).unwrap();
// Add a third person, since new ppl can only report something once.
let new_person_3 = PersonForm {
name: "jessica_prv".into(),
..PersonForm::default()
};
let inserted_jessica = Person::create(&conn, &new_person_3).unwrap();
let new_community = CommunityForm {
name: "test community prv".to_string(),
title: "nada".to_owned(),
..CommunityForm::default()
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
// Make timmy a mod
let timmy_moderator_form = CommunityModeratorForm {
community_id: inserted_community.id,
person_id: inserted_timmy.id,
};
let _inserted_moderator = CommunityModerator::join(&conn, &timmy_moderator_form).unwrap();
let new_post = PostForm {
name: "A test post crv".into(),
creator_id: inserted_timmy.id,
community_id: inserted_community.id,
..PostForm::default()
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
// sara reports
let sara_report_form = PostReportForm {
creator_id: inserted_sara.id,
post_id: inserted_post.id,
original_post_name: "Orig post".into(),
original_post_url: None,
original_post_body: None,
reason: "from sara".into(),
};
let inserted_sara_report = PostReport::report(&conn, &sara_report_form).unwrap();
// jessica reports
let jessica_report_form = PostReportForm {
creator_id: inserted_jessica.id,
post_id: inserted_post.id,
original_post_name: "Orig post".into(),
original_post_url: None,
original_post_body: None,
reason: "from jessica".into(),
};
let inserted_jessica_report = PostReport::report(&conn, &jessica_report_form).unwrap();
let agg = PostAggregates::read(&conn, inserted_post.id).unwrap();
let read_jessica_report_view =
PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let expected_jessica_report_view = PostReportView {
post_report: inserted_jessica_report.to_owned(),
post: inserted_post.to_owned(),
community: CommunitySafe {
id: inserted_community.id,
name: inserted_community.name,
icon: None,
removed: false,
deleted: false,
nsfw: false,
actor_id: inserted_community.actor_id.to_owned(),
local: true,
title: inserted_community.title,
description: None,
updated: None,
banner: None,
published: inserted_community.published,
},
creator: PersonSafe {
id: inserted_jessica.id,
name: inserted_jessica.name,
display_name: None,
published: inserted_jessica.published,
avatar: None,
actor_id: inserted_jessica.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_jessica.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
},
post_creator: PersonSafeAlias1 {
id: inserted_timmy.id,
name: inserted_timmy.name.to_owned(),
display_name: None,
published: inserted_timmy.published,
avatar: None,
actor_id: inserted_timmy.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_timmy.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
},
creator_banned_from_community: false,
my_vote: None,
counts: PostAggregates {
id: agg.id,
post_id: inserted_post.id,
comments: 0,
score: 0,
upvotes: 0,
downvotes: 0,
stickied: false,
published: agg.published,
newest_comment_time_necro: inserted_post.published,
newest_comment_time: inserted_post.published,
},
resolver: None,
};
assert_eq!(read_jessica_report_view, expected_jessica_report_view);
let mut expected_sara_report_view = expected_jessica_report_view.clone();
expected_sara_report_view.post_report = inserted_sara_report;
expected_sara_report_view.my_vote = None;
expected_sara_report_view.creator = PersonSafe {
id: inserted_sara.id,
name: inserted_sara.name,
display_name: None,
published: inserted_sara.published,
avatar: None,
actor_id: inserted_sara.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_sara.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
};
// Do a batch read of timmys reports
let reports = PostReportQueryBuilder::create(&conn, inserted_timmy.id)
.list()
.unwrap();
assert_eq!(
reports,
[
expected_sara_report_view.to_owned(),
expected_jessica_report_view.to_owned()
]
);
// Make sure the counts are correct
let report_count = PostReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap();
assert_eq!(2, report_count);
// Try to resolve the report
PostReport::resolve(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let read_jessica_report_view_after_resolve =
PostReportView::read(&conn, inserted_jessica_report.id, inserted_timmy.id).unwrap();
let mut expected_jessica_report_view_after_resolve = expected_jessica_report_view;
expected_jessica_report_view_after_resolve
.post_report
.resolved = true;
expected_jessica_report_view_after_resolve
.post_report
.resolver_id = Some(inserted_timmy.id);
expected_jessica_report_view_after_resolve
.post_report
.updated = read_jessica_report_view_after_resolve.post_report.updated;
expected_jessica_report_view_after_resolve.resolver = Some(PersonSafeAlias2 {
id: inserted_timmy.id,
name: inserted_timmy.name.to_owned(),
display_name: None,
published: inserted_timmy.published,
avatar: None,
actor_id: inserted_timmy.actor_id.to_owned(),
local: true,
banned: false,
deleted: false,
admin: false,
bot_account: false,
bio: None,
banner: None,
updated: None,
inbox_url: inserted_timmy.inbox_url.to_owned(),
shared_inbox_url: None,
matrix_user_id: None,
});
assert_eq!(
read_jessica_report_view_after_resolve,
expected_jessica_report_view_after_resolve
);
// Do a batch read of timmys reports
// It should only show saras, which is unresolved
let reports_after_resolve = PostReportQueryBuilder::create(&conn, inserted_timmy.id)
.list()
.unwrap();
assert_eq!(reports_after_resolve[0], expected_sara_report_view);
// Make sure the counts are correct
let report_count_after_resolved =
PostReportView::get_report_count(&conn, inserted_timmy.id, None).unwrap();
assert_eq!(1, report_count_after_resolved);
Person::delete(&conn, inserted_timmy.id).unwrap();
Person::delete(&conn, inserted_sara.id).unwrap();
Person::delete(&conn, inserted_jessica.id).unwrap();
Community::delete(&conn, inserted_community.id).unwrap();
}
}