194 lines
5.2 KiB
TypeScript
194 lines
5.2 KiB
TypeScript
"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<string, unknown>) {
|
|
const out: Record<string, unknown> = {};
|
|
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<string, unknown>): Promise<ActionResult> {
|
|
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<typeof CatalogPages.$inferInsert>)
|
|
.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<ActionResult<{ id: number }>> {
|
|
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: {} };
|
|
}
|