diff --git a/src/components/admin/studio/studio-client.tsx b/src/components/admin/studio/studio-client.tsx index dd5513aa80..4e77a1af36 100644 --- a/src/components/admin/studio/studio-client.tsx +++ b/src/components/admin/studio/studio-client.tsx @@ -881,14 +881,14 @@ export function StudioClient({ className="gap-1.5" disabled={organizing || !!batchProgress} onClick={organizeAll} - title="Alle meubels automatisch in catalogus-pagina's ordenen (pagina's worden aangemaakt indien nodig)" + title="Alle meubels uit de Import-sectie automatisch ordenen in catalogus-pagina's (pagina's worden aangemaakt indien nodig)" > {organizing ? ( ) : ( )} - Organiseer alles + Organiseer import {selectedNonImported > 0 && ( diff --git a/src/lib/services/catalog-repair.ts b/src/lib/services/catalog-repair.ts index 6e4fc372ea..c3c52d1d18 100644 --- a/src/lib/services/catalog-repair.ts +++ b/src/lib/services/catalog-repair.ts @@ -1,14 +1,11 @@ -import { readdir } from "node:fs/promises"; import { sql } from "drizzle-orm"; import { db } from "@/lib/db"; -import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs"; import { readFurniData, withFurniDataLock, writeFurniData, } from "@/lib/services/furni-data"; import { - allocateCatalogItemId, autoPriceFurni, CATEGORY_PAGE, classifyFurni, @@ -17,7 +14,6 @@ import { resetCatalogPageCache, } from "@/lib/services/furni-import"; import { - assetNameCandidates, classifyCatalogItem, NO_CATALOG_KINDS, } from "@/lib/services/item-kind"; @@ -466,18 +462,17 @@ export interface OrganizeCatalogResult { } /** - * Fully automatic one-shot organization of the entire `items_base` table into - * the catalog. For every furniture item it: - * 1. ensures the "Imported Furniture" parent page and the correct target - * sub-page exist (creating it if missing). The target is the item's - * furniture line (e.g. "weebz", "habbox") when known, otherwise its - * auto-detected category (e.g. "Room & Building", "Rares"), - * 2. creates a catalog_items entry if the item isn't in the catalog yet - * (placed on its line/category sub-page with the auto price), - * 3. re-homes and re-prices any existing catalog entry so it sits on its + * Fully automatic one-shot organization of everything that already lives under + * the "Imported Furniture" section of the catalog. For each imported item it: + * 1. ensures the correct target sub-page exists (creating it if missing). + * The target is the item's furniture line (e.g. "weebz", "habbox") when + * known and large enough, otherwise its auto-detected category + * (e.g. "Room & Building", "Rares"), + * 2. re-homes and re-prices any existing catalog entry so it sits on its * correct sub-page with the canonical auto-detected price. * - * No manual selection required — runs over the entire database. + * Only items already present in the Imported section are touched — no new + * items are pulled in from `items_base`. No manual selection required. */ export async function organizeCatalogAll(): Promise { // 1) Parent page (sub-pages are created on demand below). @@ -577,7 +572,6 @@ export async function organizeCatalogAll(): Promise { unknown, ]; - const idsInCatalog = new Set(); const catalogPageByItem = new Map(); const catalogCreditsByItem = new Map(); for (const row of catalogRows) { @@ -586,7 +580,6 @@ export async function organizeCatalogAll(): Promise { .map((s) => Number(s.trim())) .filter(Boolean); for (const id of ids) { - idsInCatalog.add(id); if (!catalogPageByItem.has(id)) { catalogPageByItem.set(id, Number(row.page_id)); catalogCreditsByItem.set(id, Number(row.cost_credits)); @@ -598,11 +591,33 @@ export async function organizeCatalogAll(): Promise { `)) as unknown as [Array<{ id: number }>, unknown]; const validPageIds = new Set(validPages.map((r) => Number(r.id))); - // Count items per furniture line so we only spin up a dedicated line page - // for lines that actually have enough items. Everything else falls back to - // the category page. + // Only items already living under the Imported Furniture section are + // reorganized. An item counts as "in import" when its catalog entry sits on + // a sub-page (or the parent) of the imported parent page. + const [importedPages] = (await db.execute(sql` + SELECT id FROM catalog_pages + WHERE parent_id = ${parentPageId} OR id = ${parentPageId} + `)) as unknown as [Array<{ id: number }>, unknown]; + const importedPageIds = new Set(importedPages.map((r) => Number(r.id))); + const importedItemIds = new Set(); + for (const row of catalogRows) { + if (!importedPageIds.has(Number(row.page_id))) continue; + for (const id of (row.item_ids ?? "") + .split(";") + .map((s) => Number(s.trim())) + .filter(Boolean)) { + importedItemIds.add(id); + } + } + const itemById = new Map(items.map((it) => [Number(it.id), it])); + + // Count imported items per furniture line so we only spin up a dedicated + // line page for lines that actually have enough items. Everything else + // falls back to the category page. const lineCounts = new Map(); - for (const item of items) { + for (const id of importedItemIds) { + const item = itemById.get(id); + if (!item) continue; if (NO_CATALOG_KINDS.has(classifyCatalogItem(item))) continue; const fl = resolveFurniline(item.item_name); if (fl) lineCounts.set(fl, (lineCounts.get(fl) ?? 0) + 1); @@ -612,12 +627,7 @@ export async function organizeCatalogAll(): Promise { if (count >= LINE_PAGE_MIN_ITEMS) qualifyingLines.add(line); } - // Nitro presence (pets are purchasable without a furni .nitro bundle). - const { nitroDir } = await getFurniAssetDirs(); - const nitroFiles = await readdir(nitroDir).catch(() => [] as string[]); - const nitroSet = new Set(nitroFiles); - - let catalogAdded = 0; + const catalogAdded = 0; let catalogRehomed = 0; for (const item of items) { @@ -625,17 +635,10 @@ export async function organizeCatalogAll(): Promise { if (NO_CATALOG_KINDS.has(kind)) continue; const itemId = Number(item.id); - const inCatalog = idsInCatalog.has(itemId); - // Items not yet in the catalog are only added when they are actually - // purchasable furniture (have a .nitro or are a pet). - if (!inCatalog) { - const hasNitro = assetNameCandidates(item.item_name).nitro.some((n) => - nitroSet.has(n), - ); - const purchasable = kind === "pet" || hasNitro; - if (!purchasable) continue; - } + // Only reorganize items already in the Imported Furniture section. + // Never pull new items in from items_base. + if (!importedItemIds.has(itemId)) continue; // Target page: the item's furniture line (e.g. "weebz", "habbox") when // it qualifies for its own page, otherwise its auto-detected category. @@ -662,17 +665,6 @@ export async function organizeCatalogAll(): Promise { const pageId = await ensureSubPage(captionSave, caption); const price = autoPriceFurni(item.item_name); - if (!inCatalog) { - await allocateCatalogItemId(async (nextId) => { - await db.execute(sql` - INSERT INTO catalog_items (id, page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, order_number, offer_id, extradata) - VALUES (${nextId}, ${pageId}, ${itemId}, ${item.item_name}, ${price.credits}, ${price.points}, ${price.pointsType}, 1, 1, ${itemId}, '') - `); - }); - catalogAdded++; - continue; - } - // Re-classify onto the correct sub-page and re-apply the auto price if // either drifted away from the canonical values. const currentPage = catalogPageByItem.get(itemId);