mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-09-04 12:14:31 +00:00
Remove votes when ban + remove data for a site or community. (#5810)
* Remove votes when ban + remove data for a site or community. - Fixes #5569 * Starting to revert cleanup. * Finishing up.
This commit is contained in:
parent
9ccd647e02
commit
d274d2bb1d
6 changed files with 286 additions and 96 deletions
|
@ -87,22 +87,25 @@ mod tests {
|
|||
use lemmy_api_utils::utils::remove_or_restore_user_data;
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
comment::{Comment, CommentInsertForm},
|
||||
comment::{Comment, CommentActions, CommentInsertForm, CommentLikeForm},
|
||||
community::{Community, CommunityInsertForm},
|
||||
instance::Instance,
|
||||
local_user::{LocalUser, LocalUserInsertForm},
|
||||
mod_log::moderator::{ModRemoveComment, ModRemovePost},
|
||||
person::{Person, PersonInsertForm},
|
||||
post::{Post, PostInsertForm},
|
||||
post::{Post, PostActions, PostInsertForm, PostLikeForm},
|
||||
},
|
||||
traits::Crud,
|
||||
traits::{Crud, Likeable},
|
||||
ModlogActionType,
|
||||
};
|
||||
use lemmy_db_views_comment::CommentView;
|
||||
use lemmy_db_views_modlog_combined::{
|
||||
impls::ModlogCombinedQuery,
|
||||
ModRemoveCommentView,
|
||||
ModRemovePostView,
|
||||
ModlogCombinedView,
|
||||
};
|
||||
use lemmy_db_views_post::PostView;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serial_test::serial;
|
||||
|
||||
|
@ -112,54 +115,68 @@ mod tests {
|
|||
let context = LemmyContext::init_test_context().await;
|
||||
let pool = &mut context.pool();
|
||||
|
||||
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||
|
||||
let new_mod = PersonInsertForm::test_form(inserted_instance.id, "modder");
|
||||
let inserted_mod = Person::create(pool, &new_mod).await?;
|
||||
// John is the mod
|
||||
let john = PersonInsertForm::test_form(instance.id, "john the modder");
|
||||
let john = Person::create(pool, &john).await?;
|
||||
|
||||
let new_person = PersonInsertForm::test_form(inserted_instance.id, "chrimbus");
|
||||
let inserted_person = Person::create(pool, &new_person).await?;
|
||||
let sara_form = PersonInsertForm::test_form(instance.id, "sara");
|
||||
let sara = Person::create(pool, &sara_form).await?;
|
||||
|
||||
let new_community = CommunityInsertForm::new(
|
||||
inserted_instance.id,
|
||||
let sara_local_user_form = LocalUserInsertForm::test_form(sara.id);
|
||||
let sara_local_user = LocalUser::create(pool, &sara_local_user_form, Vec::new()).await?;
|
||||
|
||||
let community_form = CommunityInsertForm::new(
|
||||
instance.id,
|
||||
"mod_community crepes".to_string(),
|
||||
"nada".to_owned(),
|
||||
"pubkey".to_string(),
|
||||
);
|
||||
let inserted_community = Community::create(pool, &new_community).await?;
|
||||
let community = Community::create(pool, &community_form).await?;
|
||||
|
||||
let post_form_1 = PostInsertForm::new(
|
||||
"A test post tubular".into(),
|
||||
inserted_person.id,
|
||||
inserted_community.id,
|
||||
);
|
||||
let inserted_post_1 = Post::create(pool, &post_form_1).await?;
|
||||
let post_form_1 = PostInsertForm::new("A test post tubular".into(), sara.id, community.id);
|
||||
let post_1 = Post::create(pool, &post_form_1).await?;
|
||||
|
||||
let post_form_2 = PostInsertForm::new(
|
||||
"A test post radical".into(),
|
||||
inserted_person.id,
|
||||
inserted_community.id,
|
||||
);
|
||||
let inserted_post_2 = Post::create(pool, &post_form_2).await?;
|
||||
let post_like_form_1 = PostLikeForm::new(post_1.id, sara.id, 1);
|
||||
let _post_like_1 = PostActions::like(pool, &post_like_form_1).await?;
|
||||
|
||||
let comment_form_1 = CommentInsertForm::new(
|
||||
inserted_person.id,
|
||||
inserted_post_1.id,
|
||||
"A test comment tubular".into(),
|
||||
);
|
||||
let _inserted_comment_1 = Comment::create(pool, &comment_form_1, None).await?;
|
||||
let post_form_2 = PostInsertForm::new("A test post radical".into(), sara.id, community.id);
|
||||
let post_2 = Post::create(pool, &post_form_2).await?;
|
||||
|
||||
let comment_form_2 = CommentInsertForm::new(
|
||||
inserted_person.id,
|
||||
inserted_post_2.id,
|
||||
"A test comment radical".into(),
|
||||
let comment_form_1 =
|
||||
CommentInsertForm::new(sara.id, post_1.id, "A test comment tubular".into());
|
||||
let comment_1 = Comment::create(pool, &comment_form_1, None).await?;
|
||||
|
||||
let comment_like_form_1 = CommentLikeForm::new(sara.id, comment_1.id, 1);
|
||||
let _comment_like_1 = CommentActions::like(pool, &comment_like_form_1).await?;
|
||||
|
||||
let comment_form_2 =
|
||||
CommentInsertForm::new(sara.id, post_2.id, "A test comment radical".into());
|
||||
let _comment_2 = Comment::create(pool, &comment_form_2, None).await?;
|
||||
|
||||
// Read saras post to make sure it has a like
|
||||
let post_view_1 =
|
||||
PostView::read(pool, post_1.id, Some(&sara_local_user), instance.id, false).await?;
|
||||
assert_eq!(1, post_view_1.post.score);
|
||||
assert_eq!(
|
||||
Some(1),
|
||||
post_view_1.post_actions.and_then(|pa| pa.like_score)
|
||||
);
|
||||
|
||||
// Read saras comment to make sure it has a like
|
||||
let comment_view_1 =
|
||||
CommentView::read(pool, comment_1.id, Some(&sara_local_user), instance.id).await?;
|
||||
assert_eq!(1, comment_view_1.post.score);
|
||||
assert_eq!(
|
||||
Some(1),
|
||||
comment_view_1.comment_actions.and_then(|ca| ca.like_score)
|
||||
);
|
||||
let _inserted_comment_2 = Comment::create(pool, &comment_form_2, None).await?;
|
||||
|
||||
// Remove the user data
|
||||
remove_or_restore_user_data(
|
||||
inserted_mod.id,
|
||||
inserted_person.id,
|
||||
john.id,
|
||||
sara.id,
|
||||
true,
|
||||
&Some("a remove reason".to_string()),
|
||||
&context,
|
||||
|
@ -217,10 +234,26 @@ mod tests {
|
|||
],
|
||||
));
|
||||
|
||||
// Verify that the likes got removed
|
||||
// post
|
||||
let post_view_1 =
|
||||
PostView::read(pool, post_1.id, Some(&sara_local_user), instance.id, false).await?;
|
||||
assert_eq!(0, post_view_1.post.score);
|
||||
assert_eq!(None, post_view_1.post_actions.and_then(|pa| pa.like_score));
|
||||
|
||||
// comment
|
||||
let comment_view_1 =
|
||||
CommentView::read(pool, comment_1.id, Some(&sara_local_user), instance.id).await?;
|
||||
assert_eq!(0, comment_view_1.post.score);
|
||||
assert_eq!(
|
||||
None,
|
||||
comment_view_1.comment_actions.and_then(|ca| ca.like_score)
|
||||
);
|
||||
|
||||
// Now restore the content, and make sure it got appended
|
||||
remove_or_restore_user_data(
|
||||
inserted_mod.id,
|
||||
inserted_person.id,
|
||||
john.id,
|
||||
sara.id,
|
||||
false,
|
||||
&Some("a restore reason".to_string()),
|
||||
&context,
|
||||
|
@ -297,7 +330,7 @@ mod tests {
|
|||
],
|
||||
));
|
||||
|
||||
Instance::delete(pool, inserted_instance.id).await?;
|
||||
Instance::delete(pool, instance.id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -623,11 +623,15 @@ pub async fn remove_or_restore_user_data(
|
|||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// Remove post and comment votes
|
||||
PostActions::remove_all_likes(pool, banned_person_id).await?;
|
||||
CommentActions::remove_all_likes(pool, banned_person_id).await?;
|
||||
}
|
||||
|
||||
// Posts
|
||||
let removed_or_restored_posts =
|
||||
Post::update_removed_for_creator(pool, banned_person_id, None, None, removed).await?;
|
||||
Post::update_removed_for_creator(pool, banned_person_id, removed).await?;
|
||||
create_modlog_entries_for_removed_or_restored_posts(
|
||||
pool,
|
||||
mod_person_id,
|
||||
|
@ -709,10 +713,18 @@ pub async fn remove_or_restore_user_data_in_community(
|
|||
reason: &Option<String>,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
// These actions are only possible when removing, not restoring
|
||||
if remove {
|
||||
// Remove post and comment votes
|
||||
PostActions::remove_likes_in_community(pool, banned_person_id, community_id).await?;
|
||||
CommentActions::remove_likes_in_community(pool, banned_person_id, community_id).await?;
|
||||
}
|
||||
|
||||
// Posts
|
||||
let posts =
|
||||
Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), None, remove)
|
||||
Post::update_removed_for_creator_and_community(pool, banned_person_id, community_id, remove)
|
||||
.await?;
|
||||
|
||||
create_modlog_entries_for_removed_or_restored_posts(
|
||||
pool,
|
||||
mod_person_id,
|
||||
|
|
|
@ -210,14 +210,8 @@ async fn update_removed_for_instance(
|
|||
removed: bool,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
Post::update_removed_for_creator(
|
||||
pool,
|
||||
blocked_person.id,
|
||||
None,
|
||||
Some(site.instance_id),
|
||||
removed,
|
||||
)
|
||||
.await?;
|
||||
Post::update_removed_for_creator_and_instance(pool, blocked_person.id, site.instance_id, removed)
|
||||
.await?;
|
||||
Comment::update_removed_for_creator_and_instance(
|
||||
pool,
|
||||
blocked_person.id,
|
||||
|
|
|
@ -70,31 +70,61 @@ impl Comment {
|
|||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)
|
||||
}
|
||||
|
||||
/// Diesel can't update from join unfortunately, so you'll need to loop over these
|
||||
async fn creator_comment_ids_in_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<Vec<CommentId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
comment::table
|
||||
.inner_join(post::table)
|
||||
.filter(comment::creator_id.eq(creator_id))
|
||||
.filter(post::community_id.eq(community_id))
|
||||
.select(comment::id)
|
||||
.load::<CommentId>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
/// Diesel can't update from join unfortunately, so you'll need to loop over these
|
||||
async fn creator_comment_ids_in_instance(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
instance_id: InstanceId,
|
||||
) -> LemmyResult<Vec<CommentId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
let community_join = community::table.on(post::community_id.eq(community::id));
|
||||
|
||||
comment::table
|
||||
.inner_join(post::table)
|
||||
.inner_join(community_join)
|
||||
.filter(comment::creator_id.eq(creator_id))
|
||||
.filter(community::instance_id.eq(instance_id))
|
||||
.select(comment::id)
|
||||
.load::<CommentId>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn update_removed_for_creator_and_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
removed: bool,
|
||||
) -> LemmyResult<Vec<CommentId>> {
|
||||
let comment_ids =
|
||||
Self::creator_comment_ids_in_community(pool, creator_id, community_id).await?;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Diesel can't update from join unfortunately, so you'll need to loop over these
|
||||
let comment_ids = comment::table
|
||||
.inner_join(post::table)
|
||||
.filter(comment::creator_id.eq(creator_id))
|
||||
.filter(post::community_id.eq(community_id))
|
||||
.select(comment::id)
|
||||
.load::<CommentId>(conn)
|
||||
.await?;
|
||||
|
||||
let form = &CommentUpdateForm {
|
||||
removed: Some(removed),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update(comment::table)
|
||||
.filter(comment::id.eq_any(comment_ids.clone()))
|
||||
.set(form)
|
||||
.set((
|
||||
comment::removed.eq(removed),
|
||||
comment::updated_at.eq(Utc::now()),
|
||||
))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
|
@ -107,26 +137,15 @@ impl Comment {
|
|||
instance_id: InstanceId,
|
||||
removed: bool,
|
||||
) -> LemmyResult<Vec<CommentId>> {
|
||||
let comment_ids = Self::creator_comment_ids_in_instance(pool, creator_id, instance_id).await?;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
// Diesel can't update from join unfortunately, so you'll need to loop over these
|
||||
let community_join = community::table.on(post::community_id.eq(community::id));
|
||||
let comment_ids = comment::table
|
||||
.inner_join(post::table)
|
||||
.inner_join(community_join)
|
||||
.filter(comment::creator_id.eq(creator_id))
|
||||
.filter(community::instance_id.eq(instance_id))
|
||||
.select(comment::id)
|
||||
.load::<CommentId>(conn)
|
||||
.await?;
|
||||
|
||||
let form = &CommentUpdateForm {
|
||||
removed: Some(removed),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
update(comment::table)
|
||||
.filter(comment::id.eq_any(comment_ids.clone()))
|
||||
.set(form)
|
||||
.set((
|
||||
comment::removed.eq(removed),
|
||||
comment::updated_at.eq(Utc::now()),
|
||||
))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(comment_ids)
|
||||
|
@ -273,6 +292,40 @@ impl Likeable for CommentActions {
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikeComment)
|
||||
}
|
||||
|
||||
async fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(comment_actions::table.filter(comment_actions::person_id.eq(creator_id)))
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)
|
||||
}
|
||||
|
||||
async fn remove_likes_in_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let comment_ids =
|
||||
Comment::creator_comment_ids_in_community(pool, creator_id, community_id).await?;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(
|
||||
comment_actions::table.filter(comment_actions::comment_id.eq_any(comment_ids.clone())),
|
||||
)
|
||||
.set_null(comment_actions::like_score)
|
||||
.set_null(comment_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)
|
||||
}
|
||||
}
|
||||
|
||||
impl Saveable for CommentActions {
|
||||
|
|
|
@ -152,31 +152,67 @@ impl Post {
|
|||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
|
||||
pub async fn update_removed_for_creator(
|
||||
async fn creator_post_ids_in_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: Option<CommunityId>,
|
||||
instance_id: Option<InstanceId>,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<Vec<PostId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
post::table
|
||||
.filter(post::creator_id.eq(creator_id))
|
||||
.filter(post::community_id.eq(community_id))
|
||||
.select(post::id)
|
||||
.load::<PostId>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
/// Diesel can't update from join unfortunately, so you sometimes need to fetch a list of post_ids
|
||||
/// for a creator.
|
||||
async fn creator_post_ids_in_instance(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
instance_id: InstanceId,
|
||||
) -> LemmyResult<Vec<PostId>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
post::table
|
||||
.inner_join(community::table)
|
||||
.filter(post::creator_id.eq(creator_id))
|
||||
.filter(community::instance_id.eq(instance_id))
|
||||
.select(post::id)
|
||||
.load::<PostId>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::NotFound)
|
||||
}
|
||||
|
||||
pub async fn update_removed_for_creator_and_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
removed: bool,
|
||||
) -> LemmyResult<Vec<Self>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
// Diesel can't update from join unfortunately, so you'll need to loop over these
|
||||
let community_join = community::table.on(post::community_id.eq(community::id));
|
||||
let mut posts_query = post::table
|
||||
.inner_join(community_join)
|
||||
update(post::table)
|
||||
.filter(post::creator_id.eq(creator_id))
|
||||
.into_boxed();
|
||||
.filter(post::community_id.eq(community_id))
|
||||
.set((post::removed.eq(removed), post::updated_at.eq(Utc::now())))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
|
||||
if let Some(community_id) = community_id {
|
||||
posts_query = posts_query.filter(post::community_id.eq(community_id));
|
||||
}
|
||||
pub async fn update_removed_for_creator_and_instance(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
instance_id: InstanceId,
|
||||
removed: bool,
|
||||
) -> LemmyResult<Vec<Self>> {
|
||||
let post_ids = Self::creator_post_ids_in_instance(pool, creator_id, instance_id).await?;
|
||||
|
||||
if let Some(instance_id) = instance_id {
|
||||
posts_query = posts_query.filter(community::instance_id.eq(instance_id));
|
||||
}
|
||||
|
||||
let post_ids = posts_query.select(post::id).load::<PostId>(conn).await?;
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
update(post::table)
|
||||
.filter(post::id.eq_any(post_ids.clone()))
|
||||
|
@ -186,6 +222,21 @@ impl Post {
|
|||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
|
||||
pub async fn update_removed_for_creator(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
removed: bool,
|
||||
) -> LemmyResult<Vec<Self>> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
update(post::table)
|
||||
.filter(post::creator_id.eq(creator_id))
|
||||
.set((post::removed.eq(removed), post::updated_at.eq(Utc::now())))
|
||||
.get_results::<Self>(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
|
||||
pub fn is_post_creator(person_id: PersonId, post_creator_id: PersonId) -> bool {
|
||||
person_id == post_creator_id
|
||||
}
|
||||
|
@ -304,6 +355,7 @@ impl Likeable for PostActions {
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)
|
||||
}
|
||||
|
||||
async fn remove_like(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
|
@ -317,6 +369,37 @@ impl Likeable for PostActions {
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)
|
||||
}
|
||||
|
||||
async fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(post_actions::table.filter(post_actions::person_id.eq(person_id)))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
|
||||
async fn remove_likes_in_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
person_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> LemmyResult<uplete::Count> {
|
||||
let post_ids = Post::creator_post_ids_in_community(pool, person_id, community_id).await?;
|
||||
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
|
||||
uplete::new(post_actions::table.filter(post_actions::post_id.eq_any(post_ids.clone())))
|
||||
.set_null(post_actions::like_score)
|
||||
.set_null(post_actions::liked_at)
|
||||
.get_result(conn)
|
||||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)
|
||||
}
|
||||
}
|
||||
|
||||
impl Saveable for PostActions {
|
||||
|
|
|
@ -143,6 +143,21 @@ pub trait Likeable {
|
|||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn remove_all_likes(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
fn remove_likes_in_community(
|
||||
pool: &mut DbPool<'_>,
|
||||
creator_id: PersonId,
|
||||
community_id: CommunityId,
|
||||
) -> impl Future<Output = LemmyResult<uplete::Count>> + Send
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
pub trait Bannable {
|
||||
|
|
Loading…
Reference in a new issue