mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 03:11:32 +00:00
* 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>
This commit is contained in:
parent
5b7376512f
commit
4e12e25c59
38 changed files with 1320 additions and 62 deletions
|
@ -70,6 +70,13 @@
|
|||
# activities synchronously for easier testing. Do not use in production.
|
||||
debug: false
|
||||
}
|
||||
# Pictrs image server configuration.
|
||||
pictrs_config: {
|
||||
# Address where pictrs is available (for image hosting)
|
||||
url: "string"
|
||||
# Set a custom pictrs API key. ( Required for deleting images )
|
||||
api_key: "string"
|
||||
}
|
||||
captcha: {
|
||||
# Whether captcha is required for signup
|
||||
enabled: false
|
||||
|
@ -108,8 +115,7 @@
|
|||
port: 8536
|
||||
# Whether the site is available over TLS. Needs to be true for federation to work.
|
||||
tls_enabled: true
|
||||
# Address where pictrs is available (for image hosting)
|
||||
pictrs_url: "http://localhost:8080"
|
||||
# A regex list of slurs to block / hide
|
||||
slur_filter: "(\bThis\b)|(\bis\b)|(\bsample\b)"
|
||||
# Maximum length of local community and user names
|
||||
actor_name_max_length: 20
|
||||
|
|
|
@ -104,6 +104,16 @@ pub async fn match_websocket_operation(
|
|||
UserOperation::SaveSiteConfig => {
|
||||
do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::PurgePerson => {
|
||||
do_websocket_operation::<PurgePerson>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::PurgeCommunity => {
|
||||
do_websocket_operation::<PurgeCommunity>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::PurgePost => do_websocket_operation::<PurgePost>(context, id, op, data).await,
|
||||
UserOperation::PurgeComment => {
|
||||
do_websocket_operation::<PurgeComment>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
|
||||
UserOperation::ResolveObject => {
|
||||
do_websocket_operation::<ResolveObject>(context, id, op, data).await
|
||||
|
|
|
@ -49,7 +49,13 @@ impl Perform for BanPerson {
|
|||
// Remove their data if that's desired
|
||||
let remove_data = data.remove_data.unwrap_or(false);
|
||||
if remove_data {
|
||||
remove_user_data(person.id, context.pool()).await?;
|
||||
remove_user_data(
|
||||
person.id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod config;
|
||||
mod leave_admin;
|
||||
mod mod_log;
|
||||
mod purge;
|
||||
mod registration_applications;
|
||||
mod resolve_object;
|
||||
mod search;
|
||||
|
|
|
@ -5,6 +5,10 @@ use lemmy_api_common::{
|
|||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||
};
|
||||
use lemmy_db_views_moderator::structs::{
|
||||
AdminPurgeCommentView,
|
||||
AdminPurgeCommunityView,
|
||||
AdminPurgePersonView,
|
||||
AdminPurgePostView,
|
||||
ModAddCommunityView,
|
||||
ModAddView,
|
||||
ModBanFromCommunityView,
|
||||
|
@ -83,17 +87,29 @@ impl Perform for GetModlog {
|
|||
.await??;
|
||||
|
||||
// These arrays are only for the full modlog, when a community isn't given
|
||||
let (removed_communities, banned, added) = if data.community_id.is_none() {
|
||||
let (
|
||||
removed_communities,
|
||||
banned,
|
||||
added,
|
||||
admin_purged_persons,
|
||||
admin_purged_communities,
|
||||
admin_purged_posts,
|
||||
admin_purged_comments,
|
||||
) = if data.community_id.is_none() {
|
||||
blocking(context.pool(), move |conn| {
|
||||
Ok((
|
||||
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||
ModBanView::list(conn, mod_person_id, page, limit)?,
|
||||
ModAddView::list(conn, mod_person_id, page, limit)?,
|
||||
AdminPurgePersonView::list(conn, mod_person_id, page, limit)?,
|
||||
AdminPurgeCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||
AdminPurgePostView::list(conn, mod_person_id, page, limit)?,
|
||||
AdminPurgeCommentView::list(conn, mod_person_id, page, limit)?,
|
||||
)) as Result<_, LemmyError>
|
||||
})
|
||||
.await??
|
||||
} else {
|
||||
(Vec::new(), Vec::new(), Vec::new())
|
||||
Default::default()
|
||||
};
|
||||
|
||||
// Return the jwt
|
||||
|
@ -108,6 +124,10 @@ impl Perform for GetModlog {
|
|||
added_to_community,
|
||||
added,
|
||||
transferred_to_community,
|
||||
admin_purged_persons,
|
||||
admin_purged_communities,
|
||||
admin_purged_posts,
|
||||
admin_purged_comments,
|
||||
hidden_communities,
|
||||
})
|
||||
}
|
||||
|
|
63
crates/api/src/site/purge/comment.rs
Normal file
63
crates/api/src/site/purge/comment.rs
Normal file
|
@ -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 })
|
||||
}
|
||||
}
|
82
crates/api/src/site/purge/community.rs
Normal file
82
crates/api/src/site/purge/community.rs
Normal file
|
@ -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 })
|
||||
}
|
||||
}
|
4
crates/api/src/site/purge/mod.rs
Normal file
4
crates/api/src/site/purge/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
mod comment;
|
||||
mod community;
|
||||
mod person;
|
||||
mod post;
|
75
crates/api/src/site/purge/person.rs
Normal file
75
crates/api/src/site/purge/person.rs
Normal file
|
@ -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 })
|
||||
}
|
||||
}
|
72
crates/api/src/site/purge/post.rs
Normal file
72
crates/api/src/site/purge/post.rs
Normal file
|
@ -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 })
|
||||
}
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
use crate::post::SiteMetadata;
|
||||
use encoding::{all::encodings, DecoderTrap};
|
||||
use lemmy_db_schema::newtypes::DbUrl;
|
||||
use lemmy_utils::{error::LemmyError, settings::structs::Settings, version::VERSION};
|
||||
use lemmy_utils::{
|
||||
error::LemmyError,
|
||||
settings::structs::Settings,
|
||||
version::VERSION,
|
||||
REQWEST_TIMEOUT,
|
||||
};
|
||||
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use serde::Deserialize;
|
||||
|
@ -105,32 +110,75 @@ pub(crate) struct PictrsFile {
|
|||
delete_token: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub(crate) struct PictrsPurgeResponse {
|
||||
msg: String,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn fetch_pictrs(
|
||||
client: &ClientWithMiddleware,
|
||||
settings: &Settings,
|
||||
image_url: &Url,
|
||||
) -> Result<PictrsResponse, LemmyError> {
|
||||
if let Some(pictrs_url) = settings.pictrs_url.to_owned() {
|
||||
is_image_content_type(client, image_url).await?;
|
||||
let pictrs_config = settings.pictrs_config()?;
|
||||
is_image_content_type(client, image_url).await?;
|
||||
|
||||
let fetch_url = format!(
|
||||
"{}/image/download?url={}",
|
||||
pictrs_url,
|
||||
utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
|
||||
);
|
||||
let fetch_url = format!(
|
||||
"{}/image/download?url={}",
|
||||
pictrs_config.url,
|
||||
utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
|
||||
);
|
||||
|
||||
let response = client.get(&fetch_url).send().await?;
|
||||
let response = client
|
||||
.get(&fetch_url)
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response: PictrsResponse = response.json().await.map_err(LemmyError::from)?;
|
||||
let response: PictrsResponse = response.json().await.map_err(LemmyError::from)?;
|
||||
|
||||
if response.msg == "ok" {
|
||||
Ok(response)
|
||||
} else {
|
||||
Err(LemmyError::from_message(&response.msg))
|
||||
}
|
||||
if response.msg == "ok" {
|
||||
Ok(response)
|
||||
} else {
|
||||
Err(LemmyError::from_message("pictrs_url not set up in config"))
|
||||
Err(LemmyError::from_message(&response.msg))
|
||||
}
|
||||
}
|
||||
|
||||
/// Purges an image from pictrs
|
||||
/// Note: This should often be coerced from a Result to .ok() in order to fail softly, because:
|
||||
/// - It might fail due to image being not local
|
||||
/// - It might not be an image
|
||||
/// - Pictrs might not be set up
|
||||
pub async fn purge_image_from_pictrs(
|
||||
client: &ClientWithMiddleware,
|
||||
settings: &Settings,
|
||||
image_url: &Url,
|
||||
) -> Result<(), LemmyError> {
|
||||
let pictrs_config = settings.pictrs_config()?;
|
||||
is_image_content_type(client, image_url).await?;
|
||||
|
||||
let alias = image_url
|
||||
.path_segments()
|
||||
.ok_or_else(|| LemmyError::from_message("Image URL missing path segments"))?
|
||||
.next_back()
|
||||
.ok_or_else(|| LemmyError::from_message("Image URL missing last path segment"))?;
|
||||
|
||||
let purge_url = format!("{}/internal/purge?alias={}", pictrs_config.url, alias);
|
||||
|
||||
let response = client
|
||||
.post(&purge_url)
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.header("x-api-token", pictrs_config.api_key)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let response: PictrsPurgeResponse = response.json().await.map_err(LemmyError::from)?;
|
||||
|
||||
if response.msg == "ok" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(LemmyError::from_message(&response.msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::sensitive::Sensitive;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, PersonId},
|
||||
newtypes::{CommentId, CommunityId, PersonId, PostId},
|
||||
ListingType,
|
||||
SearchType,
|
||||
SortType,
|
||||
|
@ -21,6 +21,10 @@ use lemmy_db_views_actor::structs::{
|
|||
PersonViewSafe,
|
||||
};
|
||||
use lemmy_db_views_moderator::structs::{
|
||||
AdminPurgeCommentView,
|
||||
AdminPurgeCommunityView,
|
||||
AdminPurgePersonView,
|
||||
AdminPurgePostView,
|
||||
ModAddCommunityView,
|
||||
ModAddView,
|
||||
ModBanFromCommunityView,
|
||||
|
@ -93,6 +97,10 @@ pub struct GetModlogResponse {
|
|||
pub added_to_community: Vec<ModAddCommunityView>,
|
||||
pub transferred_to_community: Vec<ModTransferCommunityView>,
|
||||
pub added: Vec<ModAddView>,
|
||||
pub admin_purged_persons: Vec<AdminPurgePersonView>,
|
||||
pub admin_purged_communities: Vec<AdminPurgeCommunityView>,
|
||||
pub admin_purged_posts: Vec<AdminPurgePostView>,
|
||||
pub admin_purged_comments: Vec<AdminPurgeCommentView>,
|
||||
pub hidden_communities: Vec<ModHideCommunityView>,
|
||||
}
|
||||
|
||||
|
@ -194,6 +202,39 @@ pub struct FederatedInstances {
|
|||
pub blocked: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PurgePerson {
|
||||
pub person_id: PersonId,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PurgeCommunity {
|
||||
pub community_id: CommunityId,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PurgePost {
|
||||
pub post_id: PostId,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PurgeComment {
|
||||
pub comment_id: CommentId,
|
||||
pub reason: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PurgeItemResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
pub struct ListRegistrationApplications {
|
||||
/// Only shows the unread applications (IE those without an admin actor)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{sensitive::Sensitive, site::FederatedInstances};
|
||||
use crate::{request::purge_image_from_pictrs, sensitive::Sensitive, site::FederatedInstances};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
||||
source::{
|
||||
|
@ -32,6 +32,7 @@ use lemmy_utils::{
|
|||
settings::structs::Settings,
|
||||
utils::generate_random_string,
|
||||
};
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
use rosetta_i18n::{Language, LanguageId};
|
||||
use tracing::warn;
|
||||
|
||||
|
@ -505,13 +506,98 @@ pub async fn check_private_instance_and_federation_enabled(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_user_data(banned_person_id: PersonId, pool: &DbPool) -> Result<(), LemmyError> {
|
||||
pub async fn purge_image_posts_for_person(
|
||||
banned_person_id: PersonId,
|
||||
pool: &DbPool,
|
||||
settings: &Settings,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<(), LemmyError> {
|
||||
let posts = blocking(pool, move |conn: &'_ _| {
|
||||
Post::fetch_pictrs_posts_for_creator(conn, banned_person_id)
|
||||
})
|
||||
.await??;
|
||||
for post in posts {
|
||||
if let Some(url) = post.url {
|
||||
purge_image_from_pictrs(client, settings, &url).await.ok();
|
||||
}
|
||||
if let Some(thumbnail_url) = post.thumbnail_url {
|
||||
purge_image_from_pictrs(client, settings, &thumbnail_url)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
blocking(pool, move |conn| {
|
||||
Post::remove_pictrs_post_images_and_thumbnails_for_creator(conn, banned_person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn purge_image_posts_for_community(
|
||||
banned_community_id: CommunityId,
|
||||
pool: &DbPool,
|
||||
settings: &Settings,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<(), LemmyError> {
|
||||
let posts = blocking(pool, move |conn: &'_ _| {
|
||||
Post::fetch_pictrs_posts_for_community(conn, banned_community_id)
|
||||
})
|
||||
.await??;
|
||||
for post in posts {
|
||||
if let Some(url) = post.url {
|
||||
purge_image_from_pictrs(client, settings, &url).await.ok();
|
||||
}
|
||||
if let Some(thumbnail_url) = post.thumbnail_url {
|
||||
purge_image_from_pictrs(client, settings, &thumbnail_url)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
blocking(pool, move |conn| {
|
||||
Post::remove_pictrs_post_images_and_thumbnails_for_community(conn, banned_community_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_user_data(
|
||||
banned_person_id: PersonId,
|
||||
pool: &DbPool,
|
||||
settings: &Settings,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<(), LemmyError> {
|
||||
// Purge user images
|
||||
let person = blocking(pool, move |conn| Person::read(conn, banned_person_id)).await??;
|
||||
if let Some(avatar) = person.avatar {
|
||||
purge_image_from_pictrs(client, settings, &avatar)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
if let Some(banner) = person.banner {
|
||||
purge_image_from_pictrs(client, settings, &banner)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
// Update the fields to None
|
||||
blocking(pool, move |conn| {
|
||||
Person::remove_avatar_and_banner(conn, banned_person_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Posts
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Post::update_removed_for_creator(conn, banned_person_id, None, true)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Purge image posts
|
||||
purge_image_posts_for_person(banned_person_id, pool, settings, client).await?;
|
||||
|
||||
// Communities
|
||||
// Remove all communities where they're the top mod
|
||||
// for now, remove the communities manually
|
||||
|
@ -527,8 +613,24 @@ pub async fn remove_user_data(banned_person_id: PersonId, pool: &DbPool) -> Resu
|
|||
.collect();
|
||||
|
||||
for first_mod_community in banned_user_first_communities {
|
||||
let community_id = first_mod_community.community.id;
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Community::update_removed(conn, first_mod_community.community.id, true)
|
||||
Community::update_removed(conn, community_id, true)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Delete the community images
|
||||
if let Some(icon) = first_mod_community.community.icon {
|
||||
purge_image_from_pictrs(client, settings, &icon).await.ok();
|
||||
}
|
||||
if let Some(banner) = first_mod_community.community.banner {
|
||||
purge_image_from_pictrs(client, settings, &banner)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
// Update the fields to None
|
||||
blocking(pool, move |conn| {
|
||||
Community::remove_avatar_and_banner(conn, community_id)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
|
@ -575,7 +677,26 @@ pub async fn remove_user_data_in_community(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_user_account(person_id: PersonId, pool: &DbPool) -> Result<(), LemmyError> {
|
||||
pub async fn delete_user_account(
|
||||
person_id: PersonId,
|
||||
pool: &DbPool,
|
||||
settings: &Settings,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Result<(), LemmyError> {
|
||||
// Delete their images
|
||||
let person = blocking(pool, move |conn| Person::read(conn, person_id)).await??;
|
||||
if let Some(avatar) = person.avatar {
|
||||
purge_image_from_pictrs(client, settings, &avatar)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
if let Some(banner) = person.banner {
|
||||
purge_image_from_pictrs(client, settings, &banner)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
// No need to update avatar and banner, those are handled in Person::delete_account
|
||||
|
||||
// Comments
|
||||
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
|
||||
blocking(pool, permadelete)
|
||||
|
@ -588,6 +709,9 @@ pub async fn delete_user_account(person_id: PersonId, pool: &DbPool) -> Result<(
|
|||
.await?
|
||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
|
||||
|
||||
// Purge image posts
|
||||
purge_image_posts_for_person(person_id, pool, settings, client).await?;
|
||||
|
||||
blocking(pool, move |conn| Person::delete_account(conn, person_id)).await??;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -33,7 +33,13 @@ impl PerformCrud for DeleteAccount {
|
|||
return Err(LemmyError::from_message("password_incorrect"));
|
||||
}
|
||||
|
||||
delete_user_account(local_user_view.person.id, context.pool()).await?;
|
||||
delete_user_account(
|
||||
local_user_view.person.id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
DeleteUser::send(&local_user_view.person.into(), context).await?;
|
||||
|
||||
Ok(DeleteAccountResponse {})
|
||||
|
|
|
@ -181,7 +181,13 @@ impl ActivityHandler for BlockUser {
|
|||
})
|
||||
.await??;
|
||||
if self.remove_data.unwrap_or(false) {
|
||||
remove_user_data(blocked_person.id, context.pool()).await?;
|
||||
remove_user_data(
|
||||
blocked_person.id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// write mod log
|
||||
|
|
|
@ -51,7 +51,13 @@ impl ActivityHandler for DeleteUser {
|
|||
.actor
|
||||
.dereference(context, local_instance(context), request_counter)
|
||||
.await?;
|
||||
delete_user_account(actor.id, context.pool()).await?;
|
||||
delete_user_account(
|
||||
actor.id,
|
||||
context.pool(),
|
||||
&context.settings(),
|
||||
context.client(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -138,6 +138,19 @@ impl Community {
|
|||
.set(community_form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn remove_avatar_and_banner(
|
||||
conn: &PgConnection,
|
||||
community_id: CommunityId,
|
||||
) -> Result<Self, Error> {
|
||||
use crate::schema::community::dsl::*;
|
||||
diesel::update(community.find(community_id))
|
||||
.set((
|
||||
icon.eq::<Option<String>>(None),
|
||||
banner.eq::<Option<String>>(None),
|
||||
))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Joinable for CommunityModerator {
|
||||
|
|
|
@ -263,6 +263,98 @@ impl Crud for ModAdd {
|
|||
}
|
||||
}
|
||||
|
||||
impl Crud for AdminPurgePerson {
|
||||
type Form = AdminPurgePersonForm;
|
||||
type IdType = i32;
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_person::dsl::*;
|
||||
admin_purge_person.find(from_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_person::dsl::*;
|
||||
insert_into(admin_purge_person)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, from_id: i32, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_person::dsl::*;
|
||||
diesel::update(admin_purge_person.find(from_id))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Crud for AdminPurgeCommunity {
|
||||
type Form = AdminPurgeCommunityForm;
|
||||
type IdType = i32;
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_community::dsl::*;
|
||||
admin_purge_community.find(from_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_community::dsl::*;
|
||||
insert_into(admin_purge_community)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, from_id: i32, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_community::dsl::*;
|
||||
diesel::update(admin_purge_community.find(from_id))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Crud for AdminPurgePost {
|
||||
type Form = AdminPurgePostForm;
|
||||
type IdType = i32;
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_post::dsl::*;
|
||||
admin_purge_post.find(from_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_post::dsl::*;
|
||||
insert_into(admin_purge_post)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, from_id: i32, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_post::dsl::*;
|
||||
diesel::update(admin_purge_post.find(from_id))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Crud for AdminPurgeComment {
|
||||
type Form = AdminPurgeCommentForm;
|
||||
type IdType = i32;
|
||||
fn read(conn: &PgConnection, from_id: i32) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_comment::dsl::*;
|
||||
admin_purge_comment.find(from_id).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_comment::dsl::*;
|
||||
insert_into(admin_purge_comment)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, from_id: i32, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::admin_purge_comment::dsl::*;
|
||||
diesel::update(admin_purge_comment.find(from_id))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
|
|
|
@ -228,6 +228,8 @@ impl Person {
|
|||
diesel::update(person.find(person_id))
|
||||
.set((
|
||||
display_name.eq::<Option<String>>(None),
|
||||
avatar.eq::<Option<String>>(None),
|
||||
banner.eq::<Option<String>>(None),
|
||||
bio.eq::<Option<String>>(None),
|
||||
matrix_user_id.eq::<Option<String>>(None),
|
||||
deleted.eq(true),
|
||||
|
@ -265,6 +267,15 @@ impl Person {
|
|||
.set(admin.eq(false))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn remove_avatar_and_banner(conn: &PgConnection, person_id: PersonId) -> Result<Self, Error> {
|
||||
diesel::update(person.find(person_id))
|
||||
.set((
|
||||
avatar.eq::<Option<String>>(None),
|
||||
banner.eq::<Option<String>>(None),
|
||||
))
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl PersonSafe {
|
||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
|||
traits::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable},
|
||||
utils::naive_now,
|
||||
};
|
||||
use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
|
||||
use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl, *};
|
||||
use url::Url;
|
||||
|
||||
impl Crud for Post {
|
||||
|
@ -174,6 +174,71 @@ impl Post {
|
|||
.map(Into::into),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn fetch_pictrs_posts_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: PersonId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
let pictrs_search = "%pictrs/image%";
|
||||
|
||||
post
|
||||
.filter(creator_id.eq(for_creator_id))
|
||||
.filter(url.like(pictrs_search))
|
||||
.load::<Self>(conn)
|
||||
}
|
||||
|
||||
/// Sets the url and thumbnails fields to None
|
||||
pub fn remove_pictrs_post_images_and_thumbnails_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: PersonId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
let pictrs_search = "%pictrs/image%";
|
||||
|
||||
diesel::update(
|
||||
post
|
||||
.filter(creator_id.eq(for_creator_id))
|
||||
.filter(url.like(pictrs_search)),
|
||||
)
|
||||
.set((
|
||||
url.eq::<Option<String>>(None),
|
||||
thumbnail_url.eq::<Option<String>>(None),
|
||||
))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn fetch_pictrs_posts_for_community(
|
||||
conn: &PgConnection,
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
let pictrs_search = "%pictrs/image%";
|
||||
post
|
||||
.filter(community_id.eq(for_community_id))
|
||||
.filter(url.like(pictrs_search))
|
||||
.load::<Self>(conn)
|
||||
}
|
||||
|
||||
/// Sets the url and thumbnails fields to None
|
||||
pub fn remove_pictrs_post_images_and_thumbnails_for_community(
|
||||
conn: &PgConnection,
|
||||
for_community_id: CommunityId,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
let pictrs_search = "%pictrs/image%";
|
||||
|
||||
diesel::update(
|
||||
post
|
||||
.filter(community_id.eq(for_community_id))
|
||||
.filter(url.like(pictrs_search)),
|
||||
)
|
||||
.set((
|
||||
url.eq::<Option<String>>(None),
|
||||
thumbnail_url.eq::<Option<String>>(None),
|
||||
))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
impl Likeable for PostLike {
|
||||
|
|
|
@ -577,6 +577,16 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
admin_purge_comment (id) {
|
||||
id -> Int4,
|
||||
admin_person_id -> Int4,
|
||||
post_id -> Int4,
|
||||
reason -> Nullable<Text>,
|
||||
when_ -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
email_verification (id) {
|
||||
id -> Int4,
|
||||
|
@ -587,6 +597,34 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
admin_purge_community (id) {
|
||||
id -> Int4,
|
||||
admin_person_id -> Int4,
|
||||
reason -> Nullable<Text>,
|
||||
when_ -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
admin_purge_person (id) {
|
||||
id -> Int4,
|
||||
admin_person_id -> Int4,
|
||||
reason -> Nullable<Text>,
|
||||
when_ -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
admin_purge_post (id) {
|
||||
id -> Int4,
|
||||
admin_person_id -> Int4,
|
||||
community_id -> Int4,
|
||||
reason -> Nullable<Text>,
|
||||
when_ -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
registration_application (id) {
|
||||
id -> Int4,
|
||||
|
@ -675,6 +713,13 @@ joinable!(registration_application -> person (admin_id));
|
|||
joinable!(mod_hide_community -> person (mod_person_id));
|
||||
joinable!(mod_hide_community -> community (community_id));
|
||||
|
||||
joinable!(admin_purge_comment -> person (admin_person_id));
|
||||
joinable!(admin_purge_comment -> post (post_id));
|
||||
joinable!(admin_purge_community -> person (admin_person_id));
|
||||
joinable!(admin_purge_person -> person (admin_person_id));
|
||||
joinable!(admin_purge_post -> community (community_id));
|
||||
joinable!(admin_purge_post -> person (admin_person_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
activity,
|
||||
comment,
|
||||
|
@ -718,6 +763,10 @@ allow_tables_to_appear_in_same_query!(
|
|||
comment_alias_1,
|
||||
person_alias_1,
|
||||
person_alias_2,
|
||||
admin_purge_comment,
|
||||
admin_purge_community,
|
||||
admin_purge_person,
|
||||
admin_purge_post,
|
||||
email_verification,
|
||||
registration_application
|
||||
);
|
||||
|
|
|
@ -3,6 +3,10 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::{
|
||||
admin_purge_comment,
|
||||
admin_purge_community,
|
||||
admin_purge_person,
|
||||
admin_purge_post,
|
||||
mod_add,
|
||||
mod_add_community,
|
||||
mod_ban,
|
||||
|
@ -247,3 +251,75 @@ pub struct ModAddForm {
|
|||
pub other_person_id: PersonId,
|
||||
pub removed: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_person")]
|
||||
pub struct AdminPurgePerson {
|
||||
pub id: i32,
|
||||
pub admin_person_id: PersonId,
|
||||
pub reason: Option<String>,
|
||||
pub when_: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_person")]
|
||||
pub struct AdminPurgePersonForm {
|
||||
pub admin_person_id: PersonId,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_community")]
|
||||
pub struct AdminPurgeCommunity {
|
||||
pub id: i32,
|
||||
pub admin_person_id: PersonId,
|
||||
pub reason: Option<String>,
|
||||
pub when_: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_community")]
|
||||
pub struct AdminPurgeCommunityForm {
|
||||
pub admin_person_id: PersonId,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_post")]
|
||||
pub struct AdminPurgePost {
|
||||
pub id: i32,
|
||||
pub admin_person_id: PersonId,
|
||||
pub community_id: CommunityId,
|
||||
pub reason: Option<String>,
|
||||
pub when_: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_post")]
|
||||
pub struct AdminPurgePostForm {
|
||||
pub admin_person_id: PersonId,
|
||||
pub community_id: CommunityId,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "full", derive(Queryable, Identifiable))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_comment")]
|
||||
pub struct AdminPurgeComment {
|
||||
pub id: i32,
|
||||
pub admin_person_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub reason: Option<String>,
|
||||
pub when_: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||
#[cfg_attr(feature = "full", table_name = "admin_purge_comment")]
|
||||
pub struct AdminPurgeCommentForm {
|
||||
pub admin_person_id: PersonId,
|
||||
pub post_id: PostId,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
|
62
crates/db_views_moderator/src/admin_purge_comment_view.rs
Normal file
62
crates/db_views_moderator/src/admin_purge_comment_view.rs
Normal file
|
@ -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>>()
|
||||
}
|
||||
}
|
58
crates/db_views_moderator/src/admin_purge_community_view.rs
Normal file
58
crates/db_views_moderator/src/admin_purge_community_view.rs
Normal file
|
@ -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>>()
|
||||
}
|
||||
}
|
58
crates/db_views_moderator/src/admin_purge_person_view.rs
Normal file
58
crates/db_views_moderator/src/admin_purge_person_view.rs
Normal file
|
@ -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>>()
|
||||
}
|
||||
}
|
62
crates/db_views_moderator/src/admin_purge_post_view.rs
Normal file
62
crates/db_views_moderator/src/admin_purge_post_view.rs
Normal file
|
@ -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>>()
|
||||
}
|
||||
}
|
|
@ -1,4 +1,12 @@
|
|||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_comment_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_community_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_person_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_post_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod mod_add_community_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod mod_add_view;
|
||||
|
|
|
@ -2,6 +2,10 @@ use lemmy_db_schema::source::{
|
|||
comment::Comment,
|
||||
community::CommunitySafe,
|
||||
moderator::{
|
||||
AdminPurgeComment,
|
||||
AdminPurgeCommunity,
|
||||
AdminPurgePerson,
|
||||
AdminPurgePost,
|
||||
ModAdd,
|
||||
ModAddCommunity,
|
||||
ModBan,
|
||||
|
@ -104,3 +108,29 @@ pub struct ModTransferCommunityView {
|
|||
pub community: CommunitySafe,
|
||||
pub modded_person: PersonSafeAlias1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AdminPurgeCommentView {
|
||||
pub admin_purge_comment: AdminPurgeComment,
|
||||
pub admin: PersonSafe,
|
||||
pub post: Post,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AdminPurgeCommunityView {
|
||||
pub admin_purge_community: AdminPurgeCommunity,
|
||||
pub admin: PersonSafe,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AdminPurgePersonView {
|
||||
pub admin_purge_person: AdminPurgePerson,
|
||||
pub admin: PersonSafe,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct AdminPurgePostView {
|
||||
pub admin_purge_post: AdminPurgePost,
|
||||
pub admin: PersonSafe,
|
||||
pub community: CommunitySafe,
|
||||
}
|
||||
|
|
|
@ -10,9 +10,8 @@ use actix_web::{
|
|||
HttpRequest,
|
||||
HttpResponse,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use futures::stream::{Stream, StreamExt};
|
||||
use lemmy_utils::{claims::Claims, error::LemmyError, rate_limit::RateLimit};
|
||||
use lemmy_utils::{claims::Claims, rate_limit::RateLimit, REQWEST_TIMEOUT};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use reqwest::Body;
|
||||
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
||||
|
@ -28,7 +27,8 @@ pub fn config(cfg: &mut web::ServiceConfig, client: ClientWithMiddleware, rate_l
|
|||
)
|
||||
// This has optional query params: /image/{filename}?format=jpg&thumbnail=256
|
||||
.service(web::resource("/pictrs/image/{filename}").route(web::get().to(full_res)))
|
||||
.service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)));
|
||||
.service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)))
|
||||
.service(web::resource("/pictrs/internal/purge").route(web::post().to(purge)));
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
|
@ -49,6 +49,14 @@ struct PictrsParams {
|
|||
thumbnail: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
enum PictrsPurgeParams {
|
||||
#[serde(rename = "file")]
|
||||
File(String),
|
||||
#[serde(rename = "alias")]
|
||||
Alias(String),
|
||||
}
|
||||
|
||||
fn adapt_request(
|
||||
request: &HttpRequest,
|
||||
client: &ClientWithMiddleware,
|
||||
|
@ -57,7 +65,9 @@ fn adapt_request(
|
|||
// remove accept-encoding header so that pictrs doesnt compress the response
|
||||
const INVALID_HEADERS: &[HeaderName] = &[ACCEPT_ENCODING, HOST];
|
||||
|
||||
let client_request = client.request(request.method().clone(), url);
|
||||
let client_request = client
|
||||
.request(request.method().clone(), url)
|
||||
.timeout(REQWEST_TIMEOUT);
|
||||
|
||||
request
|
||||
.headers()
|
||||
|
@ -86,7 +96,8 @@ async fn upload(
|
|||
return Ok(HttpResponse::Unauthorized().finish());
|
||||
};
|
||||
|
||||
let image_url = format!("{}/image", pictrs_url(context.settings().pictrs_url)?);
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
let image_url = format!("{}/image", pictrs_config.url);
|
||||
|
||||
let mut client_req = adapt_request(&req, &client, image_url);
|
||||
|
||||
|
@ -116,22 +127,16 @@ async fn full_res(
|
|||
let name = &filename.into_inner();
|
||||
|
||||
// If there are no query params, the URL is original
|
||||
let pictrs_url_settings = context.settings().pictrs_url;
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
let url = if params.format.is_none() && params.thumbnail.is_none() {
|
||||
format!(
|
||||
"{}/image/original/{}",
|
||||
pictrs_url(pictrs_url_settings)?,
|
||||
name,
|
||||
)
|
||||
format!("{}/image/original/{}", pictrs_config.url, name,)
|
||||
} else {
|
||||
// Use jpg as a default when none is given
|
||||
let format = params.format.unwrap_or_else(|| "jpg".to_string());
|
||||
|
||||
let mut url = format!(
|
||||
"{}/image/process.{}?src={}",
|
||||
pictrs_url(pictrs_url_settings)?,
|
||||
format,
|
||||
name,
|
||||
pictrs_config.url, format, name,
|
||||
);
|
||||
|
||||
if let Some(size) = params.thumbnail {
|
||||
|
@ -181,12 +186,8 @@ async fn delete(
|
|||
) -> Result<HttpResponse, Error> {
|
||||
let (token, file) = components.into_inner();
|
||||
|
||||
let url = format!(
|
||||
"{}/image/delete/{}/{}",
|
||||
pictrs_url(context.settings().pictrs_url)?,
|
||||
&token,
|
||||
&file
|
||||
);
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
let url = format!("{}/image/delete/{}/{}", pictrs_config.url, &token, &file);
|
||||
|
||||
let mut client_req = adapt_request(&req, &client, url);
|
||||
|
||||
|
@ -199,8 +200,32 @@ async fn delete(
|
|||
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res.bytes_stream())))
|
||||
}
|
||||
|
||||
fn pictrs_url(pictrs_url: Option<String>) -> Result<String, LemmyError> {
|
||||
pictrs_url.ok_or_else(|| anyhow!("images_disabled").into())
|
||||
async fn purge(
|
||||
web::Query(params): web::Query<PictrsPurgeParams>,
|
||||
req: HttpRequest,
|
||||
client: web::Data<ClientWithMiddleware>,
|
||||
context: web::Data<LemmyContext>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let purge_string = match params {
|
||||
PictrsPurgeParams::File(f) => format!("file={}", f),
|
||||
PictrsPurgeParams::Alias(a) => format!("alias={}", a),
|
||||
};
|
||||
|
||||
let pictrs_config = context.settings().pictrs_config()?;
|
||||
let url = format!("{}/internal/purge?{}", pictrs_config.url, &purge_string);
|
||||
|
||||
let mut client_req = adapt_request(&req, &client, url);
|
||||
|
||||
if let Some(addr) = req.head().peer_addr {
|
||||
client_req = client_req.header("X-Forwarded-For", addr.to_string())
|
||||
}
|
||||
|
||||
// Add the API token, X-Api-Token header
|
||||
client_req = client_req.header("x-api-token", pictrs_config.api_key);
|
||||
|
||||
let res = client_req.send().await.map_err(error::ErrorBadRequest)?;
|
||||
|
||||
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res.bytes_stream())))
|
||||
}
|
||||
|
||||
fn make_send<S>(mut stream: S) -> impl Stream<Item = S::Item> + Send + Unpin + 'static
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
use crate::{error::LemmyError, location_info, settings::structs::Settings};
|
||||
use crate::{
|
||||
error::LemmyError,
|
||||
location_info,
|
||||
settings::structs::{PictrsConfig, Settings},
|
||||
};
|
||||
use anyhow::{anyhow, Context};
|
||||
use deser_hjson::from_str;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -116,4 +120,11 @@ impl Settings {
|
|||
.expect("compile regex")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pictrs_config(&self) -> Result<PictrsConfig, LemmyError> {
|
||||
self
|
||||
.pictrs_config
|
||||
.to_owned()
|
||||
.ok_or_else(|| anyhow!("images_disabled").into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,9 @@ pub struct Settings {
|
|||
/// Settings related to activitypub federation
|
||||
#[default(FederationConfig::default())]
|
||||
pub federation: FederationConfig,
|
||||
/// Pictrs image server configuration.
|
||||
#[default(None)]
|
||||
pub(crate) pictrs_config: Option<PictrsConfig>,
|
||||
#[default(CaptchaConfig::default())]
|
||||
pub captcha: CaptchaConfig,
|
||||
/// Email sending configuration. All options except login/password are mandatory
|
||||
|
@ -36,12 +39,9 @@ pub struct Settings {
|
|||
/// Whether the site is available over TLS. Needs to be true for federation to work.
|
||||
#[default(true)]
|
||||
pub tls_enabled: bool,
|
||||
/// Address where pictrs is available (for image hosting)
|
||||
#[default(None)]
|
||||
#[doku(example = "http://localhost:8080")]
|
||||
pub pictrs_url: Option<String>,
|
||||
#[default(None)]
|
||||
#[doku(example = "(\\bThis\\b)|(\\bis\\b)|(\\bsample\\b)")]
|
||||
/// A regex list of slurs to block / hide
|
||||
pub slur_filter: Option<String>,
|
||||
/// Maximum length of local community and user names
|
||||
#[default(20)]
|
||||
|
@ -56,6 +56,18 @@ pub struct Settings {
|
|||
pub opentelemetry_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)]
|
||||
#[serde(default)]
|
||||
pub struct PictrsConfig {
|
||||
/// Address where pictrs is available (for image hosting)
|
||||
#[default("http://pictrs:8080")]
|
||||
pub url: String,
|
||||
|
||||
/// Set a custom pictrs API key. ( Required for deleting images )
|
||||
#[default("API_KEY")]
|
||||
pub api_key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)]
|
||||
#[serde(default)]
|
||||
pub struct CaptchaConfig {
|
||||
|
|
|
@ -142,6 +142,10 @@ pub enum UserOperation {
|
|||
GetSiteMetadata,
|
||||
BlockCommunity,
|
||||
BlockPerson,
|
||||
PurgePerson,
|
||||
PurgeCommunity,
|
||||
PurgePost,
|
||||
PurgeComment,
|
||||
}
|
||||
|
||||
#[derive(EnumString, Display, Debug, Clone)]
|
||||
|
|
|
@ -56,8 +56,10 @@ services:
|
|||
user: 991:991
|
||||
environment:
|
||||
- PICTRS_OPENTELEMETRY_URL=http://otel:4137
|
||||
- PICTRS__API_KEY=API_KEY
|
||||
ports:
|
||||
- "6670:6669"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./volumes/pictrs:/mnt
|
||||
restart: always
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
# port where lemmy should listen for incoming requests
|
||||
port: 8536
|
||||
# settings related to the postgresql database
|
||||
# address where pictrs is available
|
||||
pictrs_url: "http://pictrs:8080"
|
||||
pictrs_config: {
|
||||
url: "http://pictrs:8080"
|
||||
api_key: "API_KEY"
|
||||
}
|
||||
database: {
|
||||
# name of the postgres database for lemmy
|
||||
database: "lemmy"
|
||||
|
|
4
migrations/2021-10-01-141650_create_admin_purge/down.sql
Normal file
4
migrations/2021-10-01-141650_create_admin_purge/down.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
drop table admin_purge_person;
|
||||
drop table admin_purge_community;
|
||||
drop table admin_purge_post;
|
||||
drop table admin_purge_comment;
|
31
migrations/2021-10-01-141650_create_admin_purge/up.sql
Normal file
31
migrations/2021-10-01-141650_create_admin_purge/up.sql
Normal file
|
@ -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()
|
||||
);
|
|
@ -232,6 +232,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
"/registration_application/approve",
|
||||
web::put().to(route_post::<ApproveRegistrationApplication>),
|
||||
),
|
||||
)
|
||||
.service(
|
||||
web::scope("/admin/purge")
|
||||
.wrap(rate_limit.message())
|
||||
.route("/person", web::post().to(route_post::<PurgePerson>))
|
||||
.route("/community", web::post().to(route_post::<PurgeCommunity>))
|
||||
.route("/post", web::post().to(route_post::<PurgePost>))
|
||||
.route("/comment", web::post().to(route_post::<PurgeComment>)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -99,7 +99,7 @@ async fn main() -> Result<(), LemmyError> {
|
|||
settings.bind, settings.port
|
||||
);
|
||||
|
||||
let client = Client::builder()
|
||||
let reqwest_client = Client::builder()
|
||||
.user_agent(build_user_agent(&settings))
|
||||
.timeout(REQWEST_TIMEOUT)
|
||||
.build()?;
|
||||
|
@ -111,11 +111,16 @@ async fn main() -> Result<(), LemmyError> {
|
|||
backoff_exponent: 2,
|
||||
};
|
||||
|
||||
let client = ClientBuilder::new(client)
|
||||
let client = ClientBuilder::new(reqwest_client.clone())
|
||||
.with(TracingMiddleware)
|
||||
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
|
||||
.build();
|
||||
|
||||
// Pictrs cannot use the retry middleware
|
||||
let pictrs_client = ClientBuilder::new(reqwest_client.clone())
|
||||
.with(TracingMiddleware)
|
||||
.build();
|
||||
|
||||
check_private_instance_and_federation_enabled(&pool, &settings).await?;
|
||||
|
||||
let chat_server = ChatServer::startup(
|
||||
|
@ -149,7 +154,7 @@ async fn main() -> Result<(), LemmyError> {
|
|||
.configure(|cfg| api_routes::config(cfg, &rate_limiter))
|
||||
.configure(|cfg| lemmy_apub::http::routes::config(cfg, &settings))
|
||||
.configure(feeds::config)
|
||||
.configure(|cfg| images::config(cfg, client.clone(), &rate_limiter))
|
||||
.configure(|cfg| images::config(cfg, pictrs_client.clone(), &rate_limiter))
|
||||
.configure(nodeinfo::config)
|
||||
.configure(|cfg| webfinger::config(cfg, &settings))
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue