Files
Epicnabbo-Catalogus-Updated…/src/features/housekeeping/domains/people/commands/user-commands.ts
T

242 lines
6.6 KiB
TypeScript

import "server-only";
import { z } from "zod";
import { PERMS } from "@/lib/permission-slugs";
import type { HousekeepingCommand } from "../../../foundation/commands/registry";
import { anyCapability } from "../../../foundation/contracts";
import {
type PeopleMutationOperation,
type PeopleMutationService,
peopleMutationService,
} from "../services/mutations";
export const USER_COMMAND_IDS = [
"people.user.update",
"people.user.ban",
"people.user.unban",
"people.user.alert",
"people.user.disconnect",
"people.user.mute",
"people.user.unmute",
"people.user.reset-password",
"people.user.send-currency",
"people.user.trade-lock",
"people.users.bulk-ban",
"people.users.bulk-unban",
"people.users.bulk-currency",
"people.users.bulk-badge",
] as const;
export type UserCommandId = (typeof USER_COMMAND_IDS)[number];
interface UserCommandOptions<I> {
readonly id: UserCommandId;
readonly operation: PeopleMutationOperation;
readonly capability: string;
readonly input: z.ZodType<I>;
readonly requiresReason: boolean;
readonly attempts?: number;
}
function userCommand<I>(
service: Pick<PeopleMutationService, "execute">,
options: UserCommandOptions<I>,
): HousekeepingCommand<I, unknown> {
return {
id: options.id,
owner: "people",
risk: "sensitive",
capability: anyCapability(options.capability),
input: options.input,
requiresReason: options.requiresReason,
rateLimit: { attempts: options.attempts ?? 10, windowMs: 60_000 },
execute: (context, input) =>
service.execute(
{
capability: context.capability,
correlationId: context.correlationId,
},
options.operation,
input,
),
};
}
const positiveId = z.number().int().positive();
const requiredText = (max: number) => z.string().min(1).max(max).regex(/\S/u);
const userIds = z.array(positiveId).min(1).max(100);
const optionalUpdateFields = {
username: z.string().min(3).max(20).optional(),
mail: z.email().optional(),
rank: z.number().int().min(1).max(7).optional(),
motto: z.string().max(127).optional(),
credits: z.number().int().min(0).max(2_147_483_647).optional(),
pixels: z.number().int().min(0).max(2_147_483_647).optional(),
diamonds: z.number().int().min(0).max(2_147_483_647).optional(),
duckets: z.number().int().min(0).max(2_147_483_647).optional(),
};
const updateFields = z.union([
z.object({ ...optionalUpdateFields, username: z.string().min(3).max(20) }),
z.object({ ...optionalUpdateFields, mail: z.email() }),
z.object({ ...optionalUpdateFields, rank: z.number().int().min(1).max(7) }),
z.object({ ...optionalUpdateFields, motto: z.string().max(127) }),
z.object({
...optionalUpdateFields,
credits: z.number().int().min(0).max(2_147_483_647),
}),
z.object({
...optionalUpdateFields,
pixels: z.number().int().min(0).max(2_147_483_647),
}),
z.object({
...optionalUpdateFields,
diamonds: z.number().int().min(0).max(2_147_483_647),
}),
z.object({
...optionalUpdateFields,
duckets: z.number().int().min(0).max(2_147_483_647),
}),
]);
export function createUserCommands(
service: Pick<PeopleMutationService, "execute">,
) {
return [
userCommand(service, {
id: "people.user.update",
operation: "user.update",
capability: PERMS.USERS_EDIT,
input: z.object({ userId: positiveId, fields: updateFields }),
requiresReason: false,
}),
userCommand(service, {
id: "people.user.ban",
operation: "user.ban",
capability: PERMS.USERS_BAN,
input: z.object({
userId: positiveId,
reason: requiredText(500),
duration: z.number().int().min(0).max(87_600),
type: z.enum(["account", "ip", "machine"]),
ip: z.string().max(255).optional(),
}),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.unban",
operation: "user.unban",
capability: PERMS.USERS_BAN,
input: z.object({ userId: positiveId }),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.alert",
operation: "user.alert",
capability: PERMS.USERS_EDIT,
input: z.object({ userId: positiveId, message: requiredText(500) }),
requiresReason: false,
}),
userCommand(service, {
id: "people.user.disconnect",
operation: "user.disconnect",
capability: PERMS.USERS_EDIT,
input: z.object({ userId: positiveId }),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.mute",
operation: "user.mute",
capability: PERMS.USERS_EDIT,
input: z.object({
userId: positiveId,
duration: z.number().int().min(0).max(87_600),
}),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.unmute",
operation: "user.unmute",
capability: PERMS.USERS_EDIT,
input: z.object({ userId: positiveId }),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.reset-password",
operation: "user.reset-password",
capability: PERMS.USERS_RESET_PASSWORD,
input: z.object({ userId: positiveId }),
requiresReason: true,
attempts: 3,
}),
userCommand(service, {
id: "people.user.send-currency",
operation: "user.send-currency",
capability: PERMS.USERS_EDIT,
input: z.object({
userId: positiveId,
amount: z.number().int().min(1).max(1_000_000),
}),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.user.trade-lock",
operation: "user.trade-lock",
capability: PERMS.USERS_EDIT,
input: z.object({
userId: positiveId,
untilUnix: z.number().int().min(0).max(2_147_483_647),
}),
requiresReason: true,
attempts: 5,
}),
userCommand(service, {
id: "people.users.bulk-ban",
operation: "users.bulk-ban",
capability: PERMS.USERS_EDIT,
input: z.object({
userIds,
reason: requiredText(500),
duration: z.number().int().min(0).max(315_360_000),
}),
requiresReason: true,
attempts: 3,
}),
userCommand(service, {
id: "people.users.bulk-unban",
operation: "users.bulk-unban",
capability: PERMS.USERS_EDIT,
input: z.object({ userIds }),
requiresReason: true,
attempts: 3,
}),
userCommand(service, {
id: "people.users.bulk-currency",
operation: "users.bulk-currency",
capability: PERMS.USERS_EDIT,
input: z.object({
userIds,
amount: z.number().int().min(1).max(1_000_000),
type: z.enum(["credits", "pixels", "points"]),
}),
requiresReason: true,
attempts: 3,
}),
userCommand(service, {
id: "people.users.bulk-badge",
operation: "users.bulk-badge",
capability: PERMS.USERS_EDIT,
input: z.object({ userIds, badgeCode: requiredText(20) }),
requiresReason: true,
attempts: 3,
}),
] as const;
}
export const USER_COMMANDS = createUserCommands(peopleMutationService);