Fix catalog items missing due to page_id VARCHAR mismatch.
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:
SimoandCursor committed 2026-07-17 22:21:49 +02:00
1 parent 33a8a19820
commit 7bc0845932
6 files changed
+139 -79

No files matched your search

+80 -28
View File
@@ -1,9 +1,11 @@
"use server";
import { revalidatePath } from "next/cache";
import { Prisma } from "@/generated/prisma/client";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { allocateCatalogItemId } from "@/lib/services/furni-import";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -63,6 +65,44 @@ function pickAllowed(
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: {
pageId: number;
itemIds: string;
@@ -92,19 +132,17 @@ export async function createCatalogItem(data: {
catalogName = base?.publicName || base?.itemName || String(firstId);
}
}
const created = await prisma.catalogItems.create({
data: { ...data, catalogName },
});
const id = await insertCatalogItemRow({ ...data, catalogName });
await rcon.updateCatalog();
await logStaffActivity({
staffId: staff.id,
action: "catalog_item_create",
description: `Created catalog item #${created.id}`,
description: `Created catalog item #${id}`,
targetType: "catalog_item",
targetId: created.id,
targetId: id,
});
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. */
@@ -145,24 +183,22 @@ export async function bulkCreateCatalogItems({
continue;
}
try {
await prisma.catalogItems.create({
data: {
pageId,
itemIds: String(row.baseId),
catalogName: base.publicName || base.itemName || String(row.baseId),
costCredits: row.credits ?? 0,
costPoints: row.points ?? 0,
pointsType: row.pointsType ?? 0,
amount: 1,
limitedSells: 0,
limitedStack: 0,
orderNumber: 1,
offerId: -1,
songId: 0,
haveOffer: "1",
clubOnly: "0",
extradata: "",
},
await insertCatalogItemRow({
pageId,
itemIds: String(row.baseId),
catalogName: base.publicName || base.itemName || String(row.baseId),
costCredits: row.credits ?? 0,
costPoints: row.points ?? 0,
pointsType: row.pointsType ?? 0,
amount: 1,
limitedSells: 0,
limitedStack: 0,
orderNumber: 1,
offerId: -1,
songId: 0,
haveOffer: "1",
clubOnly: "0",
extradata: "",
});
created++;
} catch {
@@ -207,10 +243,15 @@ export async function moveCatalogItems({
targetPageId: number;
}) {
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItems.updateMany({
where: { id: { in: ids } },
data: { pageId: targetPageId },
});
if (ids.length === 0) {
return { ok: true as const, data: {} };
}
const pageIdStr = String(targetPageId);
await prisma.$executeRaw`
UPDATE catalog_items
SET page_id = ${pageIdStr}
WHERE id IN (${Prisma.join(ids)})
`;
await rcon.updateCatalog();
revalidatePath("/admin/catalog");
return { ok: true as const, data: {} };
@@ -244,6 +285,17 @@ export async function updateCatalogItem({
if (Object.keys(safeCatalog).length === 0 && !baseItem) {
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) {
await prisma.catalogItems.update({
where: { id },