forked from administration/panel
feat: wipe user messages
parent
eb0bd7a7c9
commit
8113a86db9
|
@ -30,6 +30,7 @@ import {
|
|||
unsuspendUser,
|
||||
updateBotDiscoverability,
|
||||
updateUserBadges,
|
||||
wipeUser,
|
||||
wipeUserProfile,
|
||||
} from "@/lib/actions";
|
||||
import { useRef, useState } from "react";
|
||||
|
@ -264,6 +265,51 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
|||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button className="flex-1 bg-pink-600" disabled={userInaccessible}>
|
||||
Wipe Messages
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>
|
||||
Are you sure you want to wipe this user's messages?
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
All messages sent by this user will be deleted immediately.
|
||||
<br className="text-base/8" />
|
||||
<span className="text-red-700">
|
||||
This action is irreversible and{" "}
|
||||
<b className="font-bold">will not publish any events</b>!
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
className="hover:bg-red-700 transition-all"
|
||||
onClick={() =>
|
||||
wipeUser(user._id, 0, true)
|
||||
.then(() => {
|
||||
setUserDraft((user) => ({ ...user, flags: 4 }));
|
||||
toast({ title: "Wiped user's messages" });
|
||||
})
|
||||
.catch((err) =>
|
||||
toast({
|
||||
title: "Failed to wipe user's messages!",
|
||||
description: String(err),
|
||||
variant: "destructive",
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Ban
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button className="flex-1 bg-yellow-600">Bees</Button>
|
||||
|
@ -424,29 +470,37 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
|||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" disabled={!user.bot?.owner}>Reset bot token</Button>
|
||||
<Button variant="ghost" disabled={!user.bot?.owner}>
|
||||
Reset bot token
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Reset token</AlertDialogTitle>
|
||||
<AlertDialogDescription className="flex flex-col gap-2">
|
||||
<span>
|
||||
Re-roll this bot's authentication token. This will not disconnect active connections.
|
||||
Re-roll this bot's authentication token. This will
|
||||
not disconnect active connections.
|
||||
</span>
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={() => resetBotToken(user._id)
|
||||
.then(() => toast({
|
||||
title: "Reset bot token",
|
||||
}))
|
||||
.catch((e) => toast({
|
||||
title: "Failed to reset token",
|
||||
description: String(e),
|
||||
variant: "destructive",
|
||||
}))
|
||||
onClick={() =>
|
||||
resetBotToken(user._id)
|
||||
.then(() =>
|
||||
toast({
|
||||
title: "Reset bot token",
|
||||
})
|
||||
)
|
||||
.catch((e) =>
|
||||
toast({
|
||||
title: "Failed to reset token",
|
||||
description: String(e),
|
||||
variant: "destructive",
|
||||
})
|
||||
)
|
||||
}
|
||||
>
|
||||
Reset
|
||||
|
@ -457,18 +511,16 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
|||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button variant="ghost" disabled={!user.bot?.owner}>Transfer bot</Button>
|
||||
<Button variant="ghost" disabled={!user.bot?.owner}>
|
||||
Transfer bot
|
||||
</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Transfer bot</AlertDialogTitle>
|
||||
<AlertDialogDescription className="flex flex-col gap-2">
|
||||
<span>
|
||||
Transfer this bot to a new owner.
|
||||
</span>
|
||||
<UserSelector
|
||||
onChange={setTransferTarget}
|
||||
/>
|
||||
<span>Transfer this bot to a new owner.</span>
|
||||
<UserSelector onChange={setTransferTarget} />
|
||||
<Checkbox
|
||||
checked={transferResetToken}
|
||||
onChange={(e) => setTransferResetToken(!!e)}
|
||||
|
@ -481,19 +533,28 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
|||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
disabled={!transferTarget}
|
||||
onClick={() => transferBot(user._id, transferTarget!._id, transferResetToken)
|
||||
.then(() => toast({
|
||||
title: "Reset bot token",
|
||||
}))
|
||||
.catch((e) => toast({
|
||||
title: "Failed to reset token",
|
||||
description: String(e),
|
||||
variant: "destructive",
|
||||
}))
|
||||
.finally(() => {
|
||||
setTransferResetToken(true);
|
||||
setTransferTarget(null);
|
||||
})
|
||||
onClick={() =>
|
||||
transferBot(
|
||||
user._id,
|
||||
transferTarget!._id,
|
||||
transferResetToken
|
||||
)
|
||||
.then(() =>
|
||||
toast({
|
||||
title: "Reset bot token",
|
||||
})
|
||||
)
|
||||
.catch((e) =>
|
||||
toast({
|
||||
title: "Failed to reset token",
|
||||
description: String(e),
|
||||
variant: "destructive",
|
||||
})
|
||||
)
|
||||
.finally(() => {
|
||||
setTransferResetToken(true);
|
||||
setTransferTarget(null);
|
||||
})
|
||||
}
|
||||
>
|
||||
Transfer
|
||||
|
|
136
lib/actions.ts
136
lib/actions.ts
|
@ -446,15 +446,21 @@ export async function updateUserBadges(userId: string, badges: number) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function wipeUser(userId: string, flags = 4) {
|
||||
export async function wipeUser(
|
||||
userId: string,
|
||||
flags = 4,
|
||||
onlyMessages = false
|
||||
) {
|
||||
if (RESTRICT_ACCESS_LIST.includes(userId)) throw "restricted access";
|
||||
|
||||
await checkPermission("users/action/wipe", userId, { flags });
|
||||
|
||||
const user = await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.findOne({ _id: userId });
|
||||
const user = onlyMessages
|
||||
? null
|
||||
: await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.findOne({ _id: userId });
|
||||
|
||||
const messages = await mongo()
|
||||
.db("revolt")
|
||||
|
@ -462,24 +468,28 @@ export async function wipeUser(userId: string, flags = 4) {
|
|||
.find({ author: userId }, { sort: { _id: -1 } })
|
||||
.toArray();
|
||||
|
||||
const dms = await mongo()
|
||||
.db("revolt")
|
||||
.collection<Channel>("channels")
|
||||
.find({
|
||||
channel_type: "DirectMessage",
|
||||
recipients: userId,
|
||||
})
|
||||
.toArray();
|
||||
const dms = onlyMessages
|
||||
? null
|
||||
: await mongo()
|
||||
.db("revolt")
|
||||
.collection<Channel>("channels")
|
||||
.find({
|
||||
channel_type: "DirectMessage",
|
||||
recipients: userId,
|
||||
})
|
||||
.toArray();
|
||||
|
||||
const memberships = await mongo()
|
||||
.db("revolt")
|
||||
.collection<{ _id: { user: string; server: string } }>("server_members")
|
||||
.find({ "_id.user": userId })
|
||||
.toArray();
|
||||
const memberships = onlyMessages
|
||||
? null
|
||||
: await mongo()
|
||||
.db("revolt")
|
||||
.collection<{ _id: { user: string; server: string } }>("server_members")
|
||||
.find({ "_id.user": userId })
|
||||
.toArray();
|
||||
|
||||
// retrieve messages, dm channels, relationships, server memberships
|
||||
const backup = {
|
||||
_event: "wipe",
|
||||
_event: onlyMessages ? "messages" : "wipe",
|
||||
user,
|
||||
messages,
|
||||
dms,
|
||||
|
@ -499,12 +509,14 @@ export async function wipeUser(userId: string, flags = 4) {
|
|||
.filter((attachment) => attachment)
|
||||
.map((attachment) => attachment!._id);
|
||||
|
||||
if (backup.user?.avatar) {
|
||||
attachmentIds.push(backup.user.avatar._id);
|
||||
}
|
||||
if (!onlyMessages) {
|
||||
if (backup.user?.avatar) {
|
||||
attachmentIds.push(backup.user.avatar._id);
|
||||
}
|
||||
|
||||
if (backup.user?.profile?.background) {
|
||||
attachmentIds.push(backup.user.profile.background._id);
|
||||
if (backup.user?.profile?.background) {
|
||||
attachmentIds.push(backup.user.profile.background._id);
|
||||
}
|
||||
}
|
||||
|
||||
if (attachmentIds.length) {
|
||||
|
@ -527,44 +539,46 @@ export async function wipeUser(userId: string, flags = 4) {
|
|||
author: userId,
|
||||
});
|
||||
|
||||
// delete server memberships
|
||||
await mongo().db("revolt").collection<Member>("server_members").deleteMany({
|
||||
"_id.user": userId,
|
||||
});
|
||||
|
||||
// disable account
|
||||
await disableAccount(userId);
|
||||
|
||||
// clear user profile
|
||||
await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.updateOne(
|
||||
{
|
||||
_id: userId,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
flags,
|
||||
},
|
||||
$unset: {
|
||||
avatar: 1,
|
||||
profile: 1,
|
||||
status: 1,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// broadcast wipe event
|
||||
for (const topic of [
|
||||
...backup.dms.map((x) => x._id),
|
||||
...backup.memberships.map((x) => x._id.server),
|
||||
]) {
|
||||
await publishMessage(topic, {
|
||||
type: "UserPlatformWipe",
|
||||
user_id: userId,
|
||||
flags,
|
||||
if (!onlyMessages) {
|
||||
// delete server memberships
|
||||
await mongo().db("revolt").collection<Member>("server_members").deleteMany({
|
||||
"_id.user": userId,
|
||||
});
|
||||
|
||||
// disable account
|
||||
await disableAccount(userId);
|
||||
|
||||
// clear user profile
|
||||
await mongo()
|
||||
.db("revolt")
|
||||
.collection<User>("users")
|
||||
.updateOne(
|
||||
{
|
||||
_id: userId,
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
flags,
|
||||
},
|
||||
$unset: {
|
||||
avatar: 1,
|
||||
profile: 1,
|
||||
status: 1,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// broadcast wipe event
|
||||
for (const topic of [
|
||||
...backup.dms!.map((x) => x._id),
|
||||
...backup.memberships!.map((x) => x._id.server),
|
||||
]) {
|
||||
await publishMessage(topic, {
|
||||
type: "UserPlatformWipe",
|
||||
user_id: userId,
|
||||
flags,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue