Skip to content

Commit

Permalink
Require reason before deleting a server
Browse files Browse the repository at this point in the history
  • Loading branch information
SupertigerDev committed Dec 31, 2024
1 parent b268471 commit a2fb591
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 81 deletions.
18 changes: 10 additions & 8 deletions src/routes/moderation/serverDelete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,15 @@ import { deleteServer } from '../../services/Server';
import { generateId } from '../../common/flakeId';
import { ModAuditLogType } from '../../common/ModAuditLog';
import { checkUserPassword } from '../../services/UserAuthentication';
import { warnUsersBatch } from '../../services/Moderation';

export function serverDelete(Router: Router) {
Router.delete<any>(
'/moderation/servers/:serverId',
authenticate(),
isModMiddleware,

body('password').isLength({ min: 4, max: 72 }).withMessage('Password must be between 4 and 72 characters long.').isString().withMessage('Password must be a string!').not().isEmpty().withMessage('Password is required'),
route
);
Router.delete<any>('/moderation/servers/:serverId', authenticate(), isModMiddleware, body('reason').not().isEmpty().withMessage('Reason is required.').isString().withMessage('Reason must be a string.').isLength({ min: 0, max: 500 }), body('password').isLength({ min: 4, max: 72 }).withMessage('Password must be between 4 and 72 characters long.').isString().withMessage('Password must be a string!').not().isEmpty().withMessage('Password is required'), route);
}

interface Body {
password: string;
reason?: string;
}

