mirror of https://github.com/LemmyNet/lemmy.git
* 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
|
@ -70,6 +70,13 @@
|
||||||
# activities synchronously for easier testing. Do not use in production.
|
# activities synchronously for easier testing. Do not use in production.
|
||||||
debug: false
|
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: {
|
captcha: {
|
||||||
# Whether captcha is required for signup
|
# Whether captcha is required for signup
|
||||||
enabled: false
|
enabled: false
|
||||||
|
@ -108,8 +115,7 @@
|
||||||
port: 8536
|
port: 8536
|
||||||
# Whether the site is available over TLS. Needs to be true for federation to work.
|
# Whether the site is available over TLS. Needs to be true for federation to work.
|
||||||
tls_enabled: true
|
tls_enabled: true
|
||||||
# Address where pictrs is available (for image hosting)
|
# A regex list of slurs to block / hide
|
||||||
pictrs_url: "http://localhost:8080"
|
|
||||||
slur_filter: "(\bThis\b)|(\bis\b)|(\bsample\b)"
|
slur_filter: "(\bThis\b)|(\bis\b)|(\bsample\b)"
|
||||||
# Maximum length of local community and user names
|
# Maximum length of local community and user names
|
||||||
actor_name_max_length: 20
|
actor_name_max_length: 20
|
||||||
|
|
|
@ -104,6 +104,16 @@ pub async fn match_websocket_operation(
|
||||||
UserOperation::SaveSiteConfig => {
|
UserOperation::SaveSiteConfig => {
|
||||||
do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
|
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::Search => do_websocket_operation::<Search>(context, id, op, data).await,
|
||||||
UserOperation::ResolveObject => {
|
UserOperation::ResolveObject => {
|
||||||
do_websocket_operation::<ResolveObject>(context, id, op, data).await
|
do_websocket_operation::<ResolveObject>(context, id, op, data).await
|
||||||
|
|
|
@ -49,7 +49,13 @@ impl Perform for BanPerson {
|
||||||
// Remove their data if that's desired
|
// Remove their data if that's desired
|
||||||
let remove_data = data.remove_data.unwrap_or(false);
|
let remove_data = data.remove_data.unwrap_or(false);
|
||||||
if remove_data {
|
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
|
// Mod tables
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
mod config;
|
mod config;
|
||||||
mod leave_admin;
|
mod leave_admin;
|
||||||
mod mod_log;
|
mod mod_log;
|
||||||
|
mod purge;
|
||||||
mod registration_applications;
|
mod registration_applications;
|
||||||
mod resolve_object;
|
mod resolve_object;
|
||||||
mod search;
|
mod search;
|
||||||
|
|
|
@ -5,6 +5,10 @@ use lemmy_api_common::{
|
||||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt},
|
||||||
};
|
};
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
AdminPurgeCommentView,
|
||||||
|
AdminPurgeCommunityView,
|
||||||
|
AdminPurgePersonView,
|
||||||
|
AdminPurgePostView,
|
||||||
ModAddCommunityView,
|
ModAddCommunityView,
|
||||||
ModAddView,
|
ModAddView,
|
||||||
ModBanFromCommunityView,
|
ModBanFromCommunityView,
|
||||||
|
@ -83,17 +87,29 @@ impl Perform for GetModlog {
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
// These arrays are only for the full modlog, when a community isn't given
|
// 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| {
|
blocking(context.pool(), move |conn| {
|
||||||
Ok((
|
Ok((
|
||||||
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
|
||||||
ModBanView::list(conn, mod_person_id, page, limit)?,
|
ModBanView::list(conn, mod_person_id, page, limit)?,
|
||||||
ModAddView::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>
|
)) as Result<_, LemmyError>
|
||||||
})
|
})
|
||||||
.await??
|
.await??
|
||||||
} else {
|
} else {
|
||||||
(Vec::new(), Vec::new(), Vec::new())
|
Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the jwt
|
// Return the jwt
|
||||||
|
@ -108,6 +124,10 @@ impl Perform for GetModlog {
|
||||||
added_to_community,
|
added_to_community,
|
||||||
added,
|
added,
|
||||||
transferred_to_community,
|
transferred_to_community,
|
||||||
|
admin_purged_persons,
|
||||||
|
admin_purged_communities,
|
||||||
|
admin_purged_posts,
|
||||||
|
admin_purged_comments,
|
||||||
hidden_communities,
|
hidden_communities,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
use crate::post::SiteMetadata;
|
use crate::post::SiteMetadata;
|
||||||
use encoding::{all::encodings, DecoderTrap};
|
use encoding::{all::encodings, DecoderTrap};
|
||||||
use lemmy_db_schema::newtypes::DbUrl;
|
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 percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
|
||||||
use reqwest_middleware::ClientWithMiddleware;
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -105,22 +110,31 @@ pub(crate) struct PictrsFile {
|
||||||
delete_token: String,
|
delete_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub(crate) struct PictrsPurgeResponse {
|
||||||
|
msg: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) async fn fetch_pictrs(
|
pub(crate) async fn fetch_pictrs(
|
||||||
client: &ClientWithMiddleware,
|
client: &ClientWithMiddleware,
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
image_url: &Url,
|
image_url: &Url,
|
||||||
) -> Result<PictrsResponse, LemmyError> {
|
) -> Result<PictrsResponse, LemmyError> {
|
||||||
if let Some(pictrs_url) = settings.pictrs_url.to_owned() {
|
let pictrs_config = settings.pictrs_config()?;
|
||||||
is_image_content_type(client, image_url).await?;
|
is_image_content_type(client, image_url).await?;
|
||||||
|
|
||||||
let fetch_url = format!(
|
let fetch_url = format!(
|
||||||
"{}/image/download?url={}",
|
"{}/image/download?url={}",
|
||||||
pictrs_url,
|
pictrs_config.url,
|
||||||
utf8_percent_encode(image_url.as_str(), NON_ALPHANUMERIC) // TODO this might not be needed
|
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)?;
|
||||||
|
|
||||||
|
@ -129,8 +143,42 @@ pub(crate) async fn fetch_pictrs(
|
||||||
} else {
|
} else {
|
||||||
Err(LemmyError::from_message(&response.msg))
|
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 {
|
} else {
|
||||||
Err(LemmyError::from_message("pictrs_url not set up in config"))
|
Err(LemmyError::from_message(&response.msg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::sensitive::Sensitive;
|
use crate::sensitive::Sensitive;
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, PersonId},
|
newtypes::{CommentId, CommunityId, PersonId, PostId},
|
||||||
ListingType,
|
ListingType,
|
||||||
SearchType,
|
SearchType,
|
||||||
SortType,
|
SortType,
|
||||||
|
@ -21,6 +21,10 @@ use lemmy_db_views_actor::structs::{
|
||||||
PersonViewSafe,
|
PersonViewSafe,
|
||||||
};
|
};
|
||||||
use lemmy_db_views_moderator::structs::{
|
use lemmy_db_views_moderator::structs::{
|
||||||
|
AdminPurgeCommentView,
|
||||||
|
AdminPurgeCommunityView,
|
||||||
|
AdminPurgePersonView,
|
||||||
|
AdminPurgePostView,
|
||||||
ModAddCommunityView,
|
ModAddCommunityView,
|
||||||
ModAddView,
|
ModAddView,
|
||||||
ModBanFromCommunityView,
|
ModBanFromCommunityView,
|
||||||
|
@ -93,6 +97,10 @@ pub struct GetModlogResponse {
|
||||||
pub added_to_community: Vec<ModAddCommunityView>,
|
pub added_to_community: Vec<ModAddCommunityView>,
|
||||||
pub transferred_to_community: Vec<ModTransferCommunityView>,
|
pub transferred_to_community: Vec<ModTransferCommunityView>,
|
||||||
pub added: Vec<ModAddView>,
|
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>,
|
pub hidden_communities: Vec<ModHideCommunityView>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,6 +202,39 @@ pub struct FederatedInstances {
|
||||||
pub blocked: Option<Vec<String>>,
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct ListRegistrationApplications {
|
pub struct ListRegistrationApplications {
|
||||||
/// Only shows the unread applications (IE those without an admin actor)
|
/// 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::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
newtypes::{CommunityId, LocalUserId, PersonId, PostId},
|
||||||
source::{
|
source::{
|
||||||
|
@ -32,6 +32,7 @@ use lemmy_utils::{
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
utils::generate_random_string,
|
utils::generate_random_string,
|
||||||
};
|
};
|
||||||
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
use rosetta_i18n::{Language, LanguageId};
|
use rosetta_i18n::{Language, LanguageId};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
@ -505,13 +506,98 @@ pub async fn check_private_instance_and_federation_enabled(
|
||||||
Ok(())
|
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
|
// Posts
|
||||||
blocking(pool, move |conn: &'_ _| {
|
blocking(pool, move |conn: &'_ _| {
|
||||||
Post::update_removed_for_creator(conn, banned_person_id, None, true)
|
Post::update_removed_for_creator(conn, banned_person_id, None, true)
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
|
// Purge image posts
|
||||||
|
purge_image_posts_for_person(banned_person_id, pool, settings, client).await?;
|
||||||
|
|
||||||
// Communities
|
// Communities
|
||||||
// Remove all communities where they're the top mod
|
// Remove all communities where they're the top mod
|
||||||
// for now, remove the communities manually
|
// for now, remove the communities manually
|
||||||
|
@ -527,8 +613,24 @@ pub async fn remove_user_data(banned_person_id: PersonId, pool: &DbPool) -> Resu
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for first_mod_community in banned_user_first_communities {
|
for first_mod_community in banned_user_first_communities {
|
||||||
|
let community_id = first_mod_community.community.id;
|
||||||
blocking(pool, move |conn: &'_ _| {
|
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??;
|
.await??;
|
||||||
}
|
}
|
||||||
|
@ -575,7 +677,26 @@ pub async fn remove_user_data_in_community(
|
||||||
Ok(())
|
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
|
// Comments
|
||||||
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
|
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
|
||||||
blocking(pool, permadelete)
|
blocking(pool, permadelete)
|
||||||
|
@ -588,6 +709,9 @@ pub async fn delete_user_account(person_id: PersonId, pool: &DbPool) -> Result<(
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
|
.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??;
|
blocking(pool, move |conn| Person::delete_account(conn, person_id)).await??;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -33,7 +33,13 @@ impl PerformCrud for DeleteAccount {
|
||||||
return Err(LemmyError::from_message("password_incorrect"));
|
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?;
|
DeleteUser::send(&local_user_view.person.into(), context).await?;
|
||||||
|
|
||||||
Ok(DeleteAccountResponse {})
|
Ok(DeleteAccountResponse {})
|
||||||
|
|
|
@ -181,7 +181,13 @@ impl ActivityHandler for BlockUser {
|
||||||
})
|
})
|
||||||
.await??;
|
.await??;
|
||||||
if self.remove_data.unwrap_or(false) {
|
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
|
// write mod log
|
||||||
|
|
|
@ -51,7 +51,13 @@ impl ActivityHandler for DeleteUser {
|
||||||
.actor
|
.actor
|
||||||
.dereference(context, local_instance(context), request_counter)
|
.dereference(context, local_instance(context), request_counter)
|
||||||
.await?;
|
.await?;
|
||||||
delete_user_account(actor.id, context.pool()).await?;
|
delete_user_account(
|
||||||
|
actor.id,
|
||||||
|
context.pool(),
|
||||||
|
&context.settings(),
|
||||||
|
context.client(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,19 @@ impl Community {
|
||||||
.set(community_form)
|
.set(community_form)
|
||||||
.get_result::<Self>(conn)
|
.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 {
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -228,6 +228,8 @@ impl Person {
|
||||||
diesel::update(person.find(person_id))
|
diesel::update(person.find(person_id))
|
||||||
.set((
|
.set((
|
||||||
display_name.eq::<Option<String>>(None),
|
display_name.eq::<Option<String>>(None),
|
||||||
|
avatar.eq::<Option<String>>(None),
|
||||||
|
banner.eq::<Option<String>>(None),
|
||||||
bio.eq::<Option<String>>(None),
|
bio.eq::<Option<String>>(None),
|
||||||
matrix_user_id.eq::<Option<String>>(None),
|
matrix_user_id.eq::<Option<String>>(None),
|
||||||
deleted.eq(true),
|
deleted.eq(true),
|
||||||
|
@ -265,6 +267,15 @@ impl Person {
|
||||||
.set(admin.eq(false))
|
.set(admin.eq(false))
|
||||||
.get_result::<Self>(conn)
|
.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 {
|
impl PersonSafe {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
traits::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable},
|
traits::{Crud, DeleteableOrRemoveable, Likeable, Readable, Saveable},
|
||||||
utils::naive_now,
|
utils::naive_now,
|
||||||
};
|
};
|
||||||
use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
|
use diesel::{dsl::*, result::Error, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl, *};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl Crud for Post {
|
impl Crud for Post {
|
||||||
|
@ -174,6 +174,71 @@ impl Post {
|
||||||
.map(Into::into),
|
.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 {
|
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! {
|
table! {
|
||||||
email_verification (id) {
|
email_verification (id) {
|
||||||
id -> Int4,
|
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! {
|
table! {
|
||||||
registration_application (id) {
|
registration_application (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -675,6 +713,13 @@ joinable!(registration_application -> person (admin_id));
|
||||||
joinable!(mod_hide_community -> person (mod_person_id));
|
joinable!(mod_hide_community -> person (mod_person_id));
|
||||||
joinable!(mod_hide_community -> community (community_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!(
|
allow_tables_to_appear_in_same_query!(
|
||||||
activity,
|
activity,
|
||||||
comment,
|
comment,
|
||||||
|
@ -718,6 +763,10 @@ allow_tables_to_appear_in_same_query!(
|
||||||
comment_alias_1,
|
comment_alias_1,
|
||||||
person_alias_1,
|
person_alias_1,
|
||||||
person_alias_2,
|
person_alias_2,
|
||||||
|
admin_purge_comment,
|
||||||
|
admin_purge_community,
|
||||||
|
admin_purge_person,
|
||||||
|
admin_purge_post,
|
||||||
email_verification,
|
email_verification,
|
||||||
registration_application
|
registration_application
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,6 +3,10 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
use crate::schema::{
|
use crate::schema::{
|
||||||
|
admin_purge_comment,
|
||||||
|
admin_purge_community,
|
||||||
|
admin_purge_person,
|
||||||
|
admin_purge_post,
|
||||||
mod_add,
|
mod_add,
|
||||||
mod_add_community,
|
mod_add_community,
|
||||||
mod_ban,
|
mod_ban,
|
||||||
|
@ -247,3 +251,75 @@ pub struct ModAddForm {
|
||||||
pub other_person_id: PersonId,
|
pub other_person_id: PersonId,
|
||||||
pub removed: Option<bool>,
|
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>,
|
||||||
|
}
|
||||||
|
|
|
@ -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>>()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,12 @@
|
||||||
#[cfg(feature = "full")]
|
#[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;
|
pub mod mod_add_community_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod mod_add_view;
|
pub mod mod_add_view;
|
||||||
|
|
|
@ -2,6 +2,10 @@ use lemmy_db_schema::source::{
|
||||||
comment::Comment,
|
comment::Comment,
|
||||||
community::CommunitySafe,
|
community::CommunitySafe,
|
||||||
moderator::{
|
moderator::{
|
||||||
|
AdminPurgeComment,
|
||||||
|
AdminPurgeCommunity,
|
||||||
|
AdminPurgePerson,
|
||||||
|
AdminPurgePost,
|
||||||
ModAdd,
|
ModAdd,
|
||||||
ModAddCommunity,
|
ModAddCommunity,
|
||||||
ModBan,
|
ModBan,
|
||||||
|
@ -104,3 +108,29 @@ pub struct ModTransferCommunityView {
|
||||||
pub community: CommunitySafe,
|
pub community: CommunitySafe,
|
||||||
pub modded_person: PersonSafeAlias1,
|
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,
|
HttpRequest,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
|
||||||
use futures::stream::{Stream, StreamExt};
|
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 lemmy_websocket::LemmyContext;
|
||||||
use reqwest::Body;
|
use reqwest::Body;
|
||||||
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
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
|
// 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/{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)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
@ -49,6 +49,14 @@ struct PictrsParams {
|
||||||
thumbnail: Option<String>,
|
thumbnail: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
enum PictrsPurgeParams {
|
||||||
|
#[serde(rename = "file")]
|
||||||
|
File(String),
|
||||||
|
#[serde(rename = "alias")]
|
||||||
|
Alias(String),
|
||||||
|
}
|
||||||
|
|
||||||
fn adapt_request(
|
fn adapt_request(
|
||||||
request: &HttpRequest,
|
request: &HttpRequest,
|
||||||
client: &ClientWithMiddleware,
|
client: &ClientWithMiddleware,
|
||||||
|
@ -57,7 +65,9 @@ fn adapt_request(
|
||||||
// remove accept-encoding header so that pictrs doesnt compress the response
|
// remove accept-encoding header so that pictrs doesnt compress the response
|
||||||
const INVALID_HEADERS: &[HeaderName] = &[ACCEPT_ENCODING, HOST];
|
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
|
request
|
||||||
.headers()
|
.headers()
|
||||||
|
@ -86,7 +96,8 @@ async fn upload(
|
||||||
return Ok(HttpResponse::Unauthorized().finish());
|
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);
|
let mut client_req = adapt_request(&req, &client, image_url);
|
||||||
|
|
||||||
|
@ -116,22 +127,16 @@ async fn full_res(
|
||||||
let name = &filename.into_inner();
|
let name = &filename.into_inner();
|
||||||
|
|
||||||
// If there are no query params, the URL is original
|
// 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() {
|
let url = if params.format.is_none() && params.thumbnail.is_none() {
|
||||||
format!(
|
format!("{}/image/original/{}", pictrs_config.url, name,)
|
||||||
"{}/image/original/{}",
|
|
||||||
pictrs_url(pictrs_url_settings)?,
|
|
||||||
name,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// Use jpg as a default when none is given
|
// Use jpg as a default when none is given
|
||||||
let format = params.format.unwrap_or_else(|| "jpg".to_string());
|
let format = params.format.unwrap_or_else(|| "jpg".to_string());
|
||||||
|
|
||||||
let mut url = format!(
|
let mut url = format!(
|
||||||
"{}/image/process.{}?src={}",
|
"{}/image/process.{}?src={}",
|
||||||
pictrs_url(pictrs_url_settings)?,
|
pictrs_config.url, format, name,
|
||||||
format,
|
|
||||||
name,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(size) = params.thumbnail {
|
if let Some(size) = params.thumbnail {
|
||||||
|
@ -181,12 +186,8 @@ async fn delete(
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let (token, file) = components.into_inner();
|
let (token, file) = components.into_inner();
|
||||||
|
|
||||||
let url = format!(
|
let pictrs_config = context.settings().pictrs_config()?;
|
||||||
"{}/image/delete/{}/{}",
|
let url = format!("{}/image/delete/{}/{}", pictrs_config.url, &token, &file);
|
||||||
pictrs_url(context.settings().pictrs_url)?,
|
|
||||||
&token,
|
|
||||||
&file
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut client_req = adapt_request(&req, &client, url);
|
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())))
|
Ok(HttpResponse::build(res.status()).body(BodyStream::new(res.bytes_stream())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pictrs_url(pictrs_url: Option<String>) -> Result<String, LemmyError> {
|
async fn purge(
|
||||||
pictrs_url.ok_or_else(|| anyhow!("images_disabled").into())
|
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
|
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 anyhow::{anyhow, Context};
|
||||||
use deser_hjson::from_str;
|
use deser_hjson::from_str;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -116,4 +120,11 @@ impl Settings {
|
||||||
.expect("compile regex")
|
.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
|
/// Settings related to activitypub federation
|
||||||
#[default(FederationConfig::default())]
|
#[default(FederationConfig::default())]
|
||||||
pub federation: FederationConfig,
|
pub federation: FederationConfig,
|
||||||
|
/// Pictrs image server configuration.
|
||||||
|
#[default(None)]
|
||||||
|
pub(crate) pictrs_config: Option<PictrsConfig>,
|
||||||
#[default(CaptchaConfig::default())]
|
#[default(CaptchaConfig::default())]
|
||||||
pub captcha: CaptchaConfig,
|
pub captcha: CaptchaConfig,
|
||||||
/// Email sending configuration. All options except login/password are mandatory
|
/// 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.
|
/// Whether the site is available over TLS. Needs to be true for federation to work.
|
||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub tls_enabled: bool,
|
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)]
|
#[default(None)]
|
||||||
#[doku(example = "(\\bThis\\b)|(\\bis\\b)|(\\bsample\\b)")]
|
#[doku(example = "(\\bThis\\b)|(\\bis\\b)|(\\bsample\\b)")]
|
||||||
|
/// A regex list of slurs to block / hide
|
||||||
pub slur_filter: Option<String>,
|
pub slur_filter: Option<String>,
|
||||||
/// Maximum length of local community and user names
|
/// Maximum length of local community and user names
|
||||||
#[default(20)]
|
#[default(20)]
|
||||||
|
@ -56,6 +56,18 @@ pub struct Settings {
|
||||||
pub opentelemetry_url: Option<String>,
|
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)]
|
#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct CaptchaConfig {
|
pub struct CaptchaConfig {
|
||||||
|
|
|
@ -142,6 +142,10 @@ pub enum UserOperation {
|
||||||
GetSiteMetadata,
|
GetSiteMetadata,
|
||||||
BlockCommunity,
|
BlockCommunity,
|
||||||
BlockPerson,
|
BlockPerson,
|
||||||
|
PurgePerson,
|
||||||
|
PurgeCommunity,
|
||||||
|
PurgePost,
|
||||||
|
PurgeComment,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(EnumString, Display, Debug, Clone)]
|
#[derive(EnumString, Display, Debug, Clone)]
|
||||||
|
|
|
@ -56,8 +56,10 @@ services:
|
||||||
user: 991:991
|
user: 991:991
|
||||||
environment:
|
environment:
|
||||||
- PICTRS_OPENTELEMETRY_URL=http://otel:4137
|
- PICTRS_OPENTELEMETRY_URL=http://otel:4137
|
||||||
|
- PICTRS__API_KEY=API_KEY
|
||||||
ports:
|
ports:
|
||||||
- "6670:6669"
|
- "6670:6669"
|
||||||
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/pictrs:/mnt
|
- ./volumes/pictrs:/mnt
|
||||||
restart: always
|
restart: always
|
||||||
|
|
|
@ -20,8 +20,10 @@
|
||||||
# port where lemmy should listen for incoming requests
|
# port where lemmy should listen for incoming requests
|
||||||
port: 8536
|
port: 8536
|
||||||
# settings related to the postgresql database
|
# settings related to the postgresql database
|
||||||
# address where pictrs is available
|
pictrs_config: {
|
||||||
pictrs_url: "http://pictrs:8080"
|
url: "http://pictrs:8080"
|
||||||
|
api_key: "API_KEY"
|
||||||
|
}
|
||||||
database: {
|
database: {
|
||||||
# name of the postgres database for lemmy
|
# name of the postgres database for lemmy
|
||||||
database: "lemmy"
|
database: "lemmy"
|
||||||
|
|
|
@ -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()
|
||||||
|
);
|
|
@ -232,6 +232,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
||||||
"/registration_application/approve",
|
"/registration_application/approve",
|
||||||
web::put().to(route_post::<ApproveRegistrationApplication>),
|
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
|
settings.bind, settings.port
|
||||||
);
|
);
|
||||||
|
|
||||||
let client = Client::builder()
|
let reqwest_client = Client::builder()
|
||||||
.user_agent(build_user_agent(&settings))
|
.user_agent(build_user_agent(&settings))
|
||||||
.timeout(REQWEST_TIMEOUT)
|
.timeout(REQWEST_TIMEOUT)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
@ -111,11 +111,16 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
backoff_exponent: 2,
|
backoff_exponent: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = ClientBuilder::new(client)
|
let client = ClientBuilder::new(reqwest_client.clone())
|
||||||
.with(TracingMiddleware)
|
.with(TracingMiddleware)
|
||||||
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
|
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
|
||||||
.build();
|
.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?;
|
check_private_instance_and_federation_enabled(&pool, &settings).await?;
|
||||||
|
|
||||||
let chat_server = ChatServer::startup(
|
let chat_server = ChatServer::startup(
|
||||||
|
@ -149,7 +154,7 @@ async fn main() -> Result<(), LemmyError> {
|
||||||
.configure(|cfg| api_routes::config(cfg, &rate_limiter))
|
.configure(|cfg| api_routes::config(cfg, &rate_limiter))
|
||||||
.configure(|cfg| lemmy_apub::http::routes::config(cfg, &settings))
|
.configure(|cfg| lemmy_apub::http::routes::config(cfg, &settings))
|
||||||
.configure(feeds::config)
|
.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(nodeinfo::config)
|
||||||
.configure(|cfg| webfinger::config(cfg, &settings))
|
.configure(|cfg| webfinger::config(cfg, &settings))
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue