forked from administration/panel
feat: full report management
parent
006acdb6ac
commit
45bce9d5fb
|
@ -28,9 +28,11 @@ export default async function Message({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Link href={`/panel/inspect/user/${author!._id}`}>
|
{author && (
|
||||||
<UserCard user={author!} subtitle="Message Author" />
|
<Link href={`/panel/inspect/user/${author!._id}`}>
|
||||||
</Link>
|
<UserCard user={author!} subtitle="Message Author" />
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<Link href={`/panel/inspect/channel/${channel!._id}`}>
|
<Link href={`/panel/inspect/channel/${channel!._id}`}>
|
||||||
<ChannelCard channel={channel!} subtitle="Channel" />
|
<ChannelCard channel={channel!} subtitle="Channel" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -41,8 +41,18 @@ export default async function Reports({ params }: { params: { id: string } }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<NavigationToolbar>Viewing Report</NavigationToolbar>
|
<NavigationToolbar>Viewing Report</NavigationToolbar>
|
||||||
<ReportCard report={report} />
|
<ReportActions
|
||||||
<ReportActions report={report} />
|
report={report}
|
||||||
|
reference={`${report._id.substring(20, 26)}, ${
|
||||||
|
snapshots[0]._type === "Message"
|
||||||
|
? "Message"
|
||||||
|
: snapshots[0]._type === "User"
|
||||||
|
? `${snapshots[0].username}#${snapshots[0].discriminator}`
|
||||||
|
: snapshots[0].name
|
||||||
|
}, ${report.content.report_reason}${
|
||||||
|
report.additional_context ? `, ${report.additional_context}` : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
|
||||||
<Link href={`/panel/inspect/user/${author!._id}`}>
|
<Link href={`/panel/inspect/user/${author!._id}`}>
|
||||||
<UserCard user={author!} subtitle="Report Author" />
|
<UserCard user={author!} subtitle="Report Author" />
|
||||||
|
|
|
@ -68,9 +68,9 @@ export function CompactMessage({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex-[2] min-w-0 overflow-ellipsis overflow-hidden text-left">
|
<div className="flex-[2] min-w-0 overflow-ellipsis overflow-hidden text-left">
|
||||||
{(message.attachments || message.embeds) && (
|
{message.attachments?.length || message.embeds?.length ? (
|
||||||
<ImageIcon size={12} className="inline align-middle" />
|
<ImageIcon size={12} className="inline align-middle" />
|
||||||
)}{" "}
|
) : null}{" "}
|
||||||
{message.content ?? <span className="text-gray-500">No text.</span>}
|
{message.content ?? <span className="text-gray-500">No text.</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,9 +80,7 @@ export function CompactMessage({
|
||||||
<AlertDialogTitle>
|
<AlertDialogTitle>
|
||||||
{user?.avatar && (
|
{user?.avatar && (
|
||||||
<Avatar className="inline-block align-middle mr-1">
|
<Avatar className="inline-block align-middle mr-1">
|
||||||
<AvatarImage
|
<AvatarImage src={`${AUTUMN_URL}/avatars/${user.avatar._id}`} />
|
||||||
src={`${AUTUMN_URL}/avatars/${user.avatar._id}`}
|
|
||||||
/>
|
|
||||||
</Avatar>
|
</Avatar>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{user?.username}#{user?.discriminator}
|
{user?.username}#{user?.discriminator}
|
||||||
|
|
|
@ -16,17 +16,28 @@ export function ReportCard({ report }: { report: Report }) {
|
||||||
report.status !== "Created" ? "text-gray-500" : ""
|
report.status !== "Created" ? "text-gray-500" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{report.content.report_reason.includes("Illegal") && (
|
{report.status === "Resolved" ? (
|
||||||
<Badge className="align-middle" variant="destructive">
|
<Badge className="align-middle">Resolved</Badge>
|
||||||
Urgent
|
) : report.status === "Rejected" ? (
|
||||||
|
<Badge className="align-middle">
|
||||||
|
Closed for {report.rejection_reason}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
) : (
|
||||||
|
report.content.report_reason.includes("Illegal") && (
|
||||||
|
<Badge className="align-middle" variant="destructive">
|
||||||
|
Urgent
|
||||||
|
</Badge>
|
||||||
|
)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{report.additional_context || "No reason specified"}
|
{report.additional_context || "No reason specified"}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>
|
||||||
{report._id.toString().substring(20, 26)} ·{" "}
|
{report._id.toString().substring(20, 26)} ·{" "}
|
||||||
{report.content.report_reason} · {report.content.type} ·{" "}
|
{report.content.report_reason} · {report.content.type} ·{" "}
|
||||||
{dayjs(decodeTime(report._id)).fromNow()}
|
{dayjs(decodeTime(report._id)).fromNow()}{" "}
|
||||||
|
{report.status !== "Created" && report.closed_at && (
|
||||||
|
<>· Closed {dayjs(report.closed_at).fromNow()}</>
|
||||||
|
)}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -17,7 +17,7 @@ export async function RecentMessages({
|
||||||
query: Filter<Message>;
|
query: Filter<Message>;
|
||||||
users?: boolean | User[];
|
users?: boolean | User[];
|
||||||
}) {
|
}) {
|
||||||
const recentMessages = await fetchMessages(query);
|
const recentMessages = (await fetchMessages(query)).reverse();
|
||||||
|
|
||||||
const userList = (
|
const userList = (
|
||||||
users === true
|
users === true
|
||||||
|
|
|
@ -68,7 +68,9 @@ export function RelevantModerationNotices({
|
||||||
{strikesDraft.map((strike) => (
|
{strikesDraft.map((strike) => (
|
||||||
<TableRow key={strike._id}>
|
<TableRow key={strike._id}>
|
||||||
<TableCell>{strike.reason}</TableCell>
|
<TableCell>{strike.reason}</TableCell>
|
||||||
<TableCell>{dayjs(decodeTime(strike._id)).fromNow()}</TableCell>
|
<TableCell>
|
||||||
|
{dayjs(decodeTime(strike._id))?.fromNow()}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
|
|
|
@ -11,16 +11,77 @@ import {
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu";
|
} from "../ui/dropdown-menu";
|
||||||
import { useRef, useState } from "react";
|
import { useState } from "react";
|
||||||
import { useToast } from "../ui/use-toast";
|
import { useToast } from "../ui/use-toast";
|
||||||
import { updateReportNotes } from "@/lib/actions";
|
import {
|
||||||
|
rejectReport,
|
||||||
|
reopenReport,
|
||||||
|
resolveReport,
|
||||||
|
sendAlert,
|
||||||
|
updateReportNotes,
|
||||||
|
} from "@/lib/actions";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
} from "../ui/alert-dialog";
|
||||||
|
import { ReportCard } from "../cards/ReportCard";
|
||||||
|
|
||||||
export function ReportActions({ report }: { report: Report }) {
|
const template: Record<string, (ref: string) => string> = {
|
||||||
|
resolved: (ref) =>
|
||||||
|
`Your report (${ref}) has been actioned and appropriate action has been taken.`,
|
||||||
|
invalid: (ref) => `Your report (${ref}) has been marked as invalid.`,
|
||||||
|
false: (ref) =>
|
||||||
|
`Your report (${ref}) has been marked as false or spam. False reports may lead to additional action against your account.`,
|
||||||
|
duplicate: (ref) => `Your report (${ref}) has been marked as a duplicate.`,
|
||||||
|
"not enough evidence": (ref) =>
|
||||||
|
`Your report (${ref}) has not been actioned at this time due to a lack of supporting evidence, if you have additional information to support your report, please either report individual relevant messages or send an email to contact@revolt.chat.`,
|
||||||
|
clarify: (ref) =>
|
||||||
|
`Your report (${ref}) needs clarification, please provide additional information.`,
|
||||||
|
acknowledged: (ref) =>
|
||||||
|
`Your report (${ref}) has been acknowledged, we will be monitoring the situation.`,
|
||||||
|
default: (ref) =>
|
||||||
|
`Report (${ref})\n\nNo template found for rejection reason, please specify.`,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ReportActions({
|
||||||
|
report,
|
||||||
|
reference,
|
||||||
|
}: {
|
||||||
|
report: Report;
|
||||||
|
reference: string;
|
||||||
|
}) {
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [reportDraft, setDraft] = useState(report);
|
const [reportDraft, setDraft] = useState(report);
|
||||||
|
|
||||||
|
function rejectHandler(reason: string) {
|
||||||
|
return async () => {
|
||||||
|
try {
|
||||||
|
const $set = await rejectReport(report._id, reason);
|
||||||
|
setDraft((report) => ({ ...report, ...$set }));
|
||||||
|
toast({
|
||||||
|
title: "Rejected report",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to reject report",
|
||||||
|
description: String(err),
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<ReportCard report={reportDraft} />
|
||||||
|
|
||||||
<Textarea
|
<Textarea
|
||||||
placeholder="Enter notes here... (save on unfocus)"
|
placeholder="Enter notes here... (save on unfocus)"
|
||||||
className="!min-h-0 !h-[76px]"
|
className="!min-h-0 !h-[76px]"
|
||||||
|
@ -46,9 +107,26 @@ export function ReportActions({ report }: { report: Report }) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{report.status === "Created" ? (
|
{reportDraft.status === "Created" ? (
|
||||||
<>
|
<>
|
||||||
<Button className="flex-1 bg-green-400 hover:bg-green-300">
|
<Button
|
||||||
|
className="flex-1 bg-green-400 hover:bg-green-300"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const $set = await resolveReport(report._id);
|
||||||
|
setDraft((report) => ({ ...report, ...$set }));
|
||||||
|
toast({
|
||||||
|
title: "Resolved report",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to resolve report",
|
||||||
|
description: String(err),
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
Resolve
|
Resolve
|
||||||
</Button>
|
</Button>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
|
@ -58,23 +136,117 @@ export function ReportActions({ report }: { report: Report }) {
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem>Custom Reason</DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const reason = prompt("Enter a custom reason:");
|
||||||
|
// TODO: modal
|
||||||
|
reason && rejectHandler(reason)();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Custom Reason
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuLabel>Presets</DropdownMenuLabel>
|
<DropdownMenuLabel>Presets</DropdownMenuLabel>
|
||||||
<DropdownMenuItem>Invalid</DropdownMenuItem>
|
<DropdownMenuItem onClick={rejectHandler("invalid")}>
|
||||||
<DropdownMenuItem>False or Spam</DropdownMenuItem>
|
Invalid
|
||||||
<DropdownMenuItem>Duplicate</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>Not Enough Evidence</DropdownMenuItem>
|
<DropdownMenuItem onClick={rejectHandler("false")}>
|
||||||
<DropdownMenuItem>Request Clarification</DropdownMenuItem>
|
False or Spam
|
||||||
<DropdownMenuItem>Acknowledge Only</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>Ignore</DropdownMenuItem>
|
<DropdownMenuItem onClick={rejectHandler("duplicate")}>
|
||||||
|
Duplicate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={rejectHandler("not enough evidence")}
|
||||||
|
>
|
||||||
|
Not Enough Evidence
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={rejectHandler("clarify")}>
|
||||||
|
Request Clarification
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={rejectHandler("acknowledged")}>
|
||||||
|
Acknowledge Only
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={rejectHandler("ignore")}>
|
||||||
|
Ignore
|
||||||
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button className="flex-1">Re-open</Button>
|
<Button
|
||||||
<Button className="flex-1">Send resolution notification</Button>
|
className="flex-1"
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
const $set = await reopenReport(report._id);
|
||||||
|
setDraft((report) => ({ ...report, ...$set }));
|
||||||
|
toast({
|
||||||
|
title: "Opened report again",
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
title: "Failed to re-open report",
|
||||||
|
description: String(err),
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Re-open
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button className="flex-1">Send resolution notification</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Send Notification</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="flex flex-col gap-2">
|
||||||
|
<span>
|
||||||
|
This will send a message from the Platform Moderation
|
||||||
|
account.
|
||||||
|
</span>
|
||||||
|
<Textarea
|
||||||
|
id="notification-content"
|
||||||
|
defaultValue={(
|
||||||
|
template[
|
||||||
|
reportDraft.status === "Rejected"
|
||||||
|
? reportDraft.rejection_reason
|
||||||
|
: "resolved"
|
||||||
|
] ?? template["default"]
|
||||||
|
)(reference)}
|
||||||
|
/>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => {
|
||||||
|
const msg = (
|
||||||
|
document.getElementById(
|
||||||
|
"notification-content"
|
||||||
|
) as HTMLTextAreaElement
|
||||||
|
).value;
|
||||||
|
if (!msg) return;
|
||||||
|
|
||||||
|
sendAlert(report.author_id, msg)
|
||||||
|
.then(() => toast({ title: "Sent Alert" }))
|
||||||
|
.catch((err) =>
|
||||||
|
toast({
|
||||||
|
title: "Failed to send alert!",
|
||||||
|
description: String(err),
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Send
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
import { Input } from "../ui/input";
|
import { Input } from "../ui/input";
|
||||||
import {
|
import {
|
||||||
banUser,
|
banUser,
|
||||||
|
closeReportsByUser,
|
||||||
sendAlert,
|
sendAlert,
|
||||||
suspendUser,
|
suspendUser,
|
||||||
unsuspendUser,
|
unsuspendUser,
|
||||||
|
@ -249,17 +250,10 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
||||||
More Options
|
More Options
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent className="flex flex-col">
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<DropdownMenuItem
|
<Button variant="ghost">Send Alert</Button>
|
||||||
onClick={() => {
|
|
||||||
throw "Cancel immediate propagation.";
|
|
||||||
}}
|
|
||||||
disabled={userInaccessible}
|
|
||||||
>
|
|
||||||
Send Alert
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</AlertDialogTrigger>
|
</AlertDialogTrigger>
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
|
@ -302,6 +296,42 @@ export function UserActions({ user, bot }: { user: User; bot?: Bot }) {
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>
|
||||||
|
<Button variant="ghost">Close Open Reports</Button>
|
||||||
|
</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Close Open Reports</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription className="flex flex-col gap-2">
|
||||||
|
<span>
|
||||||
|
This will close all reports still open by this user.
|
||||||
|
</span>
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() =>
|
||||||
|
closeReportsByUser(user._id)
|
||||||
|
.then((reports) =>
|
||||||
|
toast({ title: `Closed ${reports} Reports` })
|
||||||
|
)
|
||||||
|
.catch((err) =>
|
||||||
|
toast({
|
||||||
|
title: "Failed to close reports!",
|
||||||
|
description: String(err),
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Continue
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
|
||||||
{/* <DropdownMenuItem>
|
{/* <DropdownMenuItem>
|
||||||
Clear ({counts.pending}) Friend Requests
|
Clear ({counts.pending}) Friend Requests
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
@ -85,6 +85,80 @@ export async function updateReportNotes(reportId: string, notes: string) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resolveReport(reportId: string) {
|
||||||
|
const $set = {
|
||||||
|
status: "Resolved",
|
||||||
|
closed_at: new Date().toISOString(),
|
||||||
|
} as Report;
|
||||||
|
|
||||||
|
await mongo().db("revolt").collection<Report>("safety_reports").updateOne(
|
||||||
|
{ _id: reportId },
|
||||||
|
{
|
||||||
|
$set,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function rejectReport(reportId: string, reason: string) {
|
||||||
|
const $set = {
|
||||||
|
status: "Rejected",
|
||||||
|
rejection_reason: reason,
|
||||||
|
closed_at: new Date().toISOString(),
|
||||||
|
} as Report;
|
||||||
|
|
||||||
|
await mongo().db("revolt").collection<Report>("safety_reports").updateOne(
|
||||||
|
{ _id: reportId },
|
||||||
|
{
|
||||||
|
$set,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reopenReport(reportId: string) {
|
||||||
|
const $set = {
|
||||||
|
status: "Created",
|
||||||
|
} as Report;
|
||||||
|
|
||||||
|
await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.updateOne(
|
||||||
|
{ _id: reportId },
|
||||||
|
{
|
||||||
|
$set,
|
||||||
|
$unset: {
|
||||||
|
closed_at: 1,
|
||||||
|
} as never,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return $set;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeReportsByUser(userId: string) {
|
||||||
|
return await mongo()
|
||||||
|
.db("revolt")
|
||||||
|
.collection<Report>("safety_reports")
|
||||||
|
.updateMany(
|
||||||
|
{
|
||||||
|
status: "Created",
|
||||||
|
author_id: userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
status: "Rejected",
|
||||||
|
rejection_reason: "bulk close",
|
||||||
|
closed_at: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((res) => res.modifiedCount);
|
||||||
|
}
|
||||||
|
|
||||||
export async function disableAccount(userId: string) {
|
export async function disableAccount(userId: string) {
|
||||||
await mongo()
|
await mongo()
|
||||||
.db("revolt")
|
.db("revolt")
|
||||||
|
|
Loading…
Reference in New Issue