284 lines
7.2 KiB
TypeScript
284 lines
7.2 KiB
TypeScript
"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<AdminActionContext, "session" | "permissions" | "requestId">,
|
|
): 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<AdminActionContext, "session" | "permissions" | "requestId">,
|
|
operation: SystemMutationOperation,
|
|
input: unknown,
|
|
): Promise<void> {
|
|
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();
|
|
},
|
|
);
|