45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { requireStaff } from "@/lib/admin/guard";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { rcon } from "@/lib/services/rcon";
|
|
|
|
export async function giveBadge(formData: FormData): Promise<void> {
|
|
await requireStaff();
|
|
|
|
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 prisma.usersBadges.findFirst({
|
|
where: { userId, badgeCode: code },
|
|
select: { id: true },
|
|
});
|
|
if (!existing) {
|
|
const max = await prisma.usersBadges.aggregate({
|
|
where: { userId },
|
|
_max: { slotId: true },
|
|
});
|
|
const slotId = (max._max.slotId ?? 0) + 1;
|
|
await prisma.usersBadges.create({
|
|
data: { userId, slotId, badgeCode: code },
|
|
});
|
|
}
|
|
} catch {
|
|
// Best-effort: the RCON grant already succeeded for online users.
|
|
}
|
|
|
|
revalidatePath("/admin/badges");
|
|
}
|