mirror of https://github.com/LemmyNet/lemmy
* First pass at adding admin purge. #904 #1331 * Breaking out purge into 4 tables for the 4 purgeable types. * Using CommunitySafe instead in view * Fix db_schema features flags. * Attempting to pass API key. * Adding pictrs image purging - Added pictrs_config block, for API_KEY - Clear out image columns after purging * Remove the remove_images field from a few of the purge API calls. * Fix some suggestions by @nutomic. * Add separate pictrs reqwest client. * Update defaults.hjson Co-authored-by: Nutomic <me@nutomic.com>post_report_name_length
parent
5b7376512f
commit
4e12e25c59
@ -1,6 +1,7 @@
|
||||
mod config;
|
||||
mod leave_admin;
|
||||
mod mod_log;
|
||||
mod purge;
|
||||
mod registration_applications;
|
||||
mod resolve_object;
|
||||
mod search;
|
||||
|
@ -0,0 +1,63 @@
|
||||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
site::{PurgeComment, PurgeItemResponse},
|
||||
utils::{blocking, get_local_user_view_from_jwt, is_admin},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::Comment,
|
||||
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PurgeComment {
|
||||
type Response = PurgeItemResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &Self = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
|
||||
// Read the comment to get the post_id
|
||||
let comment = blocking(context.pool(), move |conn| Comment::read(conn, comment_id)).await??;
|
||||
|
||||
let post_id = comment.post_id;
|
||||
|
||||
// TODO read comments for pictrs images and purge them
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
Comment::delete(conn, comment_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Mod tables
|
||||
let reason = data.reason.to_owned();
|
||||
let form = AdminPurgeCommentForm {
|
||||
admin_person_id: local_user_view.person.id,
|
||||
reason,
|
||||
post_id,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
AdminPurgeComment::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PurgeItemResponse { success: true })
|
||||
}
|
||||
}
|
@ -0,0 +1,82 @@
|
||||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
request::purge_image_from_pictrs,
|
||||
site::{PurgeCommunity, PurgeItemResponse},
|
||||
utils::{blocking, get_local_user_view_from_jwt, is_admin, purge_image_posts_for_community},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
community::Community,
|
||||
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PurgeCommunity {
|
||||
type Response = PurgeItemResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &Self = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
|
||||
// Read the community to get its images
|
||||
let community = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
if let Some(banner) = community.banner {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &banner)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if let Some(icon) = community.icon {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &icon)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
purge_image_posts_for_community(
|
||||
community_id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
Community::delete(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Mod tables
|
||||
let reason = data.reason.to_owned();
|
||||
let form = AdminPurgeCommunityForm {
|
||||
admin_person_id: local_user_view.person.id,
|
||||
reason,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
AdminPurgeCommunity::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PurgeItemResponse { success: true })
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
mod comment;
|
||||
mod community;
|
||||
mod person;
|
||||
mod post;
|
@ -0,0 +1,75 @@
|
||||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
request::purge_image_from_pictrs,
|
||||
site::{PurgeItemResponse, PurgePerson},
|
||||
utils::{blocking, get_local_user_view_from_jwt, is_admin, purge_image_posts_for_person},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
||||
person::Person,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PurgePerson {
|
||||
type Response = PurgeItemResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &Self = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Read the person to get their images
|
||||
let person_id = data.person_id;
|
||||
let person = blocking(context.pool(), move |conn| Person::read(conn, person_id)).await??;
|
||||
|
||||
if let Some(banner) = person.banner {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &banner)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if let Some(avatar) = person.avatar {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &avatar)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
purge_image_posts_for_person(
|
||||
person_id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
blocking(context.pool(), move |conn| Person::delete(conn, person_id)).await??;
|
||||
|
||||
// Mod tables
|
||||
let reason = data.reason.to_owned();
|
||||
let form = AdminPurgePersonForm {
|
||||
admin_person_id: local_user_view.person.id,
|
||||
reason,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
AdminPurgePerson::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PurgeItemResponse { success: true })
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
use crate::Perform;
|
||||
use actix_web::web::Data;
|
||||
use lemmy_api_common::{
|
||||
request::purge_image_from_pictrs,
|
||||
site::{PurgeItemResponse, PurgePost},
|
||||
utils::{blocking, get_local_user_view_from_jwt, is_admin},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
moderator::{AdminPurgePost, AdminPurgePostForm},
|
||||
post::Post,
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::{error::LemmyError, ConnectionId};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for PurgePost {
|
||||
type Response = PurgeItemResponse;
|
||||
|
||||
#[tracing::instrument(skip(context, _websocket_id))]
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data: &Self = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let post_id = data.post_id;
|
||||
|
||||
// Read the post to get the community_id
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
|
||||
// Purge image
|
||||
if let Some(url) = post.url {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &url)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
// Purge thumbnail
|
||||
if let Some(thumbnail_url) = post.thumbnail_url {
|
||||
purge_image_from_pictrs(context.client(), &context.settings(), &thumbnail_url)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
let community_id = post.community_id;
|
||||
|
||||
blocking(context.pool(), move |conn| Post::delete(conn, post_id)).await??;
|
||||
|
||||
// Mod tables
|
||||
let reason = data.reason.to_owned();
|
||||
let form = AdminPurgePostForm {
|
||||
admin_person_id: local_user_view.person.id,
|
||||
reason,
|
||||
community_id,
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
AdminPurgePost::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(PurgeItemResponse { success: true })
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
use crate::structs::AdminPurgeCommentView;
|
||||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{admin_purge_comment, person, post},
|
||||
source::{
|
||||
moderator::AdminPurgeComment,
|
||||
person::{Person, PersonSafe},
|
||||
post::Post,
|
||||
},
|
||||
traits::{ToSafe, ViewToVec},
|
||||
utils::limit_and_offset,
|
||||
};
|
||||
|
||||
type AdminPurgeCommentViewTuple = (AdminPurgeComment, PersonSafe, Post);
|
||||
|
||||
impl AdminPurgeCommentView {
|
||||
pub fn list(
|
||||
conn: &PgConnection,
|
||||
admin_person_id: Option<PersonId>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let mut query = admin_purge_comment::table
|
||||
.inner_join(person::table.on(admin_purge_comment::admin_person_id.eq(person::id)))
|
||||
.inner_join(post::table)
|
||||
.select((
|
||||
admin_purge_comment::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
post::all_columns,
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(admin_person_id) = admin_person_id {
|
||||
query = query.filter(admin_purge_comment::admin_person_id.eq(admin_person_id));
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(page, limit);
|
||||
|
||||
let res = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(admin_purge_comment::when_.desc())
|
||||
.load::<AdminPurgeCommentViewTuple>(conn)?;
|
||||
|
||||
Ok(Self::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for AdminPurgeCommentView {
|
||||
type DbTuple = AdminPurgeCommentViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.iter()
|
||||
.map(|a| Self {
|
||||
admin_purge_comment: a.0.to_owned(),
|
||||
admin: a.1.to_owned(),
|
||||
post: a.2.to_owned(),
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
use crate::structs::AdminPurgeCommunityView;
|
||||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{admin_purge_community, person},
|
||||
source::{
|
||||
moderator::AdminPurgeCommunity,
|
||||
person::{Person, PersonSafe},
|
||||
},
|
||||
traits::{ToSafe, ViewToVec},
|
||||
utils::limit_and_offset,
|
||||
};
|
||||
|
||||
type AdminPurgeCommunityViewTuple = (AdminPurgeCommunity, PersonSafe);
|
||||
|
||||
impl AdminPurgeCommunityView {
|
||||
pub fn list(
|
||||
conn: &PgConnection,
|
||||
admin_person_id: Option<PersonId>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let mut query = admin_purge_community::table
|
||||
.inner_join(person::table.on(admin_purge_community::admin_person_id.eq(person::id)))
|
||||
.select((
|
||||
admin_purge_community::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(admin_person_id) = admin_person_id {
|
||||
query = query.filter(admin_purge_community::admin_person_id.eq(admin_person_id));
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(page, limit);
|
||||
|
||||
let res = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(admin_purge_community::when_.desc())
|
||||
.load::<AdminPurgeCommunityViewTuple>(conn)?;
|
||||
|
||||
Ok(Self::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for AdminPurgeCommunityView {
|
||||
type DbTuple = AdminPurgeCommunityViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.iter()
|
||||
.map(|a| Self {
|
||||
admin_purge_community: a.0.to_owned(),
|
||||
admin: a.1.to_owned(),
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
use crate::structs::AdminPurgePersonView;
|
||||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{admin_purge_person, person},
|
||||
source::{
|
||||
moderator::AdminPurgePerson,
|
||||
person::{Person, PersonSafe},
|
||||
},
|
||||
traits::{ToSafe, ViewToVec},
|
||||
utils::limit_and_offset,
|
||||
};
|
||||
|
||||
type AdminPurgePersonViewTuple = (AdminPurgePerson, PersonSafe);
|
||||
|
||||
impl AdminPurgePersonView {
|
||||
pub fn list(
|
||||
conn: &PgConnection,
|
||||
admin_person_id: Option<PersonId>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let mut query = admin_purge_person::table
|
||||
.inner_join(person::table.on(admin_purge_person::admin_person_id.eq(person::id)))
|
||||
.select((
|
||||
admin_purge_person::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(admin_person_id) = admin_person_id {
|
||||
query = query.filter(admin_purge_person::admin_person_id.eq(admin_person_id));
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(page, limit);
|
||||
|
||||
let res = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(admin_purge_person::when_.desc())
|
||||
.load::<AdminPurgePersonViewTuple>(conn)?;
|
||||
|
||||
Ok(Self::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for AdminPurgePersonView {
|
||||
type DbTuple = AdminPurgePersonViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.iter()
|
||||
.map(|a| Self {
|
||||
admin_purge_person: a.0.to_owned(),
|
||||
admin: a.1.to_owned(),
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
use crate::structs::AdminPurgePostView;
|
||||
use diesel::{result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{admin_purge_post, community, person},
|
||||
source::{
|
||||
community::{Community, CommunitySafe},
|
||||
moderator::AdminPurgePost,
|
||||
person::{Person, PersonSafe},
|
||||
},
|
||||
traits::{ToSafe, ViewToVec},
|
||||
utils::limit_and_offset,
|
||||
};
|
||||
|
||||
type AdminPurgePostViewTuple = (AdminPurgePost, PersonSafe, CommunitySafe);
|
||||
|
||||
impl AdminPurgePostView {
|
||||
pub fn list(
|
||||
conn: &PgConnection,
|
||||
admin_person_id: Option<PersonId>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
let mut query = admin_purge_post::table
|
||||
.inner_join(person::table.on(admin_purge_post::admin_person_id.eq(person::id)))
|
||||
.inner_join(community::table)
|
||||
.select((
|
||||
admin_purge_post::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
Community::safe_columns_tuple(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(admin_person_id) = admin_person_id {
|
||||
query = query.filter(admin_purge_post::admin_person_id.eq(admin_person_id));
|
||||
};
|
||||
|
||||
let (limit, offset) = limit_and_offset(page, limit);
|
||||
|
||||
let res = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(admin_purge_post::when_.desc())
|
||||
.load::<AdminPurgePostViewTuple>(conn)?;
|
||||
|
||||
Ok(Self::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for AdminPurgePostView {
|
||||
type DbTuple = AdminPurgePostViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.iter()
|
||||
.map(|a| Self {
|
||||
admin_purge_post: a.0.to_owned(),
|
||||
admin: a.1.to_owned(),
|
||||
community: a.2.to_owned(),
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
drop table admin_purge_person;
|
||||
drop table admin_purge_community;
|
||||
drop table admin_purge_post;
|
||||
drop table admin_purge_comment;
|
@ -0,0 +1,31 @@
|
||||
-- Add the admin_purge tables
|
||||
|
||||
create table admin_purge_person (
|
||||
id serial primary key,
|
||||
admin_person_id int references person on update cascade on delete cascade not null,
|
||||
reason text,
|
||||
when_ timestamp not null default now()
|
||||
);
|
||||
|
||||
create table admin_purge_community (
|
||||
id serial primary key,
|
||||
admin_person_id int references person on update cascade on delete cascade not null,
|
||||
reason text,
|
||||
when_ timestamp not null default now()
|
||||
);
|
||||
|
||||
create table admin_purge_post (
|
||||
id serial primary key,
|
||||
admin_person_id int references person on update cascade on delete cascade not null,
|
||||
community_id int references community on update cascade on delete cascade not null,
|
||||
reason text,
|
||||
when_ timestamp not null default now()
|
||||
);
|
||||
|
||||
create table admin_purge_comment (
|
||||
id serial primary key,
|
||||
admin_person_id int references person on update cascade on delete cascade not null,
|
||||
post_id int references post on update cascade on delete cascade not null,
|
||||
reason text,
|
||||
when_ timestamp not null default now()
|
||||
);
|
Loading…
Reference in New Issue