"use server"; import { revalidatePath } from "next/cache"; import type { Prisma } from "@/generated/prisma/client"; import { requirePermission } from "@/lib/admin/guard"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { rcon } from "@/lib/services/rcon"; import { logStaffActivity } from "@/lib/services/staff-activity"; const BC_PAGE_FIELDS = [ "caption", "parentId", "pageLayout", "enabled", "visible", "orderNum", "iconImage", "iconColor", "pageHeadline", "pageTeaser", "pageSpecial", "pageText1", "pageText2", "pageTextDetails", "pageTextTeaser", ] as const; const BC_ITEM_FIELDS = [ "itemIds", "catalogName", "orderNumber", "extradata", "pageId", ] as const; function pickAllowed( fields: Record, allowed: readonly string[], ) { const out: Record = {}; for (const key of allowed) { if (Object.hasOwn(fields, key) && fields[key] !== undefined) { out[key] = fields[key]; } } return out; } export async function updateBcPage({ id, ...fields }: { id: number } & Record) { const staff = await requirePermission(PERMS.CATALOG_EDIT); const data = pickAllowed(fields, BC_PAGE_FIELDS); if (Object.keys(data).length === 0) { return { ok: false as const, error: "No valid fields to update" }; } await prisma.catalogPagesBc.update({ where: { id }, data: data as Prisma.CatalogPagesBcUpdateInput, }); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "bc_page_update", description: `Updated BC catalog page #${id}`, targetType: "catalog_page_bc", targetId: id, }); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const }; } export async function deleteBcItem({ id }: { id: number }) { const staff = await requirePermission(PERMS.CATALOG_EDIT); await prisma.catalogItemsBc.delete({ where: { id } }); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "bc_item_delete", description: `Deleted BC catalog item #${id}`, targetType: "catalog_item_bc", targetId: id, }); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const }; } export async function updateBcItem({ id, ...data }: { id: number; itemIds?: string; catalogName?: string; orderNumber?: number; extradata?: string; }) { const staff = await requirePermission(PERMS.CATALOG_EDIT); const safe = pickAllowed(data as Record, BC_ITEM_FIELDS); if (Object.keys(safe).length === 0) { return { ok: false as const, error: "No valid fields to update" }; } await prisma.catalogItemsBc.update({ where: { id }, data: safe as Prisma.CatalogItemsBcUpdateInput, }); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "bc_item_update", description: `Updated BC catalog item #${id}`, targetType: "catalog_item_bc", targetId: id, }); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const }; } export async function createBcItem({ pageId, ...data }: { pageId: number; itemIds: string; catalogName: string; orderNumber: number; extradata: string; }) { const staff = await requirePermission(PERMS.CATALOG_EDIT); const created = await prisma.catalogItemsBc.create({ data: { pageId, ...data }, }); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "bc_item_create", description: `Created BC catalog item #${created.id}`, targetType: "catalog_item_bc", targetId: created.id, }); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const, data: { id: created.id } }; } export async function toggleBcPage({ id, field, }: { id: number; field: "enabled" | "visible"; }) { await requirePermission(PERMS.CATALOG_EDIT); const page = await prisma.catalogPagesBc.findUnique({ where: { id }, select: { enabled: true, visible: true }, }); if (!page) return { ok: false as const, error: "Page not found" }; await prisma.catalogPagesBc.update({ where: { id }, data: { [field]: page[field] === "1" ? "0" : "1" }, }); await rcon.updateCatalog(); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const }; } export async function createBcPage(input: { caption: string; parentId: number; pageLayout?: string; iconImage?: number; iconColor?: number; enabled?: "0" | "1"; visible?: "0" | "1"; orderNum?: number; }) { const staff = await requirePermission(PERMS.CATALOG_EDIT); const created = await prisma.catalogPagesBc.create({ data: { caption: input.caption, parentId: input.parentId, pageLayout: input.pageLayout ?? "default_3x3", iconColor: input.iconColor ?? 0, iconImage: input.iconImage ?? 0, orderNum: input.orderNum ?? 0, visible: input.visible ?? "1", enabled: input.enabled ?? "1", pageHeadline: "", pageTeaser: "", }, }); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "bc_page_create", description: `Created BC catalog page "${input.caption}"`, targetType: "catalog_page_bc", targetId: created.id, }); revalidatePath("/admin/catalog"); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const, data: { id: created.id } }; } async function moveBcPage(pageId: number, newParentId: number): Promise { if (newParentId > 0) { let currentId = newParentId; for (let i = 0; i < 50; i++) { if (currentId === pageId) { throw new Error("Cannot move page: would create a circular hierarchy"); } const parent = await prisma.catalogPagesBc.findUnique({ where: { id: currentId }, select: { parentId: true }, }); if (!parent || parent.parentId <= 0) break; currentId = parent.parentId; } } await prisma.catalogPagesBc.update({ where: { id: pageId }, data: { parentId: newParentId }, }); } export async function reorderBcTreePage(input: { pageId: number; newParentId?: number; newOrderNum: number; }) { await requirePermission(PERMS.CATALOG_EDIT); if (input.newParentId !== undefined) { try { await moveBcPage(input.pageId, input.newParentId); } catch (err) { return { ok: false as const, error: err instanceof Error ? err.message : "Invalid move", }; } } await prisma.catalogPagesBc.update({ where: { id: input.pageId }, data: { orderNum: input.newOrderNum }, }); await rcon.updateCatalog(); revalidatePath("/admin/catalog"); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const, data: {} }; } export async function deleteBcTreePage(input: { pageId: number; mode: "reparent" | "cascade"; }) { await requirePermission(PERMS.CATALOG_EDIT); const page = await prisma.catalogPagesBc.findUnique({ where: { id: input.pageId }, select: { parentId: true }, }); if (!page) return { ok: false as const, error: "Page not found" }; if (input.mode === "reparent") { await prisma.$transaction([ prisma.catalogPagesBc.updateMany({ where: { parentId: input.pageId }, data: { parentId: page.parentId }, }), prisma.catalogItemsBc.deleteMany({ where: { pageId: input.pageId } }), prisma.catalogPagesBc.delete({ where: { id: input.pageId } }), ]); } else { const toDelete: number[] = [input.pageId]; const queue: number[] = [input.pageId]; while (queue.length > 0) { const children = await prisma.catalogPagesBc.findMany({ where: { parentId: { in: queue } }, select: { id: true }, }); queue.length = 0; for (const child of children) { toDelete.push(child.id); queue.push(child.id); } } await prisma.$transaction([ prisma.catalogItemsBc.deleteMany({ where: { pageId: { in: toDelete } }, }), prisma.catalogPagesBc.deleteMany({ where: { id: { in: toDelete } }, }), ]); } await rcon.updateCatalog(); revalidatePath("/admin/catalog"); revalidatePath("/admin/catalog/builder-club"); return { ok: true as const, data: {} }; }