Fix catalog items missing due to page_id VARCHAR mismatch.
Local Build and Deploy / deploy (push) Successful in 56s
Local Build and Deploy / deploy (push) Successful in 56s
Use raw SQL for counts/loads/creates/moves so Habbo DBs with VARCHAR page_id and no AUTO_INCREMENT still show and persist furni. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
33a8a19820
commit
7bc0845932
6 files changed
+139
-79
No files matched your search
@@ -1,9 +1,11 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
|
import { Prisma } from "@/generated/prisma/client";
|
||||||
import { requirePermission } from "@/lib/admin/guard";
|
import { requirePermission } from "@/lib/admin/guard";
|
||||||
import { PERMS } from "@/lib/permissions";
|
import { PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { allocateCatalogItemId } from "@/lib/services/furni-import";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
|
|
||||||
@@ -63,6 +65,44 @@ function pickAllowed(
|
|||||||
return out;
|
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;
|
||||||
|
itemIds: string;
|
||||||
|
catalogName: string;
|
||||||
|
costCredits: number;
|
||||||
|
costPoints: number;
|
||||||
|
pointsType: number;
|
||||||
|
amount: number;
|
||||||
|
orderNumber: number;
|
||||||
|
offerId: number;
|
||||||
|
songId: number;
|
||||||
|
limitedSells: number;
|
||||||
|
limitedStack: number;
|
||||||
|
extradata: string;
|
||||||
|
haveOffer: string;
|
||||||
|
clubOnly: string;
|
||||||
|
}): Promise<number> {
|
||||||
|
const pageIdStr = String(data.pageId);
|
||||||
|
return allocateCatalogItemId(async (nextId) => {
|
||||||
|
await prisma.$executeRaw`
|
||||||
|
INSERT INTO catalog_items (
|
||||||
|
id, page_id, item_ids, catalog_name,
|
||||||
|
cost_credits, cost_points, points_type, amount,
|
||||||
|
order_number, offer_id, song_id,
|
||||||
|
limited_sells, limited_stack, extradata, have_offer, club_only
|
||||||
|
) VALUES (
|
||||||
|
${nextId}, ${pageIdStr}, ${data.itemIds}, ${data.catalogName},
|
||||||
|
${data.costCredits}, ${data.costPoints}, ${data.pointsType}, ${data.amount},
|
||||||
|
${data.orderNumber}, ${data.offerId}, ${data.songId},
|
||||||
|
${data.limitedSells}, ${data.limitedStack}, ${data.extradata},
|
||||||
|
${data.haveOffer}, ${data.clubOnly}
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
return nextId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function createCatalogItem(data: {
|
export async function createCatalogItem(data: {
|
||||||
pageId: number;
|
pageId: number;
|
||||||
itemIds: string;
|
itemIds: string;
|
||||||
@@ -92,19 +132,17 @@ export async function createCatalogItem(data: {
|
|||||||
catalogName = base?.publicName || base?.itemName || String(firstId);
|
catalogName = base?.publicName || base?.itemName || String(firstId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const created = await prisma.catalogItems.create({
|
const id = await insertCatalogItemRow({ ...data, catalogName });
|
||||||
data: { ...data, catalogName },
|
|
||||||
});
|
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
action: "catalog_item_create",
|
action: "catalog_item_create",
|
||||||
description: `Created catalog item #${created.id}`,
|
description: `Created catalog item #${id}`,
|
||||||
targetType: "catalog_item",
|
targetType: "catalog_item",
|
||||||
targetId: created.id,
|
targetId: id,
|
||||||
});
|
});
|
||||||
revalidatePath("/admin/catalog");
|
revalidatePath("/admin/catalog");
|
||||||
return { ok: true as const, data: { id: created.id } };
|
return { ok: true as const, data: { id } };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Bulk create with one RCON refresh at the end. */
|
/** Bulk create with one RCON refresh at the end. */
|
||||||
@@ -145,24 +183,22 @@ export async function bulkCreateCatalogItems({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await prisma.catalogItems.create({
|
await insertCatalogItemRow({
|
||||||
data: {
|
pageId,
|
||||||
pageId,
|
itemIds: String(row.baseId),
|
||||||
itemIds: String(row.baseId),
|
catalogName: base.publicName || base.itemName || String(row.baseId),
|
||||||
catalogName: base.publicName || base.itemName || String(row.baseId),
|
costCredits: row.credits ?? 0,
|
||||||
costCredits: row.credits ?? 0,
|
costPoints: row.points ?? 0,
|
||||||
costPoints: row.points ?? 0,
|
pointsType: row.pointsType ?? 0,
|
||||||
pointsType: row.pointsType ?? 0,
|
amount: 1,
|
||||||
amount: 1,
|
limitedSells: 0,
|
||||||
limitedSells: 0,
|
limitedStack: 0,
|
||||||
limitedStack: 0,
|
orderNumber: 1,
|
||||||
orderNumber: 1,
|
offerId: -1,
|
||||||
offerId: -1,
|
songId: 0,
|
||||||
songId: 0,
|
haveOffer: "1",
|
||||||
haveOffer: "1",
|
clubOnly: "0",
|
||||||
clubOnly: "0",
|
extradata: "",
|
||||||
extradata: "",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
created++;
|
created++;
|
||||||
} catch {
|
} catch {
|
||||||
@@ -207,10 +243,15 @@ export async function moveCatalogItems({
|
|||||||
targetPageId: number;
|
targetPageId: number;
|
||||||
}) {
|
}) {
|
||||||
await requirePermission(PERMS.CATALOG_EDIT);
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.catalogItems.updateMany({
|
if (ids.length === 0) {
|
||||||
where: { id: { in: ids } },
|
return { ok: true as const, data: {} };
|
||||||
data: { pageId: targetPageId },
|
}
|
||||||
});
|
const pageIdStr = String(targetPageId);
|
||||||
|
await prisma.$executeRaw`
|
||||||
|
UPDATE catalog_items
|
||||||
|
SET page_id = ${pageIdStr}
|
||||||
|
WHERE id IN (${Prisma.join(ids)})
|
||||||
|
`;
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
revalidatePath("/admin/catalog");
|
revalidatePath("/admin/catalog");
|
||||||
return { ok: true as const, data: {} };
|
return { ok: true as const, data: {} };
|
||||||
@@ -244,6 +285,17 @@ export async function updateCatalogItem({
|
|||||||
if (Object.keys(safeCatalog).length === 0 && !baseItem) {
|
if (Object.keys(safeCatalog).length === 0 && !baseItem) {
|
||||||
return { ok: false as const, error: "No valid fields to update" };
|
return { ok: false as const, error: "No valid fields to update" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// page_id is often VARCHAR — update it via raw SQL when present.
|
||||||
|
const pageIdRaw = safeCatalog.pageId;
|
||||||
|
if (pageIdRaw !== undefined) {
|
||||||
|
const pageIdStr = String(pageIdRaw);
|
||||||
|
await prisma.$executeRaw`
|
||||||
|
UPDATE catalog_items SET page_id = ${pageIdStr} WHERE id = ${id}
|
||||||
|
`;
|
||||||
|
delete safeCatalog.pageId;
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(safeCatalog).length > 0) {
|
if (Object.keys(safeCatalog).length > 0) {
|
||||||
await prisma.catalogItems.update({
|
await prisma.catalogItems.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Prisma } from "@/generated/prisma/client";
|
|||||||
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
import { calcPagination, parseListParams } from "@/lib/admin-helpers";
|
||||||
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { getCatalogItemCounts } from "@/lib/services/catalog-tree";
|
||||||
import { CatalogActions } from "./catalog-actions";
|
import { CatalogActions } from "./catalog-actions";
|
||||||
import { CatalogManagerDialog } from "./catalog-manager-dialog-wrapper";
|
import { CatalogManagerDialog } from "./catalog-manager-dialog-wrapper";
|
||||||
import { CatalogTable } from "./catalog-table";
|
import { CatalogTable } from "./catalog-table";
|
||||||
@@ -238,7 +239,7 @@ export default async function CatalogPagesPage({
|
|||||||
orderBy: { orderNum: "asc" },
|
orderBy: { orderNum: "asc" },
|
||||||
});
|
});
|
||||||
const rootIds = rootPagesRaw.map((p) => p.id);
|
const rootIds = rootPagesRaw.map((p) => p.id);
|
||||||
const [rootChildCounts, rootItemCounts] = await Promise.all([
|
const [rootChildCounts, rootItemCountMap] = await Promise.all([
|
||||||
rootIds.length > 0
|
rootIds.length > 0
|
||||||
? prisma.catalogPages.groupBy({
|
? prisma.catalogPages.groupBy({
|
||||||
by: ["parentId"],
|
by: ["parentId"],
|
||||||
@@ -246,22 +247,12 @@ export default async function CatalogPagesPage({
|
|||||||
_count: true,
|
_count: true,
|
||||||
})
|
})
|
||||||
: [],
|
: [],
|
||||||
rootIds.length > 0
|
getCatalogItemCounts(rootIds),
|
||||||
? prisma.catalogItems
|
|
||||||
.groupBy({
|
|
||||||
by: ["pageId"],
|
|
||||||
where: { pageId: { in: rootIds } },
|
|
||||||
_count: true,
|
|
||||||
})
|
|
||||||
.catch(() => [])
|
|
||||||
: [],
|
|
||||||
]);
|
]);
|
||||||
const rootChildMap = Object.fromEntries(
|
const rootChildMap = Object.fromEntries(
|
||||||
rootChildCounts.map((c) => [c.parentId, c._count]),
|
rootChildCounts.map((c) => [c.parentId, c._count]),
|
||||||
);
|
);
|
||||||
const rootItemMap = Object.fromEntries(
|
const rootItemMap = Object.fromEntries(rootItemCountMap);
|
||||||
rootItemCounts.map((c) => [c.pageId, c._count]),
|
|
||||||
);
|
|
||||||
const rootPages: TreeNodeShape[] = rootPagesRaw.map((p) => ({
|
const rootPages: TreeNodeShape[] = rootPagesRaw.map((p) => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
caption: p.caption,
|
caption: p.caption,
|
||||||
@@ -301,16 +292,16 @@ export default async function CatalogPagesPage({
|
|||||||
if (search) {
|
if (search) {
|
||||||
const searchPattern = `%${search}%`;
|
const searchPattern = `%${search}%`;
|
||||||
const parentClause = hasParent
|
const parentClause = hasParent
|
||||||
? Prisma.sql`AND cp.parentId = ${parentId}`
|
? Prisma.sql`AND cp.parent_id = ${parentId}`
|
||||||
: Prisma.empty;
|
: Prisma.empty;
|
||||||
|
|
||||||
const matchedIds = await prisma.$queryRaw<{ id: number }[]>(Prisma.sql`
|
const matchedIds = await prisma.$queryRaw<{ id: number }[]>(Prisma.sql`
|
||||||
SELECT DISTINCT cp.id FROM catalog_pages cp
|
SELECT DISTINCT cp.id FROM catalog_pages cp
|
||||||
LEFT JOIN catalog_items ci ON ci.pageId = cp.id
|
LEFT JOIN catalog_items ci ON CAST(ci.page_id AS CHAR) = CAST(cp.id AS CHAR)
|
||||||
LEFT JOIN items_base ib ON FIND_IN_SET(ib.id, REPLACE(ci.itemIds, ';', ','))
|
LEFT JOIN items_base ib ON FIND_IN_SET(ib.id, REPLACE(ci.item_ids, ';', ','))
|
||||||
WHERE (cp.caption LIKE ${searchPattern}
|
WHERE (cp.caption LIKE ${searchPattern}
|
||||||
OR cp.pageLayout LIKE ${searchPattern}
|
OR cp.page_layout LIKE ${searchPattern}
|
||||||
OR ci.catalogName LIKE ${searchPattern}
|
OR ci.catalog_name LIKE ${searchPattern}
|
||||||
OR ib.public_name LIKE ${searchPattern}
|
OR ib.public_name LIKE ${searchPattern}
|
||||||
OR ib.item_name LIKE ${searchPattern})
|
OR ib.item_name LIKE ${searchPattern})
|
||||||
${parentClause}
|
${parentClause}
|
||||||
@@ -339,7 +330,7 @@ export default async function CatalogPagesPage({
|
|||||||
const parentIds = [
|
const parentIds = [
|
||||||
...new Set(pages.map((p) => p.parentId).filter((id) => id > 0)),
|
...new Set(pages.map((p) => p.parentId).filter((id) => id > 0)),
|
||||||
];
|
];
|
||||||
const [childCounts, itemCounts, parentPages] = await Promise.all([
|
const [childCounts, itemCountMapRaw, parentPages] = await Promise.all([
|
||||||
pageIds.length > 0
|
pageIds.length > 0
|
||||||
? prisma.catalogPages.groupBy({
|
? prisma.catalogPages.groupBy({
|
||||||
by: ["parentId"],
|
by: ["parentId"],
|
||||||
@@ -347,13 +338,7 @@ export default async function CatalogPagesPage({
|
|||||||
_count: true,
|
_count: true,
|
||||||
})
|
})
|
||||||
: [],
|
: [],
|
||||||
pageIds.length > 0
|
getCatalogItemCounts(pageIds),
|
||||||
? prisma.catalogItems.groupBy({
|
|
||||||
by: ["pageId"],
|
|
||||||
where: { pageId: { in: pageIds } },
|
|
||||||
_count: true,
|
|
||||||
})
|
|
||||||
: [],
|
|
||||||
parentIds.length > 0
|
parentIds.length > 0
|
||||||
? prisma.catalogPages.findMany({
|
? prisma.catalogPages.findMany({
|
||||||
where: { id: { in: parentIds } },
|
where: { id: { in: parentIds } },
|
||||||
@@ -364,9 +349,7 @@ export default async function CatalogPagesPage({
|
|||||||
const childCountMap = Object.fromEntries(
|
const childCountMap = Object.fromEntries(
|
||||||
childCounts.map((c) => [c.parentId, c._count]),
|
childCounts.map((c) => [c.parentId, c._count]),
|
||||||
);
|
);
|
||||||
const itemCountMap = Object.fromEntries(
|
const itemCountMap = Object.fromEntries(itemCountMapRaw);
|
||||||
itemCounts.map((c) => [c.pageId, c._count]),
|
|
||||||
);
|
|
||||||
const parentNameMap = Object.fromEntries(
|
const parentNameMap = Object.fromEntries(
|
||||||
parentPages.map((p) => [p.id, p.caption]),
|
parentPages.map((p) => [p.id, p.caption]),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ import { apiError, apiOk } from "@/lib/api-response";
|
|||||||
import { PERMS } from "@/lib/permissions";
|
import { PERMS } from "@/lib/permissions";
|
||||||
import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader";
|
import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader";
|
||||||
|
|
||||||
|
function jsonSafe<T>(data: T): T {
|
||||||
|
return JSON.parse(
|
||||||
|
JSON.stringify(data, (_k, v) => (typeof v === "bigint" ? Number(v) : v)),
|
||||||
|
) as T;
|
||||||
|
}
|
||||||
|
|
||||||
export const GET = withAdmin(
|
export const GET = withAdmin(
|
||||||
{ permission: PERMS.CATALOG_VIEW },
|
{ permission: PERMS.CATALOG_VIEW },
|
||||||
async (request) => {
|
async (request) => {
|
||||||
@@ -11,6 +17,6 @@ export const GET = withAdmin(
|
|||||||
return apiError("Invalid pageId");
|
return apiError("Invalid pageId");
|
||||||
}
|
}
|
||||||
const data = await loadCatalogItemsData(pageId);
|
const data = await loadCatalogItemsData(pageId);
|
||||||
return apiOk(data as unknown as Record<string, unknown>);
|
return apiOk(jsonSafe(data) as unknown as Record<string, unknown>);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
deletePage,
|
deletePage,
|
||||||
getAncestors,
|
getAncestors,
|
||||||
getTreeFlat,
|
getTreeFlat,
|
||||||
safeItemCounts,
|
getCatalogItemCounts,
|
||||||
toInt,
|
toInt,
|
||||||
} from "@/lib/services/catalog-tree";
|
} from "@/lib/services/catalog-tree";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
@@ -158,7 +158,7 @@ async function getChildren(
|
|||||||
_count: true,
|
_count: true,
|
||||||
})
|
})
|
||||||
: Promise.resolve([]),
|
: Promise.resolve([]),
|
||||||
safeItemCounts(ids),
|
getCatalogItemCounts(ids),
|
||||||
]);
|
]);
|
||||||
const childMap = Object.fromEntries(
|
const childMap = Object.fromEntries(
|
||||||
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
||||||
@@ -234,7 +234,7 @@ async function searchPages(q: string, isBc: boolean): Promise<TreeNode[]> {
|
|||||||
_count: true,
|
_count: true,
|
||||||
})
|
})
|
||||||
: Promise.resolve([]),
|
: Promise.resolve([]),
|
||||||
safeItemCounts(ids),
|
getCatalogItemCounts(ids),
|
||||||
]);
|
]);
|
||||||
const childMap = Object.fromEntries(
|
const childMap = Object.fromEntries(
|
||||||
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
childCounts.map((c) => [toInt(c.parentId), c._count]),
|
||||||
|
|||||||
@@ -135,8 +135,11 @@ export async function loadCatalogItemsData(
|
|||||||
): Promise<CatalogItemsData> {
|
): Promise<CatalogItemsData> {
|
||||||
// Load items via raw query to work around pageId Int vs VARCHAR mismatch
|
// Load items via raw query to work around pageId Int vs VARCHAR mismatch
|
||||||
const pageIdStr = String(pageId);
|
const pageIdStr = String(pageId);
|
||||||
|
// CAST: live Habbo DBs often store page_id as VARCHAR while Prisma maps Int.
|
||||||
const rawItems = await prisma.$queryRaw<Array<Record<string, unknown>>>`
|
const rawItems = await prisma.$queryRaw<Array<Record<string, unknown>>>`
|
||||||
SELECT * FROM catalog_items WHERE page_id = ${pageIdStr} ORDER BY id ASC
|
SELECT * FROM catalog_items
|
||||||
|
WHERE CAST(page_id AS CHAR) = ${pageIdStr}
|
||||||
|
ORDER BY order_number ASC, id ASC
|
||||||
`;
|
`;
|
||||||
const items: RawItem[] = rawItems.map((r: Record<string, unknown>) => ({
|
const items: RawItem[] = rawItems.map((r: Record<string, unknown>) => ({
|
||||||
id: Number(r.id),
|
id: Number(r.id),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Prisma } from "@/generated/prisma/client";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export type { NestedTreeNode, TreeNode } from "@/types/catalog";
|
export type { NestedTreeNode, TreeNode } from "@/types/catalog";
|
||||||
@@ -9,27 +10,43 @@ function toInt(value: unknown, fallback = 0): number {
|
|||||||
return Number.isFinite(n) ? n : fallback;
|
return Number.isFinite(n) ? n : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function safeItemCounts(
|
/**
|
||||||
|
* Count catalog_items per page via raw SQL.
|
||||||
|
* Real Habbo DBs often store page_id as VARCHAR; Prisma Int groupBy fails or returns 0.
|
||||||
|
*/
|
||||||
|
export async function getCatalogItemCounts(
|
||||||
pageIds?: number[],
|
pageIds?: number[],
|
||||||
): Promise<Map<number, number>> {
|
): Promise<Map<number, number>> {
|
||||||
const map = new Map<number, number>();
|
const map = new Map<number, number>();
|
||||||
try {
|
try {
|
||||||
if (pageIds && pageIds.length === 0) return map;
|
if (pageIds && pageIds.length === 0) return map;
|
||||||
const itemCounts = pageIds
|
|
||||||
? await prisma.catalogItems.groupBy({
|
if (pageIds) {
|
||||||
by: ["pageId"],
|
const idStrs = pageIds.map(String);
|
||||||
where: { pageId: { in: pageIds } },
|
const rows = await prisma.$queryRaw<
|
||||||
_count: true,
|
Array<{ page_id: string | number; cnt: number | bigint }>
|
||||||
})
|
>`
|
||||||
: await prisma.catalogItems.groupBy({
|
SELECT page_id, COUNT(*) as cnt
|
||||||
by: ["pageId"],
|
FROM catalog_items
|
||||||
_count: true,
|
WHERE CAST(page_id AS CHAR) IN (${Prisma.join(idStrs)})
|
||||||
});
|
GROUP BY page_id
|
||||||
for (const c of itemCounts) {
|
`;
|
||||||
map.set(toInt(c.pageId), c._count);
|
for (const r of rows) {
|
||||||
|
map.set(toInt(r.page_id), toInt(r.cnt));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await prisma.$queryRaw<
|
||||||
|
Array<{ page_id: string | number; cnt: number | bigint }>
|
||||||
|
>`
|
||||||
|
SELECT page_id, COUNT(*) as cnt FROM catalog_items GROUP BY page_id
|
||||||
|
`;
|
||||||
|
for (const r of rows) {
|
||||||
|
map.set(toInt(r.page_id), toInt(r.cnt));
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Some DBs store page_id as VARCHAR — ignore counts rather than failing the tree.
|
// Best-effort — never break the tree for count failures.
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
@@ -41,7 +58,7 @@ export async function getTreeFlat(): Promise<TreeNode[]> {
|
|||||||
const allPages = await prisma.catalogPages.findMany({
|
const allPages = await prisma.catalogPages.findMany({
|
||||||
orderBy: { orderNum: "asc" },
|
orderBy: { orderNum: "asc" },
|
||||||
});
|
});
|
||||||
const itemCountMap = await safeItemCounts();
|
const itemCountMap = await getCatalogItemCounts();
|
||||||
|
|
||||||
const childCountMap = new Map<number, number>();
|
const childCountMap = new Map<number, number>();
|
||||||
for (const p of allPages) {
|
for (const p of allPages) {
|
||||||
@@ -261,5 +278,4 @@ export async function getAncestors(
|
|||||||
return ancestors;
|
return ancestors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Safe item-count helper exported for the tree API route. */
|
export { toInt };
|
||||||
export { safeItemCounts, toInt };
|
|
||||||
Reference in new issue
Block a user