mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-25 10:51:03 +00:00
Instance blocks with mod log entry and expiration (fixes #2506)
This commit is contained in:
parent
417e18e819
commit
2a1d3c9db8
15 changed files with 201 additions and 110 deletions
37
crates/api/src/site/admin_block_instance.rs
Normal file
37
crates/api/src/site/admin_block_instance.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
site::{AdminBlockInstance, AdminBlockInstanceResponse},
|
||||
utils::is_admin,
|
||||
};
|
||||
use lemmy_db_schema::source::federation_blocklist::{FederationBlockList, FederationBlockListForm};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
pub async fn block_instance(
|
||||
data: Json<AdminBlockInstance>,
|
||||
local_user_view: LocalUserView,
|
||||
context: Data<LemmyContext>,
|
||||
) -> LemmyResult<Json<AdminBlockInstanceResponse>> {
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let instance_block_form = FederationBlockListForm {
|
||||
instance_id: data.instance_id,
|
||||
admin_person_id: Some(local_user_view.person.id),
|
||||
reason: data.reason.clone(),
|
||||
expires: data.expires,
|
||||
updated: None,
|
||||
};
|
||||
|
||||
if data.block {
|
||||
FederationBlockList::block(&mut context.pool(), &instance_block_form).await?;
|
||||
} else {
|
||||
FederationBlockList::unblock(&mut context.pool(), &instance_block_form).await?;
|
||||
}
|
||||
|
||||
Ok(Json(AdminBlockInstanceResponse {
|
||||
blocked: data.block,
|
||||
}))
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
pub mod admin_block_instance;
|
||||
pub mod block;
|
||||
pub mod federated_instances;
|
||||
pub mod leave_admin;
|
||||
|
|
|
@ -7,21 +7,10 @@ use lemmy_api_common::{
|
|||
use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType};
|
||||
use lemmy_db_views::structs::LocalUserView;
|
||||
use lemmy_db_views_moderator::structs::{
|
||||
AdminPurgeCommentView,
|
||||
AdminPurgeCommunityView,
|
||||
AdminPurgePersonView,
|
||||
AdminPurgePostView,
|
||||
ModAddCommunityView,
|
||||
ModAddView,
|
||||
ModBanFromCommunityView,
|
||||
ModBanView,
|
||||
ModFeaturePostView,
|
||||
ModHideCommunityView,
|
||||
ModLockPostView,
|
||||
ModRemoveCommentView,
|
||||
ModRemoveCommunityView,
|
||||
ModRemovePostView,
|
||||
ModTransferCommunityView,
|
||||
AdminBlockInstanceView, AdminPurgeCommentView, AdminPurgeCommunityView, AdminPurgePersonView,
|
||||
AdminPurgePostView, ModAddCommunityView, ModAddView,
|
||||
ModBanFromCommunityView, ModBanView, ModFeaturePostView, ModHideCommunityView, ModLockPostView,
|
||||
ModRemoveCommentView, ModRemoveCommunityView, ModRemovePostView, ModTransferCommunityView,
|
||||
ModlogListParams,
|
||||
};
|
||||
use lemmy_utils::error::LemmyResult;
|
||||
|
@ -121,6 +110,7 @@ pub async fn get_mod_log(
|
|||
admin_purged_communities,
|
||||
admin_purged_posts,
|
||||
admin_purged_comments,
|
||||
admin_block_instance,
|
||||
) = if data.community_id.is_none() {
|
||||
(
|
||||
match type_ {
|
||||
|
@ -161,6 +151,12 @@ pub async fn get_mod_log(
|
|||
}
|
||||
_ => Default::default(),
|
||||
},
|
||||
match type_ {
|
||||
All | AdminBlockInstance if other_person_id.is_none() => {
|
||||
AdminBlockInstanceView::list(&mut context.pool(), params).await?
|
||||
}
|
||||
_ => Default::default(),
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Default::default()
|
||||
|
@ -183,5 +179,6 @@ pub async fn get_mod_log(
|
|||
admin_purged_posts,
|
||||
admin_purged_comments,
|
||||
hidden_communities,
|
||||
admin_block_instance,
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -2,13 +2,7 @@ use crate::federate_retry_sleep_duration;
|
|||
use chrono::{DateTime, Utc};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::{
|
||||
CommentId,
|
||||
CommunityId,
|
||||
InstanceId,
|
||||
LanguageId,
|
||||
PersonId,
|
||||
PostId,
|
||||
RegistrationApplicationId,
|
||||
CommentId, CommunityId, InstanceId, LanguageId, PersonId, PostId, RegistrationApplicationId,
|
||||
},
|
||||
source::{
|
||||
community::Community,
|
||||
|
@ -20,44 +14,20 @@ use lemmy_db_schema::{
|
|||
person::Person,
|
||||
tagline::Tagline,
|
||||
},
|
||||
CommentSortType,
|
||||
FederationMode,
|
||||
ListingType,
|
||||
ModlogActionType,
|
||||
PostListingMode,
|
||||
PostSortType,
|
||||
RegistrationMode,
|
||||
SearchType,
|
||||
CommentSortType, FederationMode, ListingType, ModlogActionType, PostListingMode, PostSortType,
|
||||
RegistrationMode, SearchType,
|
||||
};
|
||||
use lemmy_db_views::structs::{
|
||||
CommentView,
|
||||
LocalUserView,
|
||||
PostView,
|
||||
RegistrationApplicationView,
|
||||
SiteView,
|
||||
CommentView, LocalUserView, PostView, RegistrationApplicationView, SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::structs::{
|
||||
CommunityFollowerView,
|
||||
CommunityModeratorView,
|
||||
CommunityView,
|
||||
PersonView,
|
||||
CommunityFollowerView, CommunityModeratorView, CommunityView, PersonView,
|
||||
};
|
||||
use lemmy_db_views_moderator::structs::{
|
||||
AdminPurgeCommentView,
|
||||
AdminPurgeCommunityView,
|
||||
AdminPurgePersonView,
|
||||
AdminPurgePostView,
|
||||
ModAddCommunityView,
|
||||
ModAddView,
|
||||
ModBanFromCommunityView,
|
||||
ModBanView,
|
||||
ModFeaturePostView,
|
||||
ModHideCommunityView,
|
||||
ModLockPostView,
|
||||
ModRemoveCommentView,
|
||||
ModRemoveCommunityView,
|
||||
ModRemovePostView,
|
||||
ModTransferCommunityView,
|
||||
AdminBlockInstanceView, AdminPurgeCommentView, AdminPurgeCommunityView, AdminPurgePersonView,
|
||||
AdminPurgePostView, ModAddCommunityView, ModAddView, ModBanFromCommunityView, ModBanView,
|
||||
ModFeaturePostView, ModHideCommunityView, ModLockPostView, ModRemoveCommentView,
|
||||
ModRemoveCommunityView, ModRemovePostView, ModTransferCommunityView,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
|
@ -183,6 +153,7 @@ pub struct GetModlogResponse {
|
|||
pub admin_purged_posts: Vec<AdminPurgePostView>,
|
||||
pub admin_purged_comments: Vec<AdminPurgeCommentView>,
|
||||
pub hidden_communities: Vec<ModHideCommunityView>,
|
||||
pub admin_block_instance: Vec<AdminBlockInstanceView>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
@ -660,3 +631,20 @@ pub struct BlockInstance {
|
|||
pub struct BlockInstanceResponse {
|
||||
pub blocked: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct AdminBlockInstance {
|
||||
pub instance_id: InstanceId,
|
||||
pub block: bool,
|
||||
pub reason: Option<String>,
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "full", derive(TS))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct AdminBlockInstanceResponse {
|
||||
pub blocked: bool,
|
||||
}
|
|
@ -19,8 +19,6 @@ use lemmy_api_common::{
|
|||
use lemmy_db_schema::{
|
||||
source::{
|
||||
actor_language::SiteLanguage,
|
||||
federation_allowlist::FederationAllowList,
|
||||
federation_blocklist::FederationBlockList,
|
||||
local_site::{LocalSite, LocalSiteUpdateForm},
|
||||
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
|
||||
local_site_url_blocklist::LocalSiteUrlBlocklist,
|
||||
|
@ -152,12 +150,6 @@ pub async fn update_site(
|
|||
.await
|
||||
.ok();
|
||||
|
||||
// Replace the blocked and allowed instances
|
||||
let allowed = data.allowed_instances.clone();
|
||||
FederationAllowList::replace(&mut context.pool(), allowed).await?;
|
||||
let blocked = data.blocked_instances.clone();
|
||||
FederationBlockList::replace(&mut context.pool(), blocked).await?;
|
||||
|
||||
if let Some(url_blocklist) = data.blocked_urls.clone() {
|
||||
let parsed_urls = check_urls_are_valid(&url_blocklist)?;
|
||||
LocalSiteUrlBlocklist::replace(&mut context.pool(), parsed_urls).await?;
|
||||
|
|
|
@ -1,49 +1,30 @@
|
|||
use crate::{
|
||||
schema::federation_blocklist,
|
||||
source::{
|
||||
federation_blocklist::{FederationBlockList, FederationBlockListForm},
|
||||
instance::Instance,
|
||||
},
|
||||
source::federation_blocklist::{FederationBlockList, FederationBlockListForm},
|
||||
utils::{get_conn, DbPool},
|
||||
};
|
||||
use diesel::{delete, ExpressionMethods, QueryDsl};
|
||||
use diesel::{dsl::insert_into, result::Error};
|
||||
use diesel_async::{AsyncPgConnection, RunQueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
|
||||
impl FederationBlockList {
|
||||
pub async fn replace(pool: &mut DbPool<'_>, list_opt: Option<Vec<String>>) -> Result<(), Error> {
|
||||
pub async fn block(pool: &mut DbPool<'_>, form: &FederationBlockListForm) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
if let Some(list) = list_opt {
|
||||
Self::clear(conn).await?;
|
||||
|
||||
for domain in list {
|
||||
// Upsert all of these as instances
|
||||
let instance = Instance::read_or_create(&mut conn.into(), domain).await?;
|
||||
|
||||
let form = FederationBlockListForm {
|
||||
instance_id: instance.id,
|
||||
updated: None,
|
||||
};
|
||||
insert_into(federation_blocklist::table)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}) as _
|
||||
})
|
||||
insert_into(federation_blocklist::table)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> {
|
||||
diesel::delete(federation_blocklist::table)
|
||||
.execute(conn)
|
||||
.await
|
||||
pub async fn unblock(
|
||||
pool: &mut DbPool<'_>,
|
||||
form: &FederationBlockListForm,
|
||||
) -> Result<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
delete(
|
||||
federation_blocklist::table
|
||||
.filter(federation_blocklist::dsl::instance_id.eq(form.instance_id)),
|
||||
)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ pub enum ModlogActionType {
|
|||
AdminPurgeCommunity,
|
||||
AdminPurgePost,
|
||||
AdminPurgeComment,
|
||||
AdminBlockInstance,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
|
|
@ -282,6 +282,9 @@ diesel::table! {
|
|||
instance_id -> Int4,
|
||||
published -> Timestamptz,
|
||||
updated -> Nullable<Timestamptz>,
|
||||
admin_person_id -> Nullable<Int4>,
|
||||
reason -> Nullable<Text>,
|
||||
expires -> Nullable<Timestamptz>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -955,6 +958,7 @@ diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
|
|||
diesel::joinable!(email_verification -> local_user (local_user_id));
|
||||
diesel::joinable!(federation_allowlist -> instance (instance_id));
|
||||
diesel::joinable!(federation_blocklist -> instance (instance_id));
|
||||
diesel::joinable!(federation_blocklist -> person (admin_person_id));
|
||||
diesel::joinable!(federation_queue_state -> instance (instance_id));
|
||||
diesel::joinable!(instance_actions -> instance (instance_id));
|
||||
diesel::joinable!(instance_actions -> person (person_id));
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::newtypes::InstanceId;
|
||||
use crate::newtypes::{InstanceId, PersonId};
|
||||
#[cfg(feature = "full")]
|
||||
use crate::schema::federation_blocklist;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::Debug;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
derive(Queryable, Selectable, Associations, Identifiable)
|
||||
derive(TS, Queryable, Selectable, Associations, Identifiable)
|
||||
)]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
|
@ -17,10 +18,15 @@ use std::fmt::Debug;
|
|||
#[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))]
|
||||
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
pub struct FederationBlockList {
|
||||
pub instance_id: InstanceId,
|
||||
pub published: DateTime<Utc>,
|
||||
pub updated: Option<DateTime<Utc>>,
|
||||
// TODO: would be good to make this mandatory but value doesnt exist for old entries
|
||||
pub admin_person_id: Option<PersonId>,
|
||||
pub reason: Option<String>,
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -29,4 +35,7 @@ pub struct FederationBlockList {
|
|||
pub struct FederationBlockListForm {
|
||||
pub instance_id: InstanceId,
|
||||
pub updated: Option<DateTime<Utc>>,
|
||||
pub admin_person_id: Option<PersonId>,
|
||||
pub reason: Option<String>,
|
||||
pub expires: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
|
57
crates/db_views_moderator/src/admin_block_instance.rs
Normal file
57
crates/db_views_moderator/src/admin_block_instance.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use crate::structs::{AdminBlockInstanceView, ModlogListParams};
|
||||
use diesel::{
|
||||
result::Error,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
IntoSql,
|
||||
JoinOnDsl,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use lemmy_db_schema::{
|
||||
newtypes::PersonId,
|
||||
schema::{federation_blocklist, instance, person},
|
||||
utils::{functions::coalesce, get_conn, limit_and_offset, DbPool},
|
||||
};
|
||||
|
||||
impl AdminBlockInstanceView {
|
||||
pub async fn list(pool: &mut DbPool<'_>, params: ModlogListParams) -> Result<Vec<Self>, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
let admin_person_id_join = params.mod_person_id.unwrap_or(PersonId(-1));
|
||||
let show_mod_names = !params.hide_modlog_names;
|
||||
let show_mod_names_expr = show_mod_names.as_sql::<diesel::sql_types::Bool>();
|
||||
|
||||
let admin_names_join = coalesce(federation_blocklist::admin_person_id, 0)
|
||||
.eq(person::id)
|
||||
.and(show_mod_names_expr.or(person::id.eq(admin_person_id_join)));
|
||||
let mut query = federation_blocklist::table
|
||||
.left_join(person::table.on(admin_names_join))
|
||||
.inner_join(instance::table)
|
||||
.select((
|
||||
federation_blocklist::all_columns,
|
||||
instance::all_columns,
|
||||
person::all_columns.nullable(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if let Some(admin_person_id) = params.mod_person_id {
|
||||
query = query.filter(federation_blocklist::admin_person_id.eq(admin_person_id));
|
||||
};
|
||||
|
||||
// If a post or comment ID is given, then don't find any results
|
||||
if params.post_id.is_some() || params.comment_id.is_some() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(params.page, params.limit)?;
|
||||
|
||||
query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(federation_blocklist::published.desc())
|
||||
.load::<AdminBlockInstanceView>(conn)
|
||||
.await
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
#[cfg(feature = "full")]
|
||||
pub mod admin_block_instance;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_comment_view;
|
||||
#[cfg(feature = "full")]
|
||||
pub mod admin_purge_community_view;
|
||||
|
|
|
@ -5,6 +5,8 @@ use lemmy_db_schema::{
|
|||
source::{
|
||||
comment::Comment,
|
||||
community::Community,
|
||||
federation_blocklist::FederationBlockList,
|
||||
instance::Instance,
|
||||
moderator::{
|
||||
AdminPurgeComment,
|
||||
AdminPurgeCommunity,
|
||||
|
@ -233,6 +235,19 @@ pub struct AdminPurgePostView {
|
|||
pub community: Community,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// When an admin purges a post.
|
||||
pub struct AdminBlockInstanceView {
|
||||
pub blocklist_entry: FederationBlockList,
|
||||
pub instance: Instance,
|
||||
#[cfg_attr(feature = "full", ts(optional))]
|
||||
pub admin: Option<Person>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
#[cfg_attr(feature = "full", derive(TS, Queryable))]
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
alter table federation_blocklist drop column reason;
|
||||
alter table federation_blocklist drop column expires;
|
||||
alter table federation_blocklist drop column admin_person_id;
|
|
@ -0,0 +1,3 @@
|
|||
alter table federation_blocklist add column admin_person_id int REFERENCES person(id) ON UPDATE CASCADE ON DELETE CASCADE;
|
||||
alter table federation_blocklist add column reason text;
|
||||
alter table federation_blocklist add column expires timestamptz;
|
|
@ -19,15 +19,7 @@ use lemmy_api_common::{
|
|||
use lemmy_api_crud::post::create::send_webmention;
|
||||
use lemmy_db_schema::{
|
||||
schema::{
|
||||
captcha_answer,
|
||||
comment,
|
||||
community,
|
||||
community_actions,
|
||||
instance,
|
||||
person,
|
||||
post,
|
||||
received_activity,
|
||||
sent_activity,
|
||||
captcha_answer, comment, community, community_actions, federation_blocklist, instance, person, post, received_activity, sent_activity
|
||||
},
|
||||
source::{
|
||||
community::Community,
|
||||
|
@ -439,6 +431,15 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) {
|
|||
.await
|
||||
.inspect_err(|e| error!("Failed to remove community_ban expired rows: {e}"))
|
||||
.ok();
|
||||
|
||||
// TODO: need separate table for modlog, otherwise entry will disappear
|
||||
diesel::delete(
|
||||
federation_blocklist::table.filter(federation_blocklist::expires.lt(now().nullable())),
|
||||
)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.inspect_err(|e| error!("Failed to remove federation_blocklist expired rows: {e}"))
|
||||
.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get connection from pool: {e}");
|
||||
|
|
Loading…
Reference in a new issue