135 lines
3.7 KiB
TypeScript
135 lines
3.7 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 COMMUNITY_COMMAND_IDS = [
|
|
"people.guild.disband",
|
|
"people.application.decide",
|
|
"people.team.change",
|
|
"people.ip.action",
|
|
"people.vpn.configure",
|
|
"people.word-filter.update",
|
|
] as const;
|
|
|
|
export type CommunityCommandId = (typeof COMMUNITY_COMMAND_IDS)[number];
|
|
|
|
interface CommandOptions<I> {
|
|
readonly id: CommunityCommandId;
|
|
readonly operation: PeopleMutationOperation;
|
|
readonly capability: string;
|
|
readonly input: z.ZodType<I>;
|
|
}
|
|
|
|
function communityCommand<I>(
|
|
service: Pick<PeopleMutationService, "execute">,
|
|
options: CommandOptions<I>,
|
|
): HousekeepingCommand<I, unknown> {
|
|
return {
|
|
id: options.id,
|
|
owner: "people",
|
|
risk: "sensitive",
|
|
capability: anyCapability(options.capability),
|
|
input: options.input,
|
|
requiresReason: true,
|
|
rateLimit: { attempts: 5, 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);
|
|
|
|
export function createCommunityCommands(
|
|
service: Pick<PeopleMutationService, "execute">,
|
|
) {
|
|
return [
|
|
communityCommand(service, {
|
|
id: "people.guild.disband",
|
|
operation: "guild.disband",
|
|
capability: PERMS.USERS_EDIT,
|
|
input: z.object({ guildId: positiveId }),
|
|
}),
|
|
communityCommand(service, {
|
|
id: "people.application.decide",
|
|
operation: "application.decide",
|
|
capability: PERMS.USERS_EDIT,
|
|
input: z.object({
|
|
applicationId: positiveId,
|
|
decision: z.literal("dismiss"),
|
|
}),
|
|
}),
|
|
communityCommand(service, {
|
|
id: "people.team.change",
|
|
operation: "team.change",
|
|
capability: PERMS.USERS_EDIT,
|
|
input: z.discriminatedUnion("action", [
|
|
z.object({
|
|
action: z.literal("create"),
|
|
rankName: requiredText(255),
|
|
badge: z.string().max(255).optional(),
|
|
jobDescription: z.string().max(255).optional(),
|
|
staffColor: z.string().min(1).max(255).optional(),
|
|
hiddenRank: z.boolean().optional(),
|
|
}),
|
|
z.object({ action: z.literal("delete"), teamId: positiveId }),
|
|
]),
|
|
}),
|
|
communityCommand(service, {
|
|
id: "people.ip.action",
|
|
operation: "ip.action",
|
|
capability: PERMS.SETTINGS_EDIT,
|
|
input: z.discriminatedUnion("action", [
|
|
z.object({
|
|
action: z.enum(["add-whitelist", "add-blacklist"]),
|
|
ipAddress: requiredText(255),
|
|
asn: z.string().max(255).optional(),
|
|
}),
|
|
z.object({
|
|
action: z.enum(["delete-whitelist", "delete-blacklist"]),
|
|
id: positiveId,
|
|
}),
|
|
]),
|
|
}),
|
|
communityCommand(service, {
|
|
id: "people.vpn.configure",
|
|
operation: "vpn.configure",
|
|
capability: PERMS.SETTINGS_EDIT,
|
|
input: z.object({
|
|
enabled: z.boolean(),
|
|
provider: z.enum(["none", "proxycheck", "ipqualityscore"]),
|
|
apiKey: z.string().max(255),
|
|
blockMessage: z.string().max(255),
|
|
}),
|
|
}),
|
|
communityCommand(service, {
|
|
id: "people.word-filter.update",
|
|
operation: "word-filter.update",
|
|
capability: PERMS.WORDFILTER_EDIT,
|
|
input: z.discriminatedUnion("action", [
|
|
z.object({ action: z.literal("add"), word: requiredText(255) }),
|
|
z.object({ action: z.literal("delete"), id: positiveId }),
|
|
]),
|
|
}),
|
|
] as const;
|
|
}
|
|
|
|
export const COMMUNITY_COMMANDS = createCommunityCommands(
|
|
peopleMutationService,
|
|
);
|