Add clone source selection to Studio furni browser
Allow the admin Studio to browse and import furniture from custom retro hotel sources, not just official Habbo furnidata. - GET /api/admin/import/furni?source=<id> lists a clone source's furnidata (via getCloneList) and returns a per-item iconUrl from the source's iconBaseUrl so previews render correctly - Studio client gets a source selector dropdown (official Habbo plus all configured clone_sources) that resets the selection and reloads the list when switched - Single and batch imports now send sourceId so SWF/nitro/icon assets are fetched from the selected hotel's CDN - Preview images prefer the source iconUrl with the existing fallback chain; header shows the active source name
This commit is contained in:
1 parent
5b09179404
commit
54f2bc5e0c
2 files changed
+127
-24
No files matched your search
@@ -6,6 +6,7 @@ import { apiError, apiOk } from "@/lib/api-response";
|
||||
import { CatalogPages, db, ItemsBase } from "@/lib/db";
|
||||
import { PERMS } from "@/lib/permissions";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { getCloneList } from "@/lib/services/clone-import";
|
||||
import { getSource } from "@/lib/services/clone-sources";
|
||||
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
||||
import {
|
||||
@@ -213,6 +214,64 @@ export const GET = withAdmin(
|
||||
const search = request.nextUrl.searchParams.get("search") || "";
|
||||
const page = parseInt(request.nextUrl.searchParams.get("page") || "1", 10);
|
||||
|
||||
// ── Browse a custom clone source (retro hotel furnidata) ────────
|
||||
const sourceId = request.nextUrl.searchParams.get("source");
|
||||
if (sourceId) {
|
||||
const source = await getSource(sourceId);
|
||||
if (!source) return apiError("Source not found", 404);
|
||||
|
||||
const { items, meta } = await getCloneList({
|
||||
source,
|
||||
search,
|
||||
page,
|
||||
perPage: 50,
|
||||
filter: "all",
|
||||
});
|
||||
|
||||
const sourceIconBase = source.iconBaseUrl?.trim().replace(/\/+$/, "");
|
||||
const enriched = items.map((item) => {
|
||||
const imported = item.present;
|
||||
let nitroExists = false;
|
||||
if (imported) {
|
||||
const starIdx = item.classname.indexOf("*");
|
||||
const base =
|
||||
starIdx !== -1
|
||||
? item.classname.substring(0, starIdx)
|
||||
: item.classname;
|
||||
const p = path.join(
|
||||
/*turbopackIgnore: true*/ nitroDir,
|
||||
`${base}.nitro`,
|
||||
);
|
||||
nitroExists = existsSync(/*turbopackIgnore: true*/ p);
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
classname: item.classname,
|
||||
type: item.itemType === "i" ? "wallitem" : "flooritem",
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
revision: 0,
|
||||
category: String(item.category ?? ""),
|
||||
alreadyImported: imported,
|
||||
nitroExists,
|
||||
iconUrl: sourceIconBase
|
||||
? `${sourceIconBase}/${encodeURIComponent(item.classname)}_icon.png`
|
||||
: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return apiOk({
|
||||
items: enriched,
|
||||
source: { id: source.id, name: source.name },
|
||||
meta: {
|
||||
currentPage: meta.page,
|
||||
lastPage: Math.max(Math.ceil(meta.total / meta.perPage), 1),
|
||||
total: meta.total,
|
||||
perPage: meta.perPage,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Pull every imported classname once so we can both (a) sort non-imported
|
||||
// items to the top *before* pagination and (b) skip the per-page lookup
|
||||
// below. Single-column indexed query — fast even at 10k+ rows.
|
||||
|
||||
Reference in new issue
Block a user