From bb7ec3db0209f23a86fc24aea8cb33250c0ae238 Mon Sep 17 00:00:00 2001 From: openhands Date: Sat, 22 Aug 2026 11:27:22 +0200 Subject: [PATCH] feat: add automatic catalog organization for all imported furniture Add a fully automatic "Organize all" action in the Studio that organizes the entire database into catalog pages: - Create a dedicated sub-page per furniture line (e.g. weebz, habbox) from FurnitureData.json, falling back to the auto-detected category. - Add missing purchasable items to the catalog on their line/category page. - Re-home and re-price existing catalog entries onto their correct page. - Pages are created automatically when missing; no manual selection needed. Includes the organizeCatalogAll service, a POST /api/admin/import/furni/organize endpoint, and the Studio button. --- .../api/admin/import/furni/organize/route.ts | 42 ++++ src/components/admin/studio/studio-client.tsx | 47 ++++ src/lib/services/catalog-repair.ts | 232 ++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 src/app/api/admin/import/furni/organize/route.ts diff --git a/src/app/api/admin/import/furni/organize/route.ts b/src/app/api/admin/import/furni/organize/route.ts new file mode 100644 index 0000000000..0e2889515d --- /dev/null +++ b/src/app/api/admin/import/furni/organize/route.ts @@ -0,0 +1,42 @@ +import { withAdmin } from "@/lib/api-handler"; +import { apiError, apiOk } from "@/lib/api-response"; +import { PERMS } from "@/lib/permissions"; +import { logAudit } from "@/lib/services/audit"; +import { organizeCatalogAll } from "@/lib/services/catalog-repair"; +import { rcon } from "@/lib/services/rcon"; + +// ── Fully automatic catalog organization of the whole database ────── +export const POST = withAdmin( + { permission: PERMS.ASSETS_IMPORT }, + async (_request, ctx) => { + try { + const result = await organizeCatalogAll(); + + // Refresh game server caches so the reorganized catalog shows up. + try { + await rcon.updateCatalog(); + } catch (err) { + console.warn( + "[organize-furni] RCON catalog refresh failed:", + (err as Error).message, + ); + } + + logAudit({ + userId: ctx.session.user.id, + action: "furni_organize", + target: "CatalogItems", + targetId: 0, + after: { + pagesCreated: result.pagesCreated, + catalogAdded: result.catalogAdded, + catalogRehomed: result.catalogRehomed, + }, + }); + + return apiOk({ ...result }); + } catch (err) { + return apiError((err as Error).message, 500); + } + }, +); diff --git a/src/components/admin/studio/studio-client.tsx b/src/components/admin/studio/studio-client.tsx index 7c2daaa666..dd5513aa80 100644 --- a/src/components/admin/studio/studio-client.tsx +++ b/src/components/admin/studio/studio-client.tsx @@ -514,6 +514,37 @@ export function StudioClient({ } }; + const [organizing, setOrganizing] = useState(false); + + async function organizeAll() { + if (organizing) return; + setOrganizing(true); + try { + const res = await adminFetch("/api/admin/import/furni/organize", { + method: "POST", + }); + const data = await res.json(); + if (res.ok) { + const parts = [ + `Catalogus georganiseerd`, + data.pagesCreated > 0 + ? `${data.pagesCreated} pagina's aangemaakt` + : null, + data.catalogAdded > 0 ? `${data.catalogAdded} nieuw` : null, + data.catalogRehomed > 0 ? `${data.catalogRehomed} bijgewerkt` : null, + ].filter(Boolean); + toast.success(parts.join(" · ")); + fetchStats(); + } else { + toast.error(data.error || "Organiseren mislukt"); + } + } catch { + toast.error("Netwerkfout"); + } finally { + setOrganizing(false); + } + } + async function deleteItem() { if (!deleting) return; try { @@ -844,6 +875,22 @@ export function StudioClient({
+ + {selectedNonImported > 0 && (