diff --git a/src/app/api/admin/import/furni/route.ts b/src/app/api/admin/import/furni/route.ts index 746c08e5..53c2cc32 100644 --- a/src/app/api/admin/import/furni/route.ts +++ b/src/app/api/admin/import/furni/route.ts @@ -19,6 +19,7 @@ import { ensureDirectories, IMPORTED_PAGE_CAPTION_SAVE, importSingleFurni, + reconcileImportedOfferIds, resetCatalogPageCache, } from "@/lib/services/furni-import"; import { getOfficialHabboFurnidata } from "@/lib/services/habbo-furnidata-cache"; @@ -403,10 +404,19 @@ export const POST = withAdmin( ); } + // Reconcile offer_id with the furnidata sprite id across the tree + const reconciled = await reconcileImportedOfferIds(); + const succeeded = results.filter((r) => r.ok).length; const failed = results.filter((r) => !r.ok).length; - return apiOk({ batch: true, succeeded, failed, results }); + return apiOk({ + batch: true, + succeeded, + failed, + results, + offerIdsFixed: reconciled.fixed, + }); } // ── Single import ──────────────────────────────────────────────── @@ -455,6 +465,9 @@ export const POST = withAdmin( result.warnings.push("RCON cache refresh failed"); } + // Reconcile offer_id with the furnidata sprite id across the tree + const reconciled = await reconcileImportedOfferIds(); + logAudit({ userId: ctx.session.user.id, action: "furni_import", @@ -475,9 +488,9 @@ export const POST = withAdmin( return apiOk({ id: result.itemId, catalogItemId: result.catalogItemId, - dimensions: result.dimensions, + offerIdsFixed: reconciled.fixed, spriteCount: result.spriteCount, - warnings: result.warnings.length > 0 ? result.warnings : undefined, + warnings: result.warnings, }); }, ); diff --git a/src/lib/services/furni-import.ts b/src/lib/services/furni-import.ts index 400db6b0..ea1fbc2a 100644 --- a/src/lib/services/furni-import.ts +++ b/src/lib/services/furni-import.ts @@ -284,6 +284,25 @@ export function resetCatalogPageCache(): void { categoryCacheTimestamp = 0; } +/** + * Walk every catalog item under the "Imported Furniture" tree and make its + * offer_id match the furnidata sprite id (item_ids). Fixes any stale values + * (e.g. legacy -1 rows) after an import. + */ +export async function reconcileImportedOfferIds(): Promise<{ + fixed: number; +}> { + const parentId = await getOrCreateImportedParentPage(); + const [result] = (await db.execute(sql` + UPDATE catalog_items ci + JOIN catalog_pages cp ON cp.id = ci.page_id + SET ci.offer_id = CAST(ci.item_ids AS UNSIGNED) + WHERE cp.parent_id = ${parentId} + AND ci.offer_id <> CAST(ci.item_ids AS UNSIGNED) + `)) as unknown as [Record, unknown]; + return { fixed: Number(result.affectedRows ?? 0) }; +} + /** * Allocate the next `catalog_items.id` and run the INSERT through a * process-level async mutex so concurrent imports in the same Node process