refactor(catalog): unify commands and embed guarded catalog workspace
This commit is contained in:
1 parent
16f35568f5
commit
193b6686a9
57 files changed
+3268
-728
No files matched your search
+60
-118
@@ -1,12 +1,24 @@
|
||||
"use server";
|
||||
|
||||
import { eq, inArray } from "drizzle-orm";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { catalogFailure } from "@/features/catalog/server/errors";
|
||||
import {
|
||||
createBcOfferCommand,
|
||||
updateBcOfferCommand,
|
||||
} from "@/features/catalog/server/offer-commands";
|
||||
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 { CatalogItemsBc, CatalogPagesBc, db } from "@/lib/db";
|
||||
import { CatalogItemsBc, 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";
|
||||
|
||||
const BC_PAGE_FIELDS = [
|
||||
@@ -50,19 +62,24 @@ function pickAllowed(
|
||||
|
||||
export async function updateBcPage({
|
||||
id,
|
||||
expected,
|
||||
...fields
|
||||
}: { id: number } & Record<string, unknown>) {
|
||||
}: { id: number; expected?: Record<string, unknown> } & Record<
|
||||
string,
|
||||
unknown
|
||||
>) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
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();
|
||||
try {
|
||||
await updatePageCommand("bc", id, data, expected);
|
||||
} catch (error) {
|
||||
return { ok: false as const, error: catalogFailure(error).message };
|
||||
}
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_page_update",
|
||||
@@ -79,7 +96,7 @@ export async function deleteBcItem({ id }: { id: number }) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
await db.delete(CatalogItemsBc).where(eq(CatalogItemsBc.id, id));
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_delete",
|
||||
@@ -108,11 +125,12 @@ export async function updateBcItem({
|
||||
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();
|
||||
try {
|
||||
await updateBcOfferCommand(id, safe);
|
||||
} catch (error) {
|
||||
return { ok: false as const, error: catalogFailure(error).message };
|
||||
}
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_update",
|
||||
@@ -137,11 +155,8 @@ export async function createBcItem({
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
const [result] = await db
|
||||
.insert(CatalogItemsBc)
|
||||
.values({ pageId, ...data });
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
const createdId = await createBcOfferCommand({ pageId, ...data });
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_item_create",
|
||||
@@ -163,20 +178,8 @@ export async function toggleBcPage({
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
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();
|
||||
await togglePageCommand("bc", id, field);
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const };
|
||||
});
|
||||
@@ -194,7 +197,7 @@ export async function createBcPage(input: {
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
const [result] = await db.insert(CatalogPagesBc).values({
|
||||
const createdId = await createPageCommand("bc", {
|
||||
caption: input.caption,
|
||||
parentId: input.parentId,
|
||||
pageLayout: input.pageLayout ?? "default_3x3",
|
||||
@@ -206,8 +209,7 @@ export async function createBcPage(input: {
|
||||
pageHeadline: "",
|
||||
pageTeaser: "",
|
||||
});
|
||||
const createdId = Number(result.insertId);
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_page_create",
|
||||
@@ -221,28 +223,6 @@ export async function createBcPage(input: {
|
||||
});
|
||||
}
|
||||
|
||||
async function moveBcPage(pageId: number, newParentId: number): Promise<void> {
|
||||
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 db
|
||||
.select({ parentId: CatalogPagesBc.parentId })
|
||||
.from(CatalogPagesBc)
|
||||
.where(eq(CatalogPagesBc.id, currentId))
|
||||
.limit(1);
|
||||
if (!parent || parent.parentId <= 0) break;
|
||||
currentId = parent.parentId;
|
||||
}
|
||||
}
|
||||
await db
|
||||
.update(CatalogPagesBc)
|
||||
.set({ parentId: newParentId })
|
||||
.where(eq(CatalogPagesBc.id, pageId));
|
||||
}
|
||||
|
||||
export async function reorderBcTreePage(input: {
|
||||
pageId: number;
|
||||
newParentId?: number;
|
||||
@@ -250,21 +230,11 @@ export async function reorderBcTreePage(input: {
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
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();
|
||||
await updatePageCommand("bc", input.pageId, {
|
||||
parentId: input.newParentId,
|
||||
orderNum: input.newOrderNum,
|
||||
});
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
@@ -277,51 +247,23 @@ export async function deleteBcTreePage(input: {
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
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" };
|
||||
await deletePageCommand("bc", input.pageId, input.mode);
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
});
|
||||
}
|
||||
|
||||
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 rcon.updateCatalog();
|
||||
export async function reorderBcCatalogPages(input: {
|
||||
parentId: number;
|
||||
ids: number[];
|
||||
expectedIds: number[];
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return withCatalogExport(async () => {
|
||||
await reorderPagesCommand("bc", input);
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog");
|
||||
revalidatePath("/admin/catalog/builder-club");
|
||||
return { ok: true as const, data: {} };
|
||||
|
||||
+34
-112
@@ -2,72 +2,23 @@
|
||||
|
||||
import { eq, inArray, like, or, sql } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { offerPatchSchema } from "@/features/catalog/domain/offer-input";
|
||||
import {
|
||||
createOfferCommand,
|
||||
moveOffersCommand,
|
||||
reorderOffersCommand,
|
||||
updateOfferCommand,
|
||||
} from "@/features/catalog/server/offer-commands";
|
||||
import { sendCatalogUpdate } from "@/features/catalog/server/sync-status";
|
||||
import { requirePermission } from "@/lib/admin/guard";
|
||||
import { CatalogItems, db, ItemsBase } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { withCatalogExport } from "@/lib/services/catalog-git-queue";
|
||||
import { allocateCatalogItemId } from "@/lib/services/furni-import";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||
import { translateItemsSchema } from "@/lib/validators/catalog";
|
||||
|
||||
const CATALOG_ITEM_FIELDS = [
|
||||
"pageId",
|
||||
"itemIds",
|
||||
"catalogName",
|
||||
"costCredits",
|
||||
"costPoints",
|
||||
"pointsType",
|
||||
"amount",
|
||||
"orderNumber",
|
||||
"offerId",
|
||||
"songId",
|
||||
"limitedSells",
|
||||
"limitedStack",
|
||||
"extradata",
|
||||
"haveOffer",
|
||||
"clubOnly",
|
||||
] as const;
|
||||
|
||||
const ITEMS_BASE_FIELDS = [
|
||||
"publicName",
|
||||
"itemName",
|
||||
"type",
|
||||
"width",
|
||||
"length",
|
||||
"stackHeight",
|
||||
"allowStack",
|
||||
"allowSit",
|
||||
"allowLay",
|
||||
"allowWalk",
|
||||
"allowGift",
|
||||
"allowTrade",
|
||||
"allowRecycle",
|
||||
"allowMarketplaceSell",
|
||||
"allowInventoryStack",
|
||||
"interactionType",
|
||||
"interactionModesCount",
|
||||
"vendingIds",
|
||||
"customparams",
|
||||
"effectIdMale",
|
||||
"effectIdFemale",
|
||||
"clothingOnWalk",
|
||||
] as const;
|
||||
|
||||
function pickAllowed(
|
||||
fields: Record<string, unknown>,
|
||||
allowed: readonly string[],
|
||||
): Record<string, unknown> {
|
||||
const out: Record<string, unknown> = {};
|
||||
for (const key of allowed) {
|
||||
if (Object.hasOwn(fields, key) && fields[key] !== undefined) {
|
||||
out[key] = fields[key];
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Raw INSERT — catalog_items.id has no AUTO_INCREMENT on real Habbo DBs; page_id is often VARCHAR. */
|
||||
async function insertCatalogItemRow(data: {
|
||||
pageId: number;
|
||||
@@ -87,8 +38,10 @@ async function insertCatalogItemRow(data: {
|
||||
clubOnly: string;
|
||||
}): Promise<number> {
|
||||
const pageIdStr = String(data.pageId);
|
||||
return allocateCatalogItemId(async (nextId) => {
|
||||
await db.execute(sql`
|
||||
offerPatchSchema.required().parse(data);
|
||||
return allocateCatalogItemId((nextId) =>
|
||||
createOfferCommand("normal", data, async (tx) => {
|
||||
await tx.execute(sql`
|
||||
INSERT INTO catalog_items (
|
||||
id, page_id, item_ids, catalog_name,
|
||||
cost_credits, cost_points, points_type, amount,
|
||||
@@ -102,8 +55,9 @@ async function insertCatalogItemRow(data: {
|
||||
${data.haveOffer}, ${data.clubOnly}
|
||||
)
|
||||
`);
|
||||
return nextId;
|
||||
});
|
||||
return nextId;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function createCatalogItem(data: {
|
||||
@@ -141,7 +95,7 @@ export async function createCatalogItem(data: {
|
||||
}
|
||||
}
|
||||
const id = await insertCatalogItemRow({ ...data, catalogName });
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_item_create",
|
||||
@@ -221,7 +175,7 @@ export async function bulkCreateCatalogItems({
|
||||
}
|
||||
|
||||
if (created > 0) {
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_items_bulk_create",
|
||||
@@ -240,7 +194,7 @@ export async function deleteCatalogItems({ ids }: { ids: number[] }) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
await db.delete(CatalogItems).where(inArray(CatalogItems.id, ids));
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_items_delete",
|
||||
@@ -264,16 +218,8 @@ export async function moveCatalogItems({
|
||||
if (ids.length === 0) {
|
||||
return { ok: true as const, data: {} };
|
||||
}
|
||||
const pageIdStr = String(targetPageId);
|
||||
await db.execute(sql`
|
||||
UPDATE catalog_items
|
||||
SET page_id = ${pageIdStr}
|
||||
WHERE id IN (${sql.join(
|
||||
ids.map((id) => sql`${id}`),
|
||||
sql`, `,
|
||||
)})
|
||||
`);
|
||||
await rcon.updateCatalog();
|
||||
await moveOffersCommand(ids, targetPageId);
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog");
|
||||
return { ok: true as const, data: {} };
|
||||
});
|
||||
@@ -286,13 +232,8 @@ export async function reorderCatalogItems({
|
||||
}) {
|
||||
await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
for (const { id, orderNumber } of orders) {
|
||||
await db
|
||||
.update(CatalogItems)
|
||||
.set({ orderNumber })
|
||||
.where(eq(CatalogItems.id, id));
|
||||
}
|
||||
await rcon.updateCatalog();
|
||||
await reorderOffersCommand(orders);
|
||||
await sendCatalogUpdate();
|
||||
revalidatePath("/admin/catalog");
|
||||
return { ok: true as const, data: {} };
|
||||
});
|
||||
@@ -309,37 +250,18 @@ export async function updateCatalogItem({
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
return await withCatalogExport(async () => {
|
||||
const safeCatalog = pickAllowed(catalogFields, CATALOG_ITEM_FIELDS);
|
||||
if (Object.keys(safeCatalog).length === 0 && !baseItem) {
|
||||
return { ok: false as const, error: "No valid fields to update" };
|
||||
try {
|
||||
await updateOfferCommand({ id, catalogFields, baseItem });
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unable to update catalog item",
|
||||
};
|
||||
}
|
||||
|
||||
// page_id is often VARCHAR — update it via raw SQL when present.
|
||||
const pageIdRaw = safeCatalog.pageId;
|
||||
if (pageIdRaw !== undefined) {
|
||||
const pageIdStr = String(pageIdRaw);
|
||||
await db.execute(sql`
|
||||
UPDATE catalog_items SET page_id = ${pageIdStr} WHERE id = ${id}
|
||||
`);
|
||||
delete safeCatalog.pageId;
|
||||
}
|
||||
|
||||
if (Object.keys(safeCatalog).length > 0) {
|
||||
await db
|
||||
.update(CatalogItems)
|
||||
.set(safeCatalog as Partial<typeof CatalogItems.$inferInsert>)
|
||||
.where(eq(CatalogItems.id, id));
|
||||
}
|
||||
if (baseItem) {
|
||||
const safeBase = pickAllowed(baseItem.fields, ITEMS_BASE_FIELDS);
|
||||
if (Object.keys(safeBase).length > 0) {
|
||||
await db
|
||||
.update(ItemsBase)
|
||||
.set(safeBase as Partial<typeof ItemsBase.$inferInsert>)
|
||||
.where(eq(ItemsBase.id, baseItem.id));
|
||||
}
|
||||
}
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_item_update",
|
||||
@@ -453,7 +375,7 @@ export async function translateCatalogItems(input: {
|
||||
invalidateFurniDataCache();
|
||||
}
|
||||
|
||||
await rcon.updateCatalog();
|
||||
await sendCatalogUpdate();
|
||||
await logAudit({
|
||||
userId: staff.id,
|
||||
action: "items_base_translate",
|
||||
|
||||
+51
-48
@@ -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: {} };
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user