Local Build and Deploy / deploy (push) Successful in 56s
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]>
177 lines
4.4 KiB
TypeScript
177 lines
4.4 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { requirePermission } from "@/lib/admin/guard";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
|
|
|
// Writeable boxes (website_writeable_boxes). CMS-owned table backing the
|
|
// content panels rendered on the public home page. Active boxes (is_active)
|
|
// are the ones shown publicly, ordered by `position`.
|
|
|
|
/** Parse a non-negative Int form value, falling back to 0. */
|
|
function reqInt(formData: FormData, key: string): number {
|
|
const raw = String(formData.get(key) ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
if (raw === "") return 0;
|
|
const n = Number(raw);
|
|
if (!Number.isFinite(n) || n < 0) return 0;
|
|
return Math.floor(n);
|
|
}
|
|
|
|
/** Parse the BigInt `id` form value, returning null when blank/invalid. */
|
|
function parseId(formData: FormData): bigint | null {
|
|
const raw = String(formData.get("id") ?? "")
|
|
.normalize("NFC")
|
|
.trim();
|
|
if (!raw) return null;
|
|
try {
|
|
return BigInt(raw);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function revalidate(): void {
|
|
revalidatePath("/admin/writeable-boxes");
|
|
// Active boxes render on the public home page (root layout).
|
|
revalidatePath("/", "layout");
|
|
}
|
|
|
|
export async function createBox(formData: FormData): Promise<void> {
|
|
const staff = await requirePermission(PERMS.PAGES_EDIT);
|
|
|
|
const title = String(formData.get("title") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255);
|
|
if (!title) return;
|
|
|
|
const now = new Date();
|
|
try {
|
|
const created = await prisma.websiteWriteableBoxes.create({
|
|
data: {
|
|
title,
|
|
icon:
|
|
String(formData.get("icon") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255) || null,
|
|
content: String(formData.get("content") ?? "").normalize("NFC"),
|
|
position: reqInt(formData, "position"),
|
|
isActive:
|
|
String(formData.get("isActive") ?? "").normalize("NFC") === "1",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
},
|
|
});
|
|
await logStaffActivity({
|
|
staffId: staff.id,
|
|
action: "writeable_box_create",
|
|
description: `Created writeable box "${title}" (#${created.id})`,
|
|
targetType: "writeable_box",
|
|
targetId: Number(created.id),
|
|
});
|
|
} catch {
|
|
// DB unavailable — swallow and re-render.
|
|
return;
|
|
}
|
|
|
|
revalidate();
|
|
}
|
|
|
|
export async function updateBox(formData: FormData): Promise<void> {
|
|
const staff = await requirePermission(PERMS.PAGES_EDIT);
|
|
|
|
const id = parseId(formData);
|
|
if (id == null) return;
|
|
|
|
const title = String(formData.get("title") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255);
|
|
if (!title) return;
|
|
|
|
try {
|
|
await prisma.websiteWriteableBoxes.update({
|
|
where: { id },
|
|
data: {
|
|
title,
|
|
icon:
|
|
String(formData.get("icon") ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, 255) || null,
|
|
content: String(formData.get("content") ?? "").normalize("NFC"),
|
|
position: reqInt(formData, "position"),
|
|
isActive:
|
|
String(formData.get("isActive") ?? "").normalize("NFC") === "1",
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
await logStaffActivity({
|
|
staffId: staff.id,
|
|
action: "writeable_box_update",
|
|
description: `Updated writeable box #${id} ("${title}")`,
|
|
targetType: "writeable_box",
|
|
targetId: Number(id),
|
|
});
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
revalidate();
|
|
}
|
|
|
|
export async function deleteBox(formData: FormData): Promise<void> {
|
|
const staff = await requirePermission(PERMS.PAGES_EDIT);
|
|
|
|
const id = parseId(formData);
|
|
if (id == null) return;
|
|
|
|
try {
|
|
await prisma.websiteWriteableBoxes.delete({ where: { id } });
|
|
await logStaffActivity({
|
|
staffId: staff.id,
|
|
action: "writeable_box_delete",
|
|
description: `Deleted writeable box #${id}`,
|
|
targetType: "writeable_box",
|
|
targetId: Number(id),
|
|
});
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
revalidate();
|
|
}
|
|
|
|
export async function toggleBox(formData: FormData): Promise<void> {
|
|
const staff = await requirePermission(PERMS.PAGES_EDIT);
|
|
|
|
const id = parseId(formData);
|
|
if (id == null) return;
|
|
|
|
// `next` carries the desired state ("1" to activate, anything else to hide).
|
|
const next = String(formData.get("next") ?? "").normalize("NFC") === "1";
|
|
|
|
try {
|
|
await prisma.websiteWriteableBoxes.update({
|
|
where: { id },
|
|
data: { isActive: next, updatedAt: new Date() },
|
|
});
|
|
await logStaffActivity({
|
|
staffId: staff.id,
|
|
action: "writeable_box_toggle",
|
|
description: `${next ? "Activated" : "Hid"} writeable box #${id}`,
|
|
targetType: "writeable_box",
|
|
targetId: Number(id),
|
|
});
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
revalidate();
|
|
}
|