Files
EpicNext-Cms/src/actions/admin-help.ts
T
simoleo89andCursor 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

171 lines
4.7 KiB
TypeScript

"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
import { logStaffActivity } from "@/lib/services/staff-activity";
// CRUD for help-center FAQ entries (website_help_center_categories). Each entry
// is a titled content block with an optional image and call-to-action button.
function parsePosition(value: FormDataEntryValue | null): number {
const n = Number(value);
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 1;
}
export async function createHelpQuestion(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.PAGES_EDIT);
const name = String(formData.get("name") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const content = String(formData.get("content") ?? "")
.normalize("NFC")
.trim();
if (!name || !content) return;
const imageUrl = String(formData.get("imageUrl") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonText = String(formData.get("buttonText") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonUrl = String(formData.get("buttonUrl") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonColor =
String(formData.get("buttonColor") ?? "")
.normalize("NFC")
.trim()
.slice(0, 16) || "#eeb425";
const buttonBorderColor =
String(formData.get("buttonBorderColor") ?? "")
.normalize("NFC")
.trim()
.slice(0, 16) || "#facc15";
try {
const entry = await prisma.websiteHelpCenterCategories.create({
data: {
name,
content,
position: parsePosition(formData.get("position")),
imageUrl: imageUrl || null,
buttonText: buttonText || null,
buttonUrl: buttonUrl || null,
buttonColor,
buttonBorderColor,
smallBox: formData.get("smallBox") != null,
},
});
await logStaffActivity({
staffId: staff.id,
action: "help_create",
description: `Created help-center entry #${entry.id} (${name})`,
targetType: "help_center_category",
targetId: Number(entry.id),
});
} catch {
// Unique name collision or DB error — re-render unchanged with error.
revalidatePath("/admin/help-questions");
redirect(
"/admin/help-questions/new?error=Unique name collision or database error. Please try again.",
);
}
revalidatePath("/admin/help-questions");
redirect("/admin/help-questions");
}
export async function updateHelpQuestion(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
const name = String(formData.get("name") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const content = String(formData.get("content") ?? "")
.normalize("NFC")
.trim();
if (!name || !content) return;
const imageUrl = String(formData.get("imageUrl") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonText = String(formData.get("buttonText") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonUrl = String(formData.get("buttonUrl") ?? "")
.normalize("NFC")
.trim()
.slice(0, 255);
const buttonColor =
String(formData.get("buttonColor") ?? "")
.normalize("NFC")
.trim()
.slice(0, 16) || "#eeb425";
const buttonBorderColor =
String(formData.get("buttonBorderColor") ?? "")
.normalize("NFC")
.trim()
.slice(0, 16) || "#facc15";
try {
await prisma.websiteHelpCenterCategories.update({
where: { id },
data: {
name,
content,
position: parsePosition(formData.get("position")),
imageUrl: imageUrl || null,
buttonText: buttonText || null,
buttonUrl: buttonUrl || null,
buttonColor,
buttonBorderColor,
smallBox: formData.get("smallBox") != null,
},
});
await logStaffActivity({
staffId: staff.id,
action: "help_update",
description: `Updated help-center entry #${id} (${name})`,
targetType: "help_center_category",
targetId: Number(id),
});
} catch {
// Not found, unique collision, or DB error — ignore.
revalidatePath(`/admin/help-questions/${id}`);
return;
}
redirect("/admin/help-questions");
}
export async function deleteHelpQuestion(formData: FormData): Promise<void> {
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
try {
await prisma.websiteHelpCenterCategories.delete({ where: { id } });
await logStaffActivity({
staffId: staff.id,
action: "help_delete",
description: `Deleted help-center entry #${id}`,
targetType: "help_center_category",
targetId: Number(id),
});
} catch {
// Not found or DB error — ignore.
}
redirect("/admin/help-questions");
}