feat: Add automatic database consistency fix to furniture imports

- Added fixDatabaseConsistencyAfterImport() function in furni-import.ts
  * Fills empty catalog_name from items_base.public_name
  * Sets have_offer = '1' where it was '0'
  * Returns counts of fixes applied

- Integrated fix into import flows:
  * src/app/api/admin/import/furni/route.ts (single & batch POST)
  * src/app/api/admin/import/furni/batch/route.ts (SSE batch POST)

- API responses now include:
  * catalogNameFixed: number of catalog names filled
  * haveOfferFixed: number of have_offer values fixed

- Ensures 100% catalog_name coverage and have_offer=1 after every import
- Typecheck, lint, and all 827 tests pass
This commit is contained in:
openhands committed 2026-08-21 21:31:39 +02:00
1 parent b5c8a29956
commit 86f3d82c5d
3 files changed
+48

No files matched your search

@@ -13,6 +13,7 @@ import { buildLocalizedFurniDataFiles } from "@/lib/services/furni-data-i18n";
import {
ensureDirectories,
ensureFurniOwnership,
fixDatabaseConsistencyAfterImport,
importSingleFurni,
reconcileImportedOfferIds,
syncAssetsToGamedataBundle,
@@ -265,6 +266,9 @@ export const POST = withAdmin(
);
}
// Fix database consistency (catalog_name + have_offer) after import
const dbFix = await fixDatabaseConsistencyAfterImport();
send({
type: "batch_complete",
succeeded,
@@ -281,6 +285,8 @@ export const POST = withAdmin(
offerRebuildFixed,
spriteFixed,
interactionFixed,
catalogNameFixed: dbFix.catalogNameFixed,
haveOfferFixed: dbFix.haveOfferFixed,
languages,
duration: Date.now() - startTime,
});
+11
View File
@@ -27,6 +27,7 @@ import {
downloadFile,
ensureDirectories,
ensureFurniOwnership,
fixDatabaseConsistencyAfterImport,
IMPORTED_PAGE_CAPTION_SAVE,
importSingleFurni,
reconcileImportedOfferIds,
@@ -480,6 +481,9 @@ export const POST = withAdmin(
// Verify + fix interaction_modes_count once for the whole batch
const verification = await verifyAndFixInteractionModesCount();
// Fix database consistency (catalog_name + have_offer) after import
const dbFix = await fixDatabaseConsistencyAfterImport();
// Rebuild all localized furnidata files (best effort)
let languages: Awaited<ReturnType<typeof buildLocalizedFurniDataFiles>> =
[];
@@ -513,6 +517,8 @@ export const POST = withAdmin(
spriteChecked: spriteVerify.checked,
spriteFixed: spriteVerify.fixed,
interactionFixed: verification.fixed,
catalogNameFixed: dbFix.catalogNameFixed,
haveOfferFixed: dbFix.haveOfferFixed,
languages,
});
}
@@ -575,6 +581,9 @@ export const POST = withAdmin(
// Verify the furnidata matches the DB ids
const furniReconcile = await reconcileFurniDataWithItemsBase();
// Fix database consistency (catalog_name + have_offer) after import
const dbFix = await fixDatabaseConsistencyAfterImport();
// Rebuild every catalog offer_id from the furnidata
const offerRebuild = await rebuildCatalogOfferIds();
@@ -632,6 +641,8 @@ export const POST = withAdmin(
spriteChecked: spriteVerify.checked,
spriteFixed: spriteVerify.fixed,
interactionFixed: verification.fixed,
catalogNameFixed: dbFix.catalogNameFixed,
haveOfferFixed: dbFix.haveOfferFixed,
languages: languagesPatch,
spriteCount: result.spriteCount,
warnings: result.warnings,
+31
View File
@@ -1086,6 +1086,37 @@ async function realNitroModes(classname: string): Promise<number | undefined> {
}
}
/** Fix database consistency after import:
* 1. Fill empty catalog_name from items_base.public_name
* 2. Set have_offer = '1' where it was '0'
* Returns { catalogNameFixed, haveOfferFixed } with counts. */
export async function fixDatabaseConsistencyAfterImport(): Promise<{
catalogNameFixed: number;
haveOfferFixed: number;
}> {
// 1. Fill empty catalog_name from items_base.public_name
const [nameResult] = (await db.execute(sql`
UPDATE catalog_items c
JOIN items_base i ON c.item_ids = i.id
SET c.catalog_name = CASE
WHEN i.public_name LIKE '%*%' THEN SUBSTRING_INDEX(i.public_name, '*', 1)
ELSE i.public_name
END
WHERE c.catalog_name IS NULL OR c.catalog_name = ''
`)) as unknown as [Record<string, unknown>, unknown];
const catalogNameFixed = Number(nameResult.affectedRows ?? 0);
// 2. Set have_offer = '1' where it was '0'
const [offerResult] = (await db.execute(sql`
UPDATE catalog_items
SET have_offer = '1'
WHERE have_offer = '0'
`)) as unknown as [Record<string, unknown>, unknown];
const haveOfferFixed = Number(offerResult.affectedRows ?? 0);
return { catalogNameFixed, haveOfferFixed };
}
/** Verify and fix interaction values for ALL furniture in items_base.
* Run after import to ensure every item's interaction_type, modes count and
* sit/lay/stand flags match the real furniture data. Only items with