refactor(catalog): unify commands and embed guarded catalog workspace
CI / check (push) Successful in 1m16s
CI / deploy (push) Successful in 28s

This commit is contained in:
Simo committed 2026-09-06 19:40:52 +02:00
1 parent 16f35568f5
commit 193b6686a9
57 files changed
+3268 -728

No files matched your search

+51 -48
View File
@@ -1,14 +1,19 @@
"use server";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { catalogFailure } from "@/features/catalog/server/errors";
import {
createPageCommand,
deletePageCommand,
reorderPagesCommand,
togglePageCommand,
updatePageCommand,
} from "@/features/catalog/server/page-commands";
import { sendCatalogUpdate } from "@/features/catalog/server/sync-status";
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 { withCatalogExport } from "@/lib/services/catalog-git-queue";
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 = [
@@ -46,8 +51,12 @@ function pickPageFields(fields: Record<string, unknown>) {
export async function updateCatalogPage({
id,
expected,
...fields
}: { id: number } & Record<string, unknown>): Promise<ActionResult> {
}: { id: number; expected?: Record<string, unknown> } & Record<
string,
unknown
>): Promise<ActionResult> {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
const data = pickPageFields(fields);
@@ -57,11 +66,12 @@ export async function updateCatalogPage({
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();
try {
await updatePageCommand("normal", id, data, expected);
} catch (error) {
return { ok: false as const, error: catalogFailure(error).message };
}
await sendCatalogUpdate();
await logStaffActivity({
staffId: staff.id,
action: "catalog_page_update",
@@ -77,8 +87,8 @@ export async function updateCatalogPage({
export async function deleteCatalogPage({ id }: { id: number }) {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
await deletePage(id, "reparent");
await rcon.updateCatalog();
await deletePageCommand("normal", id, "reparent");
await sendCatalogUpdate();
await logStaffActivity({
staffId: staff.id,
action: "catalog_page_delete",
@@ -100,22 +110,12 @@ export async function toggleCatalogPage({
}) {
await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
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();
await togglePageCommand(
"normal",
id,
action === "toggleEnabled" ? "enabled" : "visible",
);
await sendCatalogUpdate();
revalidatePath("/admin/catalog");
return { ok: true as const, data: {} };
});
@@ -134,7 +134,7 @@ export async function createCatalogPage(input: {
}): Promise<ActionResult<{ id: number }>> {
const staff = await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
const [result] = await db.insert(CatalogPages).values({
const createdId = await createPageCommand("normal", {
caption: input.caption,
parentId: input.parentId,
pageLayout: input.pageLayout ?? "default_3x3",
@@ -151,8 +151,7 @@ export async function createCatalogPage(input: {
pageTeaser: "",
includes: "",
});
const createdId = Number(result.insertId);
await rcon.updateCatalog();
await sendCatalogUpdate();
await logStaffActivity({
staffId: staff.id,
action: "catalog_page_create",
@@ -172,21 +171,11 @@ export async function reorderTreePage(input: {
}) {
await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
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();
await updatePageCommand("normal", input.pageId, {
parentId: input.newParentId,
orderNum: input.newOrderNum,
});
await sendCatalogUpdate();
revalidatePath("/admin/catalog");
return { ok: true as const, data: {} };
});
@@ -198,8 +187,22 @@ export async function deleteTreePage(input: {
}) {
await requirePermission(PERMS.CATALOG_EDIT);
return await withCatalogExport(async () => {
await deletePage(input.pageId, input.mode);
await rcon.updateCatalog();
await deletePageCommand("normal", input.pageId, input.mode);
await sendCatalogUpdate();
revalidatePath("/admin/catalog");
return { ok: true as const, data: {} };
});
}
export async function reorderCatalogPages(input: {
parentId: number;
ids: number[];
expectedIds: number[];
}) {
await requirePermission(PERMS.CATALOG_EDIT);
return withCatalogExport(async () => {
await reorderPagesCommand("normal", input);
await sendCatalogUpdate();
revalidatePath("/admin/catalog");
return { ok: true as const, data: {} };
});