"use server"; import { and, eq, max } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { requirePermission } from "@/lib/admin/guard"; import { db, UsersBadges } from "@/lib/db"; import { PERMS } from "@/lib/permissions"; import { rcon } from "@/lib/services/rcon"; export async function giveBadge(formData: FormData): Promise { await requirePermission(PERMS.CATALOG_EDIT); const userId = Number(formData.get("userId")); const code = String(formData.get("code") ?? "") .normalize("NFC") .trim() .slice(0, 32); if (!(userId > 0) || code.length === 0) return; // Fire the emulator command so the badge appears live for online users. await rcon.giveBadge(userId, code); // Persist the badge directly so it survives a relog / offline grant. // users_badges has no unique (user_id, badge_code) constraint, so guard // against duplicates and compute the next free slot ourselves. try { const [existing] = await db .select({ id: UsersBadges.id }) .from(UsersBadges) .where( and(eq(UsersBadges.userId, userId), eq(UsersBadges.badgeCode, code)), ) .limit(1); if (!existing) { const [agg] = await db .select({ maxSlot: max(UsersBadges.slotId) }) .from(UsersBadges) .where(eq(UsersBadges.userId, userId)); const slotId = (agg?.maxSlot ?? 0) + 1; await db.insert(UsersBadges).values({ userId, slotId, badgeCode: code }); } } catch { // Best-effort: the RCON grant already succeeded for online users. } revalidatePath("/admin/badges"); }