import { promises as fs } from "node:fs"; import path from "node:path"; import { sql } from "drizzle-orm"; import { withAdmin } from "@/lib/api-handler"; import { apiError, apiOk } from "@/lib/api-response"; import { db } from "@/lib/db"; import { type FurnitureInspection, validateClassnames, } from "@/lib/furni/studio-inspection"; import { PERMS } from "@/lib/permission-slugs"; import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs"; import { readFurniData } from "@/lib/services/furni-data"; async function asset(directory: string, name: string) { try { const stat = await fs.stat(path.join(directory, name)); return { exists: stat.isFile(), bytes: stat.size }; } catch (error) { return { exists: (error as NodeJS.ErrnoException).code === "ENOENT" ? false : null, bytes: 0, }; } } // Read-only inspection lives outside import routes so it cannot enqueue a Git export. export const POST = withAdmin( { permission: PERMS.ASSETS_IMPORT }, async (request) => { const body = await request.json().catch(() => null); const names = validateClassnames(body?.classnames); if (!names) return apiError("Provide 1–500 valid furniture classnames", 400); const namesSql = sql.join( names.map((name) => sql`${name}`), sql`, `, ); const [rows] = (await db.execute( sql`SELECT id, sprite_id AS spriteId, item_name AS classname, public_name AS name, type FROM items_base WHERE item_name IN (${namesSql})`, )) as unknown as [ Array, unknown, ]; const [offers] = (await db.execute( sql`SELECT ib.item_name AS classname, ci.id, ci.page_id AS pageId, ci.cost_credits AS credits, ci.cost_points AS points FROM items_base ib JOIN catalog_items ci ON FIND_IN_SET(ib.id, REPLACE(ci.item_ids, ';', ',')) > 0 WHERE ib.item_name IN (${namesSql})`, )) as unknown as [ Array, unknown, ]; let readable = true; let data: Record = {}; try { data = await readFurniData(); } catch { readable = false; } const entries = new Map(); for (const [section, type] of [ ["roomitemtypes", "flooritem"], ["wallitemtypes", "wallitem"], ] as const) { const list = ( data[section] as | { furnitype?: Array> } | undefined )?.furnitype; if (!Array.isArray(list)) { readable = false; continue; } for (const entry of list) { if (!entry || typeof entry !== "object") { readable = false; continue; } const name = String(entry.classname ?? ""); if (!names.includes(name)) continue; const values = entries.get(name) ?? []; values.push({ id: Number(entry.id), name: String(entry.name ?? ""), description: String(entry.description ?? ""), revision: Number(entry.revision ?? 0), type, }); entries.set(name, values); } } const dirs = await getFurniAssetDirs(); const items: FurnitureInspection[] = []; for (let offset = 0; offset < names.length; offset += 20) { items.push( ...(await Promise.all( names.slice(offset, offset + 20).map(async (classname) => { const base = classname.split("*")[0]; const [nitro, icon] = await Promise.all([ asset(dirs.nitroDir, `${base}.nitro`), asset(dirs.iconDir, `${classname.replace(/\*/g, "_")}_icon.png`), ]); return { classname, sql: rows.filter((row) => row.classname === classname), catalog: offers.filter((row) => row.classname === classname), furnidata: entries.get(classname) ?? [], furnidataReadable: readable, nitro, icon, }; }), )), ); } return apiOk({ items }); }, );