interface Params {
Expand Down Expand Up @@ -55,12 +50,19 @@ async function route(req: Request<Params, unknown, Body>, res: Response) {
return res.status(403).json(error);
}

await warnUsersBatch({
userIds: [server.createdById],
reason: `${server.name} deleted: ` + req.body.reason,
modUserId: req.userCache.id,
});

await prisma.modAuditLog.create({
data: {
id: generateId(),
actionType: ModAuditLogType.serverDelete,
actionById: req.userCache.id,
serverName: server.name,
reason: req.body.reason,
serverId: server.id,
},
});
Expand Down
81 changes: 10 additions & 71 deletions src/routes/moderation/userBatchWarn.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { Request, Response, Router } from 'express';
import { body } from 'express-validator';
import { dateToDateTime, prisma } from '../../common/database';
import { prisma } from '../../common/database';
import { customExpressValidatorResult, generateError } from '../../common/errorHandler';
import { generateId } from '../../common/flakeId';
import { removeDuplicates } from '../../common/utils';
import { authenticate } from '../../middleware/authenticate';
import { isModMiddleware } from './isModMiddleware';
import { ModAuditLogType } from '../../common/ModAuditLog';
import { checkUserPassword } from '../../services/UserAuthentication';
import { emitUserNoticeCreates } from '../../emits/User';
import { warnUsersBatch } from '../../services/Moderation';

export enum NoticeType {
Warning = 0,
Expand Down Expand Up @@ -39,73 +36,15 @@ async function route(req: Request<unknown, unknown, Body>, res: Response) {
const isPasswordValid = await checkUserPassword(account.password, req.body.password);
if (!isPasswordValid) return res.status(403).json(generateError('Invalid password.', 'password'));

if (req.body.userIds.length >= 5000) return res.status(403).json(generateError('user ids must contain less than 5000 ids.'));

const sanitizedUserIds = removeDuplicates(req.body.userIds) as string[];

const sixMonthsMS = new Date().setMonth(new Date().getMonth() + 6);

await prisma.account.updateMany({
where: {
warnExpiresAt: {
lt: dateToDateTime(),
},
},
data: {
warnCount: 0,
warnExpiresAt: null,
},
});

await prisma.account.updateMany({
where: {
userId: { in: sanitizedUserIds },
},
data: {
warnCount: { increment: 1 },
warnExpiresAt: dateToDateTime(sixMonthsMS),
},
});

const noticeIds: string[] = [];

await prisma.userNotice.createMany({
data: sanitizedUserIds.map((userId) => {
const id = generateId();
noticeIds.push(id);

return {
id,
userId,
content: req.body.reason,
type: NoticeType.Warning,
createdById: req.userCache.id,
};
}),
});

const createdNotices = await prisma.userNotice.findMany({
where: { id: { in: noticeIds } },
select: { userId: true, id: true, type: true, title: true, content: true, createdAt: true, createdBy: { select: { username: true } } },
});

emitUserNoticeCreates(createdNotices);

const warnedUsers = await prisma.user.findMany({
where: { id: { in: sanitizedUserIds } },
select: { id: true, username: true },
const [status, error] = await warnUsersBatch({
userIds: req.body.userIds,
reason: req.body.reason,
modUserId: req.userCache.id,
});

await prisma.modAuditLog.createMany({
data: warnedUsers.map((user) => ({
id: generateId(),
actionType: ModAuditLogType.userWarned,
actionById: req.userCache.id,
username: user.username,
userId: user.id,
reason: req.body.reason,
})),
});
if (error) {
return res.status(403).json(error);
}

res.status(200).json({ success: true });
return res.status(200).json({ status });
}
89 changes: 87 additions & 2 deletions src/services/Moderation.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
import { removeUserCacheByUserIds } from '../cache/UserCache';
import { dateToDateTime, prisma } from '../common/database';
import { generateId } from '../common/flakeId';
import { ModAuditLogType } from '../common/ModAuditLog';
import { removeDuplicates } from '../common/utils';
import { emitErrorTo } from '../emits/Connection';
import { emitUserNoticeCreates } from '../emits/User';
import { NoticeType } from '../routes/moderation/userBatchWarn';

interface DisconnectUsersOptions {
userIds: string[];
message?: string;
type?: string;
reason?: string;
expire?: string | null;
by?: {username: string}
by?: { username: string };
clearCache: boolean;
}

export async function disconnectUsers(opts: DisconnectUsersOptions) {
if (!opts.userIds.length) {
return;
}

if (opts.clearCache) {
await removeUserCacheByUserIds(opts.userIds);
}
Expand All @@ -32,3 +38,82 @@ export async function disconnectUsers(opts: DisconnectUsersOptions) {
},
});
}

interface WarnUsersOpts {
userIds: string[];
reason: string;
modUserId: string;
skipAuditLog?: boolean;
}
export async function warnUsersBatch(opts: WarnUsersOpts) {
if (opts.userIds.length >= 5000) return [null, 'user ids must contain less than 5000 ids.'] as const;

const sanitizedUserIds = removeDuplicates(opts.userIds) as string[];

const sixMonthsMS = new Date().setMonth(new Date().getMonth() + 6);

await prisma.account.updateMany({
where: {
warnExpiresAt: {
lt: dateToDateTime(),
},
},
data: {
warnCount: 0,
warnExpiresAt: null,
},
});

await prisma.account.updateMany({
where: {
userId: { in: sanitizedUserIds },
},
data: {
warnCount: { increment: 1 },
warnExpiresAt: dateToDateTime(sixMonthsMS),
},
});

const noticeIds: string[] = [];

await prisma.userNotice.createMany({
data: sanitizedUserIds.map((userId) => {
const id = generateId();
noticeIds.push(id);

return {
id,
userId,
content: opts.reason,
type: NoticeType.Warning,
createdById: opts.modUserId,
};
}),
});

const createdNotices = await prisma.userNotice.findMany({
where: { id: { in: noticeIds } },
select: { userId: true, id: true, type: true, title: true, content: true, createdAt: true, createdBy: { select: { username: true } } },
});

emitUserNoticeCreates(createdNotices);

if (!opts.skipAuditLog) {
const warnedUsers = await prisma.user.findMany({
where: { id: { in: sanitizedUserIds } },
select: { id: true, username: true },
});
await prisma.modAuditLog.createMany({
data: warnedUsers.map((user) => ({
id: generateId(),
actionType: ModAuditLogType.userWarned,
actionById: opts.modUserId,
username: user.username,
userId: user.id,
reason: opts.reason,
})),
});
}

return [true, null] as const;
}

0 comments on commit a2fb591

Please sign in to comment.