mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 03:11:32 +00:00
Added option to remove banned user data (posts, comments, communities) (#1093)
- Works for both a site-ban, and a community ban. - Fixes #557
This commit is contained in:
parent
725e46da4a
commit
c323ab5275
10 changed files with 184 additions and 42 deletions
2
docs/src/contributing_websocket_http_api.md
vendored
2
docs/src/contributing_websocket_http_api.md
vendored
|
@ -818,6 +818,7 @@ Marks all user replies and mentions as read.
|
|||
data: {
|
||||
user_id: i32,
|
||||
ban: bool,
|
||||
remove_data: Option<bool>, // Removes/Restores their comments, posts, and communities
|
||||
reason: Option<String>,
|
||||
expires: Option<i64>,
|
||||
auth: String
|
||||
|
@ -1177,6 +1178,7 @@ Search types are `All, Comments, Posts, Communities, Users, Url`
|
|||
community_id: i32,
|
||||
user_id: i32,
|
||||
ban: bool,
|
||||
remove_data: Option<bool>, // Removes/Restores their comments and posts for that community
|
||||
reason: Option<String>,
|
||||
expires: Option<i64>,
|
||||
auth: String
|
||||
|
|
|
@ -97,16 +97,15 @@ impl Comment {
|
|||
comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn permadelete(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
|
||||
pub fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
|
||||
diesel::update(comment.find(comment_id))
|
||||
diesel::update(comment.filter(creator_id.eq(for_creator_id)))
|
||||
.set((
|
||||
content.eq("*Permananently Deleted*"),
|
||||
deleted.eq(true),
|
||||
updated.eq(naive_now()),
|
||||
))
|
||||
.get_result::<Self>(conn)
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_deleted(
|
||||
|
@ -131,6 +130,17 @@ impl Comment {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
diesel::update(comment.filter(creator_id.eq(for_creator_id)))
|
||||
.set((removed.eq(new_removed), updated.eq(naive_now())))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_read(conn: &PgConnection, comment_id: i32, new_read: bool) -> Result<Self, Error> {
|
||||
use crate::schema::comment::dsl::*;
|
||||
diesel::update(comment.find(comment_id))
|
||||
|
|
|
@ -121,6 +121,17 @@ impl Community {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::community::dsl::*;
|
||||
diesel::update(community.filter(creator_id.eq(for_creator_id)))
|
||||
.set((removed.eq(new_removed), updated.eq(naive_now())))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_creator(
|
||||
conn: &PgConnection,
|
||||
community_id: i32,
|
||||
|
|
|
@ -95,13 +95,13 @@ impl Post {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn permadelete(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
|
||||
pub fn permadelete_for_creator(conn: &PgConnection, for_creator_id: i32) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
|
||||
let perma_deleted = "*Permananently Deleted*";
|
||||
let perma_deleted_url = "https://deleted.com";
|
||||
|
||||
diesel::update(post.find(post_id))
|
||||
diesel::update(post.filter(creator_id.eq(for_creator_id)))
|
||||
.set((
|
||||
name.eq(perma_deleted),
|
||||
url.eq(perma_deleted_url),
|
||||
|
@ -109,7 +109,7 @@ impl Post {
|
|||
deleted.eq(true),
|
||||
updated.eq(naive_now()),
|
||||
))
|
||||
.get_result::<Self>(conn)
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_deleted(
|
||||
|
@ -134,6 +134,26 @@ impl Post {
|
|||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_removed_for_creator(
|
||||
conn: &PgConnection,
|
||||
for_creator_id: i32,
|
||||
for_community_id: Option<i32>,
|
||||
new_removed: bool,
|
||||
) -> Result<Vec<Self>, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
|
||||
let mut update = diesel::update(post).into_boxed();
|
||||
update = update.filter(creator_id.eq(for_creator_id));
|
||||
|
||||
if let Some(for_community_id) = for_community_id {
|
||||
update = update.filter(community_id.eq(for_community_id));
|
||||
}
|
||||
|
||||
update
|
||||
.set((removed.eq(new_removed), updated.eq(naive_now())))
|
||||
.get_results::<Self>(conn)
|
||||
}
|
||||
|
||||
pub fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result<Self, Error> {
|
||||
use crate::schema::post::dsl::*;
|
||||
diesel::update(post.find(post_id))
|
||||
|
|
|
@ -13,8 +13,11 @@ use crate::{
|
|||
use actix_web::client::Client;
|
||||
use anyhow::Context;
|
||||
use lemmy_db::{
|
||||
comment::Comment,
|
||||
comment_view::CommentQueryBuilder,
|
||||
diesel_option_overwrite,
|
||||
naive_now,
|
||||
post::Post,
|
||||
Bannable,
|
||||
Crud,
|
||||
Followable,
|
||||
|
@ -81,6 +84,7 @@ pub struct BanFromCommunity {
|
|||
pub community_id: i32,
|
||||
user_id: i32,
|
||||
ban: bool,
|
||||
remove_data: Option<bool>,
|
||||
reason: Option<String>,
|
||||
expires: Option<i64>,
|
||||
auth: String,
|
||||
|
@ -676,6 +680,7 @@ impl Perform for BanFromCommunity {
|
|||
let user = get_user_from_jwt(&data.auth, pool).await?;
|
||||
|
||||
let community_id = data.community_id;
|
||||
let banned_user_id = data.user_id;
|
||||
|
||||
// Verify that only mods or admins can ban
|
||||
is_mod_or_admin(pool, user.id, community_id).await?;
|
||||
|
@ -697,6 +702,34 @@ impl Perform for BanFromCommunity {
|
|||
}
|
||||
}
|
||||
|
||||
// Remove/Restore their data if that's desired
|
||||
if let Some(remove_data) = data.remove_data {
|
||||
// Posts
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Post::update_removed_for_creator(conn, banned_user_id, Some(community_id), remove_data)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Comments
|
||||
// Diesel doesn't allow updates with joins, so this has to be a loop
|
||||
let comments = blocking(pool, move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.for_creator_id(banned_user_id)
|
||||
.for_community_id(community_id)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
for comment in &comments {
|
||||
let comment_id = comment.id;
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Comment::update_removed(conn, comment_id, remove_data)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
// TODO eventually do correct expires
|
||||
let expires = match data.expires {
|
||||
|
|
|
@ -177,6 +177,7 @@ pub struct AddAdminResponse {
|
|||
pub struct BanUser {
|
||||
user_id: i32,
|
||||
ban: bool,
|
||||
remove_data: Option<bool>,
|
||||
reason: Option<String>,
|
||||
expires: Option<i64>,
|
||||
auth: String,
|
||||
|
@ -850,6 +851,27 @@ impl Perform for BanUser {
|
|||
return Err(APIError::err("couldnt_update_user").into());
|
||||
}
|
||||
|
||||
// Remove their data if that's desired
|
||||
if let Some(remove_data) = data.remove_data {
|
||||
// Posts
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Post::update_removed_for_creator(conn, banned_user_id, None, remove_data)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Communities
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Community::update_removed_for_creator(conn, banned_user_id, remove_data)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Comments
|
||||
blocking(pool, move |conn: &'_ _| {
|
||||
Comment::update_removed_for_creator(conn, banned_user_id, remove_data)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
|
||||
// Mod tables
|
||||
let expires = match data.expires {
|
||||
Some(time) => Some(naive_from_unix(time)),
|
||||
|
@ -1064,40 +1086,15 @@ impl Perform for DeleteAccount {
|
|||
|
||||
// Comments
|
||||
let user_id = user.id;
|
||||
let comments = blocking(pool, move |conn| {
|
||||
CommentQueryBuilder::create(conn)
|
||||
.for_creator_id(user_id)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
for comment in &comments {
|
||||
let comment_id = comment.id;
|
||||
let permadelete = move |conn: &'_ _| Comment::permadelete(conn, comment_id);
|
||||
if blocking(pool, permadelete).await?.is_err() {
|
||||
return Err(APIError::err("couldnt_update_comment").into());
|
||||
}
|
||||
let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, user_id);
|
||||
if blocking(pool, permadelete).await?.is_err() {
|
||||
return Err(APIError::err("couldnt_update_comment").into());
|
||||
}
|
||||
|
||||
// Posts
|
||||
let posts = blocking(pool, move |conn| {
|
||||
PostQueryBuilder::create(conn)
|
||||
.sort(&SortType::New)
|
||||
.for_creator_id(user_id)
|
||||
.limit(std::i64::MAX)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
// TODO: this should probably be a bulk operation
|
||||
for post in &posts {
|
||||
let post_id = post.id;
|
||||
let permadelete = move |conn: &'_ _| Post::permadelete(conn, post_id);
|
||||
if blocking(pool, permadelete).await?.is_err() {
|
||||
return Err(APIError::err("couldnt_update_post").into());
|
||||
}
|
||||
let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, user_id);
|
||||
if blocking(pool, permadelete).await?.is_err() {
|
||||
return Err(APIError::err("couldnt_update_post").into());
|
||||
}
|
||||
|
||||
Ok(LoginResponse {
|
||||
|
|
37
ui/src/components/comment-node.tsx
vendored
37
ui/src/components/comment-node.tsx
vendored
|
@ -43,6 +43,7 @@ interface CommentNodeState {
|
|||
showRemoveDialog: boolean;
|
||||
removeReason: string;
|
||||
showBanDialog: boolean;
|
||||
removeData: boolean;
|
||||
banReason: string;
|
||||
banExpires: string;
|
||||
banType: BanType;
|
||||
|
@ -87,6 +88,7 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
showRemoveDialog: false,
|
||||
removeReason: null,
|
||||
showBanDialog: false,
|
||||
removeData: null,
|
||||
banReason: null,
|
||||
banExpires: null,
|
||||
banType: BanType.Community,
|
||||
|
@ -699,6 +701,20 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
value={this.state.banReason}
|
||||
onInput={linkEvent(this, this.handleModBanReasonChange)}
|
||||
/>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="mod-ban-remove-data"
|
||||
type="checkbox"
|
||||
checked={this.state.removeData}
|
||||
onChange={linkEvent(this, this.handleModRemoveDataChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="mod-ban-remove-data">
|
||||
{i18n.t('remove_posts_comments')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* TODO hold off on expires until later */}
|
||||
{/* <div class="form-group row"> */}
|
||||
|
@ -951,6 +967,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleModRemoveDataChange(i: CommentNode, event: any) {
|
||||
i.state.removeData = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleModRemoveSubmit(i: CommentNode) {
|
||||
event.preventDefault();
|
||||
let form: RemoveCommentForm = {
|
||||
|
@ -1024,18 +1045,30 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
|
|||
event.preventDefault();
|
||||
|
||||
if (i.state.banType == BanType.Community) {
|
||||
// If its an unban, restore all their data
|
||||
let ban = !i.props.node.comment.banned_from_community;
|
||||
if (ban == false) {
|
||||
i.state.removeData = false;
|
||||
}
|
||||
let form: BanFromCommunityForm = {
|
||||
user_id: i.props.node.comment.creator_id,
|
||||
community_id: i.props.node.comment.community_id,
|
||||
ban: !i.props.node.comment.banned_from_community,
|
||||
ban,
|
||||
remove_data: i.state.removeData,
|
||||
reason: i.state.banReason,
|
||||
expires: getUnixTime(i.state.banExpires),
|
||||
};
|
||||
WebSocketService.Instance.banFromCommunity(form);
|
||||
} else {
|
||||
// If its an unban, restore all their data
|
||||
let ban = !i.props.node.comment.banned;
|
||||
if (ban == false) {
|
||||
i.state.removeData = false;
|
||||
}
|
||||
let form: BanUserForm = {
|
||||
user_id: i.props.node.comment.creator_id,
|
||||
ban: !i.props.node.comment.banned,
|
||||
ban,
|
||||
remove_data: i.state.removeData,
|
||||
reason: i.state.banReason,
|
||||
expires: getUnixTime(i.state.banExpires),
|
||||
};
|
||||
|
|
37
ui/src/components/post-listing.tsx
vendored
37
ui/src/components/post-listing.tsx
vendored
|
@ -44,6 +44,7 @@ interface PostListingState {
|
|||
showRemoveDialog: boolean;
|
||||
removeReason: string;
|
||||
showBanDialog: boolean;
|
||||
removeData: boolean;
|
||||
banReason: string;
|
||||
banExpires: string;
|
||||
banType: BanType;
|
||||
|
@ -74,6 +75,7 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
showRemoveDialog: false,
|
||||
removeReason: null,
|
||||
showBanDialog: false,
|
||||
removeData: null,
|
||||
banReason: null,
|
||||
banExpires: null,
|
||||
banType: BanType.Community,
|
||||
|
@ -931,6 +933,20 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
value={this.state.banReason}
|
||||
onInput={linkEvent(this, this.handleModBanReasonChange)}
|
||||
/>
|
||||
<div class="form-group">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="mod-ban-remove-data"
|
||||
type="checkbox"
|
||||
checked={this.state.removeData}
|
||||
onChange={linkEvent(this, this.handleModRemoveDataChange)}
|
||||
/>
|
||||
<label class="form-check-label" htmlFor="mod-ban-remove-data">
|
||||
{i18n.t('remove_posts_comments')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* TODO hold off on expires until later */}
|
||||
{/* <div class="form-group row"> */}
|
||||
|
@ -1241,6 +1257,11 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleModRemoveDataChange(i: PostListing, event: any) {
|
||||
i.state.removeData = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleModRemoveSubmit(i: PostListing) {
|
||||
event.preventDefault();
|
||||
let form: RemovePostForm = {
|
||||
|
@ -1311,18 +1332,30 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
|
|||
event.preventDefault();
|
||||
|
||||
if (i.state.banType == BanType.Community) {
|
||||
// If its an unban, restore all their data
|
||||
let ban = !i.props.post.banned_from_community;
|
||||
if (ban == false) {
|
||||
i.state.removeData = false;
|
||||
}
|
||||
let form: BanFromCommunityForm = {
|
||||
user_id: i.props.post.creator_id,
|
||||
community_id: i.props.post.community_id,
|
||||
ban: !i.props.post.banned_from_community,
|
||||
ban,
|
||||
remove_data: i.state.removeData,
|
||||
reason: i.state.banReason,
|
||||
expires: getUnixTime(i.state.banExpires),
|
||||
};
|
||||
WebSocketService.Instance.banFromCommunity(form);
|
||||
} else {
|
||||
// If its an unban, restore all their data
|
||||
let ban = !i.props.post.banned;
|
||||
if (ban == false) {
|
||||
i.state.removeData = false;
|
||||
}
|
||||
let form: BanUserForm = {
|
||||
user_id: i.props.post.creator_id,
|
||||
ban: !i.props.post.banned,
|
||||
ban,
|
||||
remove_data: i.state.removeData,
|
||||
reason: i.state.banReason,
|
||||
expires: getUnixTime(i.state.banExpires),
|
||||
};
|
||||
|
|
2
ui/src/interfaces.ts
vendored
2
ui/src/interfaces.ts
vendored
|
@ -413,6 +413,7 @@ export interface BanFromCommunityForm {
|
|||
community_id: number;
|
||||
user_id: number;
|
||||
ban: boolean;
|
||||
remove_data?: boolean;
|
||||
reason?: string;
|
||||
expires?: number;
|
||||
auth?: string;
|
||||
|
@ -877,6 +878,7 @@ export interface SiteResponse {
|
|||
export interface BanUserForm {
|
||||
user_id: number;
|
||||
ban: boolean;
|
||||
remove_data?: boolean;
|
||||
reason?: string;
|
||||
expires?: number;
|
||||
auth?: string;
|
||||
|
|
1
ui/translations/en.json
vendored
1
ui/translations/en.json
vendored
|
@ -15,6 +15,7 @@
|
|||
"number_of_comments": "{{count}} Comment",
|
||||
"number_of_comments_plural": "{{count}} Comments",
|
||||
"remove_comment": "Remove Comment",
|
||||
"remove_posts_comments": "Remove Posts and Comments",
|
||||
"communities": "Communities",
|
||||
"users": "Users",
|
||||
"create_a_community": "Create a community",
|
||||
|
|
Loading…
Reference in a new issue