feat(housekeeping): deliver people account workflows
This commit is contained in:
1 parent
7920d4f46c
commit
e1b31ff773
45 files changed
+4290
-1243
No files matched your search
@@ -0,0 +1,241 @@
|
||||
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);
|
||||
Reference in new issue
Block a user