Files
Epicnabbo-Catalogus-Updated…/src/actions/admin-badge-upload.ts
T
simoleoandCursor 0e89d03940 Finish fine-grained ACL across remaining admin pages and actions.
Replace leftover requireStaff gates with module PERMS, drop hardcoded room rank thresholds, and expand contract tests so admin mutations cannot regress to dashboard-only checks.

Co-authored-by: Cursor <[email protected]>
2026-07-15 20:15:22 +02:00

75 lines
2.3 KiB
TypeScript

"use server";
import { writeFile } from "node:fs/promises";
import path from "node:path";
import { redirect } from "next/navigation";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { logStaffActivity } from "@/lib/services/staff-activity";
// Writes a badge image to the configured emulator badge directory. The path is
// read from BADGE_UPLOAD_DIR so deployments can point it at their emulator's
// `swf/c_images/album1584` (or equivalent) without code changes. AtomCMS only
// ever stores .gif badges, so every upload is normalised to `<code>.gif`.
const CODE_RE = /^[A-Za-z0-9_-]{1,64}$/;
const MAX_BYTES = 1024 * 1024; // 1MB
const ALLOWED_TYPES = new Set(["image/gif", "image/png"]);
function back(param: string, value: string): never {
redirect(`/admin/badges?${param}=${encodeURIComponent(value)}`);
}
export async function uploadBadge(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const dir = process.env.BADGE_UPLOAD_DIR;
if (!dir) {
back("error", "Badge upload directory not configured");
}
const code = String(formData.get("code") ?? "")
.normalize("NFC")
.trim();
if (!CODE_RE.test(code)) {
back("error", "Invalid badge code (use A-Z, 0-9, _ or -, max 64 chars)");
}
const file = formData.get("file");
if (!(file instanceof File)) {
back("error", "No file uploaded");
}
if (file.size === 0) {
back("error", "Uploaded file is empty");
}
if (file.size > MAX_BYTES) {
back("error", "File too large (max 1MB)");
}
if (!ALLOWED_TYPES.has(file.type)) {
back("error", "File must be a GIF or PNG image");
}
try {
const buffer = Buffer.from(await file.arrayBuffer());
const baseDir = path.resolve(dir);
const target = path.resolve(baseDir, `${code}.gif`);
if (!target.startsWith(baseDir + path.sep)) {
back("error", "Invalid path");
}
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(target, buffer);
} catch {
back("error", "Could not write the badge file to disk");
}
await logStaffActivity({
staffId: staff.id,
action: "badge_upload",
description: `Uploaded badge image "${code}.gif"`,
targetType: "badge",
});
redirect(`/admin/badges?uploaded=${encodeURIComponent(code)}`);
}