From 7ef5038a9e5160b6651340050d1e9a5205240200 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 14 Aug 2026 18:35:00 +0200 Subject: [PATCH] Add updateExisting option to furniture import - Added updateExisting parameter to importSingleFurni and API routes - When updateExisting=true, existing items are updated (catalog price/page) instead of failing - Added catalogUpdated flag to ImportSingleResult - Updates existing catalog entries (price, page) instead of skipping duplicates --- src/app/api/admin/import/furni/route.ts | 3 ++ src/lib/services/furni-import.ts | 59 ++++++++++++++++++------- src/types/furni.ts | 1 + 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/app/api/admin/import/furni/route.ts b/src/app/api/admin/import/furni/route.ts index 90b7e190..1f501a01 100644 --- a/src/app/api/admin/import/furni/route.ts +++ b/src/app/api/admin/import/furni/route.ts @@ -297,6 +297,7 @@ export const POST = withAdmin( type: item.type ?? "flooritem", revision: item.revision ?? 0, category: item.category ?? "unknown", + updateExisting: body.updateExisting === true, sourceSwfBaseUrl: source?.sourceSwfBaseUrl, nitroBaseUrl: source?.nitroBaseUrl || undefined, iconBaseUrl: source?.iconBaseUrl || undefined, @@ -359,6 +360,7 @@ export const POST = withAdmin( revision, category, sourceId, + updateExisting, } = body; if (!classname || !name) return apiError("Missing classname or name", 400); @@ -374,6 +376,7 @@ export const POST = withAdmin( type: type ?? "flooritem", revision: revision ?? 0, category: category ?? "unknown", + updateExisting: updateExisting === true, sourceSwfBaseUrl: source?.sourceSwfBaseUrl, nitroBaseUrl: source?.nitroBaseUrl || undefined, iconBaseUrl: source?.iconBaseUrl || undefined, diff --git a/src/lib/services/furni-import.ts b/src/lib/services/furni-import.ts index 5f1e7b48..db654531 100644 --- a/src/lib/services/furni-import.ts +++ b/src/lib/services/furni-import.ts @@ -424,6 +424,7 @@ export async function importSingleFurni(params: { revision: number; category: string; skipFurniDataWrite?: boolean; + updateExisting?: boolean; onProgress?: (status: string) => void; /** Per-source SWF download base URL (e.g. "https://virtualc.nl/dcr"). */ sourceSwfBaseUrl?: string; @@ -440,6 +441,7 @@ export async function importSingleFurni(params: { revision, category, skipFurniDataWrite, + updateExisting, onProgress, sourceSwfBaseUrl, nitroBaseUrl: sourceNitroBaseUrl, @@ -454,28 +456,34 @@ export async function importSingleFurni(params: { .where(eq(ItemsBase.itemName, classname)) .limit(1); if (existsByName) { - return { - ok: false, - warnings, - error: `Item "${classname}" already exists (id: ${existsByName.id})`, - }; + if (!updateExisting) { + return { + ok: false, + warnings, + error: `Item "${classname}" already exists (id: ${existsByName.id})`, + }; + } + // Update existing item - fall through to update logic } const [existsById] = await db .select({ itemName: ItemsBase.itemName }) .from(ItemsBase) .where(eq(ItemsBase.id, originalId)) .limit(1); - if (existsById) { - return { - ok: false, - warnings, - error: `spriteId ${originalId} already used by "${existsById.itemName}"`, - }; + if (existsById && (!existsByName || existsById.itemName !== classname)) { + if (!updateExisting) { + return { + ok: false, + warnings, + error: `spriteId ${originalId} already used by "${existsById.itemName}"`, + }; + } + // Update existing item - fall through to update logic } // ── Check if spriteId already used in FurnitureData.json ── const takenBy = await findFurniDataIdConflict(originalId, classname); - if (takenBy) { + if (takenBy && !updateExisting) { return { ok: false, warnings, @@ -824,6 +832,7 @@ export async function importSingleFurni(params: { // ── Create catalog entry (with duplicate check + category sub-page) ── let catalogItemId: number | null = null; + let catalogUpdated = false; try { const itemIdStr = String(spriteId); @@ -834,9 +843,28 @@ export async function importSingleFurni(params: { if (existingCatalog.length > 0) { catalogItemId = existingCatalog[0].id; - warnings.push( - `Item already in catalog (catalog_item #${catalogItemId}, page ${existingCatalog[0].page_id}) — skipped duplicate`, - ); + if (updateExisting) { + // Update existing catalog entry: price, page, etc. + const categoryPageId = await getOrCreateCategoryPage( + classname, + itemType, + ); + const pageIdStr = String(categoryPageId); + const price = autoPriceFurni(classname); + await db.execute(sql` + UPDATE catalog_items + SET page_id = ${pageIdStr}, cost_credits = ${price.credits}, cost_points = ${price.points}, points_type = ${price.pointsType} + WHERE id = ${catalogItemId} + `); + catalogUpdated = true; + warnings.push( + `Item updated in catalog (catalog_item #${catalogItemId}, page ${categoryPageId})`, + ); + } else { + warnings.push( + `Item already in catalog (catalog_item #${catalogItemId}, page ${existingCatalog[0].page_id}) — skipped duplicate`, + ); + } } else { // Get or create the right category sub-page based on classname const categoryPageId = await getOrCreateCategoryPage(classname, itemType); @@ -861,6 +889,7 @@ export async function importSingleFurni(params: { ok: true, itemId: spriteId, catalogItemId, + catalogUpdated, dimensions: dims, spriteCount: conversionResult?.spriteCount, warnings, diff --git a/src/types/furni.ts b/src/types/furni.ts index 7e576313..6c58aa71 100644 --- a/src/types/furni.ts +++ b/src/types/furni.ts @@ -93,6 +93,7 @@ export interface ImportSingleResult { ok: boolean; itemId?: number; catalogItemId?: number | null; + catalogUpdated?: boolean; dimensions?: { x: number; y: number; z: number }; spriteCount?: number; warnings: string[];