feat: export Catalog Studio assets and SQL to catalog repository
This commit is contained in:
1 parent
bfbf244141
commit
9ec9ab31ad
21 files changed
+1811
-514
No files matched your search
+174
-155
@@ -5,6 +5,7 @@ import { revalidatePath } from "next/cache";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { CatalogItemsBc, CatalogPagesBc, db } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
|
||||
@@ -52,39 +53,43 @@ export async function updateBcPage({
|
||||
...fields
|
||||
}: { id: number } & Record<string, unknown>) {
|
||||
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 db
|
||||
.update(CatalogPagesBc)
|
||||
.set(data as Partial<typeof CatalogPagesBc.$inferInsert>)
|
||||
.where(eq(CatalogPagesBc.id, id));
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_page_update",
|
||||
description: `Updated BC catalog page #${id}`,
|
||||
targetType: "catalog_page_bc",
|
||||
targetId: id,
|
||||
return await withCatalogExport(async () => {
|
||||
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 db
|
||||
.update(CatalogPagesBc)
|
||||
.set(data as Partial<typeof CatalogPagesBc.$inferInsert>)
|
||||
.where(eq(CatalogPagesBc.id, id));
|
||||
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 };
|
||||
});
|
||||
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 db.delete(CatalogItemsBc).where(eq(CatalogItemsBc.id, 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,
|
||||
return await withCatalogExport(async () => {
|
||||
await db.delete(CatalogItemsBc).where(eq(CatalogItemsBc.id, 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 };
|
||||
});
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const };
|
||||
}
|
||||
|
||||
export async function updateBcItem({
|
||||
@@ -98,24 +103,26 @@ export async function updateBcItem({
|
||||
extradata?: string;
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
const safe = pickAllowed(data as Record<string, unknown>, BC_ITEM_FIELDS);
|
||||
if (Object.keys(safe).length === 0) {
|
||||
return { ok: false as const, error: "No valid fields to update" };
|
||||
}
|
||||
await db
|
||||
.update(CatalogItemsBc)
|
||||
.set(safe as Partial<typeof CatalogItemsBc.$inferInsert>)
|
||||
.where(eq(CatalogItemsBc.id, id));
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_update",
|
||||
description: `Updated BC catalog item #${id}`,
|
||||
targetType: "catalog_item_bc",
|
||||
targetId: id,
|
||||
return await withCatalogExport(async () => {
|
||||
const safe = pickAllowed(data as Record<string, unknown>, BC_ITEM_FIELDS);
|
||||
if (Object.keys(safe).length === 0) {
|
||||
return { ok: false as const, error: "No valid fields to update" };
|
||||
}
|
||||
await db
|
||||
.update(CatalogItemsBc)
|
||||
.set(safe as Partial<typeof CatalogItemsBc.$inferInsert>)
|
||||
.where(eq(CatalogItemsBc.id, id));
|
||||
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 };
|
||||
});
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const };
|
||||
}
|
||||
|
||||
export async function createBcItem({
|
||||
@@ -129,18 +136,22 @@ export async function createBcItem({
|
||||
extradata: string;
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
const [result] = await db.insert(CatalogItemsBc).values({ pageId, ...data });
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_create",
|
||||
description: `Created BC catalog item #${createdId}`,
|
||||
targetType: "catalog_item_bc",
|
||||
targetId: createdId,
|
||||
return await withCatalogExport(async () => {
|
||||
const [result] = await db
|
||||
.insert(CatalogItemsBc)
|
||||
.values({ pageId, ...data });
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_create",
|
||||
description: `Created BC catalog item #${createdId}`,
|
||||
targetType: "catalog_item_bc",
|
||||
targetId: createdId,
|
||||
});
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: { id: createdId } };
|
||||
});
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: { id: createdId } };
|
||||
}
|
||||
|
||||
export async function toggleBcPage({
|
||||
@@ -151,22 +162,24 @@ export async function toggleBcPage({
|
||||
field: "enabled" | "visible";
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
const [page] = await db
|
||||
.select({
|
||||
enabled: CatalogPagesBc.enabled,
|
||||
visible: CatalogPagesBc.visible,
|
||||
})
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, id))
|
||||
.limit(1);
|
||||
if (!page) return { ok: false as const, error: "Page not found" };
|
||||
await db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ [field]: page[field] === "1" ? "0" : "1" })
|
||||
.where(eq(CatalogPagesBc.id, id));
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const };
|
||||
return await withCatalogExport(async () => {
|
||||
const [page] = await db
|
||||
.select({
|
||||
enabled: CatalogPagesBc.enabled,
|
||||
visible: CatalogPagesBc.visible,
|
||||
})
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, id))
|
||||
.limit(1);
|
||||
if (!page) return { ok: false as const, error: "Page not found" };
|
||||
await db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ [field]: page[field] === "1" ? "0" : "1" })
|
||||
.where(eq(CatalogPagesBc.id, id));
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const };
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBcPage(input: {
|
||||
@@ -180,30 +193,32 @@ export async function createBcPage(input: {
|
||||
orderNum?: number;
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
const [result] = await db.insert(CatalogPagesBc).values({
|
||||
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: "",
|
||||
return await withCatalogExport(async () => {
|
||||
const [result] = await db.insert(CatalogPagesBc).values({
|
||||
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: "",
|
||||
});
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_page_create",
|
||||
description: `Created BC catalog page "${input.caption}"`,
|
||||
targetType: "catalog_page_bc",
|
||||
targetId: createdId,
|
||||
});
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: { id: createdId } };
|
||||
});
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_page_create",
|
||||
description: `Created BC catalog page "${input.caption}"`,
|
||||
targetType: "catalog_page_bc",
|
||||
targetId: createdId,
|
||||
});
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: { id: createdId } };
|
||||
}
|
||||
|
||||
async function moveBcPage(pageId: number, newParentId: number): Promise<void> {
|
||||
@@ -234,24 +249,26 @@ export async function reorderBcTreePage(input: {
|
||||
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",
|
||||
};
|
||||
return await withCatalogExport(async () => {
|
||||
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 db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ orderNum: input.newOrderNum })
|
||||
.where(eq(CatalogPagesBc.id, input.pageId));
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
await db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ orderNum: input.newOrderNum })
|
||||
.where(eq(CatalogPagesBc.id, input.pageId));
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteBcTreePage(input: {
|
||||
@@ -259,52 +276,54 @@ export async function deleteBcTreePage(input: {
|
||||
mode: "reparent" | "cascade";
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
const [page] = await db
|
||||
.select({ parentId: CatalogPagesBc.parentId })
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, input.pageId))
|
||||
.limit(1);
|
||||
if (!page) return { ok: false as const, error: "Page not found" };
|
||||
return await withCatalogExport(async () => {
|
||||
const [page] = await db
|
||||
.select({ parentId: CatalogPagesBc.parentId })
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, input.pageId))
|
||||
.limit(1);
|
||||
if (!page) return { ok: false as const, error: "Page not found" };
|
||||
|
||||
if (input.mode === "reparent") {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(CatalogPagesBc)
|
||||
.set({ parentId: page.parentId })
|
||||
.where(eq(CatalogPagesBc.parentId, input.pageId));
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(eq(CatalogItemsBc.pageId, input.pageId));
|
||||
await tx
|
||||
.delete(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, input.pageId));
|
||||
});
|
||||
} else {
|
||||
const toDelete: number[] = [input.pageId];
|
||||
const queue: number[] = [input.pageId];
|
||||
while (queue.length > 0) {
|
||||
const children = await db
|
||||
.select({ id: CatalogPagesBc.id })
|
||||
.from(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.parentId, queue));
|
||||
queue.length = 0;
|
||||
for (const child of children) {
|
||||
toDelete.push(child.id);
|
||||
queue.push(child.id);
|
||||
if (input.mode === "reparent") {
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.update(CatalogPagesBc)
|
||||
.set({ parentId: page.parentId })
|
||||
.where(eq(CatalogPagesBc.parentId, input.pageId));
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(eq(CatalogItemsBc.pageId, input.pageId));
|
||||
await tx
|
||||
.delete(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, input.pageId));
|
||||
});
|
||||
} else {
|
||||
const toDelete: number[] = [input.pageId];
|
||||
const queue: number[] = [input.pageId];
|
||||
while (queue.length > 0) {
|
||||
const children = await db
|
||||
.select({ id: CatalogPagesBc.id })
|
||||
.from(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.parentId, queue));
|
||||
queue.length = 0;
|
||||
for (const child of children) {
|
||||
toDelete.push(child.id);
|
||||
queue.push(child.id);
|
||||
}
|
||||
}
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(inArray(CatalogItemsBc.pageId, toDelete));
|
||||
await tx
|
||||
.delete(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.id, toDelete));
|
||||
});
|
||||
}
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(CatalogItemsBc)
|
||||
.where(inArray(CatalogItemsBc.pageId, toDelete));
|
||||
await tx
|
||||
.delete(CatalogPagesBc)
|
||||
.where(inArray(CatalogPagesBc.id, toDelete));
|
||||
});
|
||||
}
|
||||
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
await rcon.updateCatalog();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user