"use server"; import { revalidatePath } from "next/cache"; import { z } from "zod"; import { type SystemMutationContext, type SystemMutationOperation, systemMutationService, } from "@/features/housekeeping/domains/system/services/mutations"; import { createHousekeepingCapabilityContext } from "@/features/housekeeping/foundation/capability-context"; import type { AdminActionContext } from "@/lib/foundation/types"; import { PERMS } from "@/lib/permissions"; import { adminAction } from "@/lib/safe-action"; import { ActionError, actionOk } from "@/lib/safe-action-shared"; const PATH = "/admin/commandocentrum"; const RCON_FAIL = "RCON command failed. Is the emulator running?"; function mutationContext( ctx: Pick, ): SystemMutationContext { return { capability: createHousekeepingCapabilityContext( { id: Number(ctx.session.user.id), rank: Number(ctx.session.user.rank), username: ctx.session.user.username, }, ctx.permissions, ), correlationId: String(ctx.requestId), }; } async function runRconMutation( ctx: Pick, operation: SystemMutationOperation, input: unknown, ): Promise { const result = await systemMutationService.execute( mutationContext(ctx), operation, input, ); if (result.ok) return; if (result.error.messageKey === "errors.housekeeping.system.userNotFound") { throw new ActionError("User not found"); } if (result.error.messageKey === "errors.housekeeping.system.rankNotFound") { throw new ActionError("Rank does not exist"); } if ( result.error.messageKey === "errors.housekeeping.system.cannotChangePeerRank" ) { throw new ActionError("Cannot change rank of a user at or above your rank"); } if ( result.error.messageKey === "errors.housekeeping.system.cannotAssignPeerRank" ) { throw new ActionError("Cannot set a rank equal to or above your own"); } throw new ActionError(RCON_FAIL); } function revalidate(): void { revalidatePath(PATH); } export const updateCatalog = adminAction( { permission: PERMS.RCON_EXECUTE }, async (ctx) => { await runRconMutation(ctx, "rcon.update-catalog", {}); revalidate(); return actionOk(); }, ); export const updateWordFilter = adminAction( { permission: PERMS.RCON_EXECUTE }, async (ctx) => { await runRconMutation(ctx, "rcon.update-word-filter", {}); revalidate(); return actionOk(); }, ); export const updateNavigator = adminAction( { permission: PERMS.RCON_EXECUTE }, async (ctx) => { await runRconMutation(ctx, "rcon.update-navigator", {}); revalidate(); return actionOk(); }, ); const hotelAlertSchema = z.object({ message: z.string().trim().min(1).max(512), }); export const hotelAlert = adminAction( { permission: PERMS.RCON_EXECUTE, schema: hotelAlertSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.hotel-alert", { message: ctx.data.message.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const disconnectSchema = z.object({ userId: z.coerce.number().int().positive(), username: z.string().trim().min(1), }); export const disconnectUser = adminAction( { permission: PERMS.RCON_EXECUTE, schema: disconnectSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.disconnect-user", { userId: ctx.data.userId, username: ctx.data.username.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const alertUserSchema = z.object({ userId: z.coerce.number().int().positive(), message: z.string().trim().min(1).max(512), }); export const alertUser = adminAction( { permission: PERMS.RCON_EXECUTE, schema: alertUserSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.alert-user", { userId: ctx.data.userId, message: ctx.data.message.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const forwardUserSchema = z.object({ userId: z.coerce.number().int().positive(), roomId: z.coerce.number().int().positive(), }); export const forwardUser = adminAction( { permission: PERMS.RCON_EXECUTE, schema: forwardUserSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.forward-user", ctx.data); revalidate(); return actionOk(); }, ); const giveCreditsSchema = z.object({ userId: z.coerce.number().int().positive(), credits: z.coerce.number().int().positive(), }); export const giveCredits = adminAction( { permission: PERMS.RCON_EXECUTE, schema: giveCreditsSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.give-credits", { userId: ctx.data.userId, amount: ctx.data.credits, }); revalidate(); return actionOk(); }, ); const giveAmountSchema = z.object({ userId: z.coerce.number().int().positive(), amount: z.coerce.number().int().positive(), }); export const giveDuckets = adminAction( { permission: PERMS.RCON_EXECUTE, schema: giveAmountSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.give-duckets", ctx.data); revalidate(); return actionOk(); }, ); export const giveDiamonds = adminAction( { permission: PERMS.RCON_EXECUTE, schema: giveAmountSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.give-diamonds", ctx.data); revalidate(); return actionOk(); }, ); const giveBadgeSchema = z.object({ userId: z.coerce.number().int().positive(), badge: z.string().trim().min(1).max(32), }); export const giveBadge = adminAction( { permission: PERMS.RCON_EXECUTE, schema: giveBadgeSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.give-badge", { userId: ctx.data.userId, badge: ctx.data.badge.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const setMottoSchema = z.object({ userId: z.coerce.number().int().positive(), motto: z.string().trim().min(1).max(127), }); export const setMotto = adminAction( { permission: PERMS.RCON_EXECUTE, schema: setMottoSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.set-motto", { userId: ctx.data.userId, motto: ctx.data.motto.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const setRankSchema = z.object({ userId: z.coerce.number().int().positive(), rank: z.coerce.number().int().min(1).max(9999), }); export const setRank = adminAction( { permission: PERMS.RCON_EXECUTE, schema: setRankSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.set-rank", ctx.data); revalidate(); return actionOk(); }, ); const executeCommandSchema = z.object({ userId: z.coerce.number().int().positive(), command: z.string().trim().min(1).max(100), }); export const executeCommand = adminAction( { permission: PERMS.RCON_EXECUTE, schema: executeCommandSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.execute-command", { userId: ctx.data.userId, command: ctx.data.command.normalize("NFC"), }); revalidate(); return actionOk(); }, ); const sendGiftSchema = z.object({ userId: z.coerce.number().int().positive(), itemId: z.coerce.number().int().positive(), message: z.string().trim().max(255).optional().default("Here is a gift."), }); export const sendGift = adminAction( { permission: PERMS.RCON_EXECUTE, schema: sendGiftSchema }, async (ctx) => { await runRconMutation(ctx, "rcon.send-gift", { userId: ctx.data.userId, itemId: ctx.data.itemId, message: ctx.data.message.trim().slice(0, 255) || "Here is a gift.", }); revalidate(); return actionOk(); }, );