"use server"; import { eq } from "drizzle-orm"; import { revalidatePath } from "next/cache"; import { requirePermission } from "@/lib/admin/guard"; import { CatalogPages, db } from "@/lib/db"; import { PERMS } from "@/lib/permissions"; import type { ActionResult } from "@/lib/safe-action-shared"; import { deletePage, movePage } from "@/lib/services/catalog-tree"; import { rcon } from "@/lib/services/rcon"; import { logStaffActivity } from "@/lib/services/staff-activity"; const CATALOG_PAGE_FIELDS = [ "caption", "parentId", "pageLayout", "enabled", "visible", "minRank", "clubOnly", "vipOnly", "orderNum", "iconImage", "iconColor", "pageHeadline", "pageTeaser", "pageSpecial", "pageText1", "pageText2", "pageTextDetails", "pageTextTeaser", "includes", "captionSave", ] as const; function pickPageFields(fields: Record) { const out: Record = {}; for (const key of CATALOG_PAGE_FIELDS) { if (Object.hasOwn(fields, key) && fields[key] !== undefined) { out[key] = fields[key]; } } return out; } export async function updateCatalogPage({ id, ...fields }: { id: number } & Record): Promise { const staff = await requirePermission(PERMS.CATALOG_EDIT); const data = pickPageFields(fields); if (Object.keys(data).length === 0) { return { ok: false as const, error: "No valid fields to update" }; } if (typeof data.caption === "string" && !data.captionSave) { data.captionSave = data.caption.slice(0, 25); } await db .update(CatalogPages) .set(data as Partial) .where(eq(CatalogPages.id, id)); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "catalog_page_update", description: `Updated catalog page #${id}`, targetType: "catalog_page", targetId: id, }); revalidatePath("/admin/catalog"); return { ok: true as const, data: {} }; } export async function deleteCatalogPage({ id }: { id: number }) { const staff = await requirePermission(PERMS.CATALOG_EDIT); await deletePage(id, "reparent"); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "catalog_page_delete", description: `Deleted catalog page #${id}`, targetType: "catalog_page", targetId: id, }); revalidatePath("/admin/catalog"); return { ok: true as const, data: {} }; } export async function toggleCatalogPage({ id, action, }: { id: number; action: "toggleEnabled" | "toggleVisible"; }) { await requirePermission(PERMS.CATALOG_EDIT); const [page] = await db .select({ enabled: CatalogPages.enabled, visible: CatalogPages.visible, }) .from(CatalogPages) .where(eq(CatalogPages.id, id)) .limit(1); if (!page) return { ok: false as const, error: "Catalog page not found" }; const field = action === "toggleEnabled" ? "enabled" : "visible"; const current = action === "toggleEnabled" ? page.enabled : page.visible; await db .update(CatalogPages) .set({ [field]: current === "1" ? "0" : "1" }) .where(eq(CatalogPages.id, id)); await rcon.updateCatalog(); revalidatePath("/admin/catalog"); return { ok: true as const, data: {} }; } export async function createCatalogPage(input: { caption: string; parentId: number; pageLayout?: string; iconImage?: number; iconColor?: number; enabled?: "0" | "1"; visible?: "0" | "1"; minRank?: number; orderNum?: number; }): Promise> { const staff = await requirePermission(PERMS.CATALOG_EDIT); const [result] = await db.insert(CatalogPages).values({ caption: input.caption, parentId: input.parentId, pageLayout: input.pageLayout ?? "default_3x3", captionSave: input.caption.slice(0, 25), iconColor: input.iconColor ?? 0, iconImage: input.iconImage ?? 0, minRank: input.minRank ?? 1, orderNum: input.orderNum ?? 0, visible: input.visible ?? "1", enabled: input.enabled ?? "1", clubOnly: "0", vipOnly: "0", pageHeadline: "", pageTeaser: "", includes: "", }); const createdId = Number(result.insertId); await rcon.updateCatalog(); await logStaffActivity({ staffId: staff.id, action: "catalog_page_create", description: `Created catalog page "${input.caption}"`, targetType: "catalog_page", targetId: createdId, }); revalidatePath("/admin/catalog"); return { ok: true as const, data: { id: createdId } }; } export async function reorderTreePage(input: { pageId: number; newParentId?: number; newOrderNum: number; }) { await requirePermission(PERMS.CATALOG_EDIT); if (input.newParentId !== undefined) { try { await movePage(input.pageId, input.newParentId); } catch (err) { return { ok: false as const, error: err instanceof Error ? err.message : "Invalid move", }; } } await db .update(CatalogPages) .set({ orderNum: input.newOrderNum }) .where(eq(CatalogPages.id, input.pageId)); await rcon.updateCatalog(); revalidatePath("/admin/catalog"); return { ok: true as const, data: {} }; } export async function deleteTreePage(input: { pageId: number; mode: "reparent" | "cascade"; }) { await requirePermission(PERMS.CATALOG_EDIT); await deletePage(input.pageId, input.mode); await rcon.updateCatalog(); revalidatePath("/admin/catalog"); return { ok: true as const, data: {} }; }