"use server"; import { revalidatePath } from "next/cache"; import type { $Enums } from "@/generated/prisma/client"; import { requirePermissionRateLimited } from "@/lib/admin/guard"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { rcon } from "@/lib/services/rcon"; import { logStaffActivity } from "@/lib/services/staff-activity"; type BanType = $Enums.bans_type; const BAN_TYPES: ReadonlySet = new Set([ "account", "ip", "machine", "super", ]); export async function createBan(formData: FormData): Promise { const staff = await requirePermissionRateLimited(PERMS.USERS_BAN); const userId = Number(formData.get("userId")); const reason = String(formData.get("reason") ?? "") .normalize("NFC") .trim() .slice(0, 200) || "Banned"; const hours = Number(formData.get("hours")); const type = String(formData.get("type")); if (!(userId > 0) || !BAN_TYPES.has(type)) return; const now = Math.floor(Date.now() / 1000); // Emulator convention: banExpire 0 = permanent (not a far-future timestamp). const banExpire = hours > 0 ? now + Math.floor(hours) * 3600 : 0; const user = await prisma.user.findUnique({ where: { id: userId }, select: { username: true }, }); await prisma.ban.create({ data: { userId, ip: "", machineId: "", userStaffId: staff.id, timestamp: now, banExpire, banReason: reason, type: type as BanType, cfhTopic: -1, }, }); if (user) await rcon.disconnectUser(userId, user.username); await logStaffActivity({ staffId: staff.id, action: "user_ban", description: `Banned user #${userId} (${type}, ${hours > 0 ? `${hours}h` : "permanent"}): ${reason}`, targetType: "user", targetId: userId, }); revalidatePath("/admin/bans"); } export async function liftBan(formData: FormData): Promise { const staff = await requirePermissionRateLimited(PERMS.USERS_BAN); const id = Number(formData.get("id")); if (id > 0) { await prisma.ban.delete({ where: { id } }); await logStaffActivity({ staffId: staff.id, action: "ban_lift", description: `Lifted ban #${id}`, }); } revalidatePath("/admin/bans"); }