Add community reports to API (#5435)

* add routes

* implement some of api

* fix use of check_report_reason

* fix create_community_report

* fix all compile errors

* add field to reportcombined struct

* add CommunityReportView::read test

* import resolve_community_report

* remove references to deleted table

* resolve reports after remove
This commit is contained in:
dullbananas 2025-03-10 06:37:50 -07:00 committed by GitHub
parent b8de9a518b
commit 08defa29fe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 225 additions and 2 deletions

View file

@ -0,0 +1,71 @@
use crate::check_report_reason;
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
reports::community::{CommunityReportResponse, CreateCommunityReport},
utils::{send_new_report_email_to_admins, slur_regex},
};
use lemmy_db_schema::{
source::{
community::Community,
community_report::{CommunityReport, CommunityReportForm},
local_site::LocalSite,
},
traits::{Crud, Reportable},
};
use lemmy_db_views::structs::{CommunityReportView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
pub async fn create_community_report(
data: Json<CreateCommunityReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityReportResponse>> {
let reason = data.reason.trim().to_string();
let slur_regex = slur_regex(&context).await?;
check_report_reason(&reason, &slur_regex)?;
let person_id = local_user_view.person.id;
let community_id = data.community_id;
let community = Community::read(&mut context.pool(), community_id).await?;
let report_form = CommunityReportForm {
creator_id: person_id,
community_id,
original_community_banner: community.banner,
original_community_description: community.description,
original_community_icon: community.icon,
original_community_name: community.name,
original_community_sidebar: community.sidebar,
original_community_title: community.title,
reason,
};
let report = CommunityReport::report(&mut context.pool(), &report_form)
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let community_report_view =
CommunityReportView::read(&mut context.pool(), report.id, person_id).await?;
// Email the admins
let local_site = LocalSite::read(&mut context.pool()).await?;
if local_site.reports_email_admins {
send_new_report_email_to_admins(
&community_report_view.creator.name,
// The argument here is normally the reported content's creator, but a community doesn't have
// a single person to be considered the creator or the person responsible for the bad thing,
// so the community name is used instead
&community_report_view.community.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
// TODO: consider federating this
Ok(Json(CommunityReportResponse {
community_report_view,
}))
}

View file

@ -0,0 +1,2 @@
pub mod create;
pub mod resolve;

View file

@ -0,0 +1,36 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
reports::community::{CommunityReportResponse, ResolveCommunityReport},
utils::is_admin,
};
use lemmy_db_schema::{source::community_report::CommunityReport, traits::Reportable};
use lemmy_db_views::structs::{CommunityReportView, LocalUserView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
pub async fn resolve_community_report(
data: Json<ResolveCommunityReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<CommunityReportResponse>> {
is_admin(&local_user_view)?;
let report_id = data.report_id;
let person_id = local_user_view.person.id;
if data.resolved {
CommunityReport::resolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
} else {
CommunityReport::unresolve(&mut context.pool(), report_id, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
}
let community_report_view =
CommunityReportView::read(&mut context.pool(), report_id, person_id).await?;
Ok(Json(CommunityReportResponse {
community_report_view,
}))
}

View file

@ -1,4 +1,5 @@
pub mod comment_report;
pub mod community_report;
pub mod post_report;
pub mod private_message_report;
pub mod report_combined;

View file

@ -0,0 +1,31 @@
use lemmy_db_schema::newtypes::{CommunityId, CommunityReportId};
use lemmy_db_views::structs::CommunityReportView;
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use ts_rs::TS;
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create a report for a community.
pub struct CreateCommunityReport {
pub community_id: CommunityId,
pub reason: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A community report response.
pub struct CommunityReportResponse {
pub community_report_view: CommunityReportView,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Resolve a community report.
pub struct ResolveCommunityReport {
pub report_id: CommunityReportId,
pub resolved: bool,
}

View file

@ -1,4 +1,5 @@
pub mod combined;
pub mod comment;
pub mod community;
pub mod post;
pub mod private_message;

View file

@ -10,9 +10,10 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
community::{Community, CommunityUpdateForm},
community_report::CommunityReport,
mod_log::moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
},
traits::Crud,
traits::{Crud, Reportable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
@ -48,6 +49,13 @@ pub async fn remove_community(
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateCommunity)?;
CommunityReport::resolve_all_for_object(
&mut context.pool(),
community_id,
local_user_view.person.id,
)
.await?;
// Mod tables
let form = ModRemoveCommunityForm {
mod_person_id: local_user_view.person.id,

View file

@ -14,6 +14,7 @@ use lemmy_db_schema::{
comment::{Comment, CommentUpdateForm},
comment_report::CommentReport,
community::{Community, CommunityUpdateForm},
community_report::CommunityReport,
mod_log::moderator::{
ModRemoveComment,
ModRemoveCommentForm,
@ -116,6 +117,7 @@ pub(in crate::activities) async fn receive_remove_action(
if community.local {
Err(FederationError::OnlyLocalAdminCanRemoveCommunity)?
}
CommunityReport::resolve_all_for_object(&mut context.pool(), community.id, actor.id).await?;
let form = ModRemoveCommunityForm {
mod_person_id: actor.id,
community_id: community.id,

View file

@ -1,4 +1,10 @@
use crate::newtypes::{CommentReportId, PostReportId, PrivateMessageReportId, ReportCombinedId};
use crate::newtypes::{
CommentReportId,
CommunityReportId,
PostReportId,
PrivateMessageReportId,
ReportCombinedId,
};
#[cfg(feature = "full")]
use crate::schema::report_combined;
use chrono::{DateTime, Utc};
@ -20,4 +26,5 @@ pub struct ReportCombined {
pub post_report_id: Option<PostReportId>,
pub comment_report_id: Option<CommentReportId>,
pub private_message_report_id: Option<PrivateMessageReportId>,
pub community_report_id: Option<CommunityReportId>,
}

View file

@ -511,6 +511,7 @@ mod tests {
combined::report_combined_view::ReportCombinedQuery,
structs::{
CommentReportView,
CommunityReportView,
LocalUserView,
PostReportView,
ReportCombinedView,
@ -1160,6 +1161,9 @@ mod tests {
assert_eq!(community_report.reason, v.community_report.reason);
assert_eq!(data.community.name, v.community.name);
assert_eq!(data.community.title, v.community.title);
let read_report =
CommunityReportView::read(pool, community_report.id, data.admin_view.person.id).await?;
assert_eq!(&read_report, v);
} else {
panic!("wrong type");
}

View file

@ -0,0 +1,55 @@
use crate::structs::CommunityReportView;
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
aliases,
impls::community::community_follower_select_subscribed_type,
newtypes::{CommunityReportId, PersonId},
schema::{community, community_actions, community_report, person},
utils::{get_conn, DbPool},
};
impl CommunityReportView {
/// returns the CommunityReportView for the provided report_id
///
/// * `report_id` - the report id to obtain
pub async fn read(
pool: &mut DbPool<'_>,
report_id: CommunityReportId,
my_person_id: PersonId,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
let community_actions_join = community_actions::table.on(
community_actions::community_id
.eq(community_report::community_id)
.and(community_actions::person_id.eq(my_person_id)),
);
community_report::table
.find(report_id)
.inner_join(community::table)
.inner_join(person::table.on(community_report::creator_id.eq(person::id)))
.left_join(
aliases::person2
.on(community_report::resolver_id.eq(aliases::person2.field(person::id).nullable())),
)
.left_join(community_actions_join)
.select((
community_report::all_columns,
community::all_columns,
person::all_columns,
community_follower_select_subscribed_type(),
aliases::person2.fields(person::all_columns.nullable()),
))
.first(conn)
.await
}
}

View file

@ -1,6 +1,8 @@
#[cfg(feature = "full")]
pub mod comment_report_view;
#[cfg(feature = "full")]
pub mod community_report_view;
#[cfg(feature = "full")]
pub mod post_report_view;
#[cfg(feature = "full")]
pub mod private_message_report_view;

View file

@ -66,6 +66,7 @@ use lemmy_api::{
private_message::mark_read::mark_pm_as_read,
reports::{
comment_report::{create::create_comment_report, resolve::resolve_comment_report},
community_report::{create::create_community_report, resolve::resolve_community_report},
post_report::{create::create_post_report, resolve::resolve_post_report},
private_message_report::{create::create_pm_report, resolve::resolve_pm_report},
report_combined::list::list_reports,
@ -217,6 +218,8 @@ pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
.route("/hide", put().to(hide_community))
.route("/list", get().to(list_communities))
.route("/follow", post().to(follow_community))
.route("/report", post().to(create_community_report))
.route("/report/resolve", put().to(resolve_community_report))
.route("/delete", post().to(delete_community))
// Mod Actions
.route("/remove", post().to(remove_community))