style: clean up code with prettier and eslint

This commit is contained in:
openhands committed 2026-07-12 20:31:05 +02:00
1 parent 60278b9e71
commit fb8e77bb68
378 files changed
+22407 -20689

No files matched your search

+33 -33
View File
@@ -1,71 +1,71 @@
import { withAdmin } from '@/lib/api-handler'
import { apiError } from '@/lib/api-response'
import { PERMS } from '@/lib/permissions'
import { logAudit } from '@/lib/services/audit'
import { cloneSingleFurni, fetchSourceFurnidata } from '@/lib/services/clone-import'
import { getSource } from '@/lib/services/clone-sources'
import { runSseBatch } from '@/lib/services/import/core/sse-batch'
import { withAdmin } from "@/lib/api-handler";
import { apiError } from "@/lib/api-response";
import { PERMS } from "@/lib/permissions";
import { logAudit } from "@/lib/services/audit";
import { cloneSingleFurni, fetchSourceFurnidata } from "@/lib/services/clone-import";
import { getSource } from "@/lib/services/clone-sources";
import { runSseBatch } from "@/lib/services/import/core/sse-batch";
interface BatchItem {
classname: string
classname: string;
}
export const POST = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request, ctx) => {
const body = await request.json()
const sourceId: string = body.sourceId || ''
const rawClassnames: string[] = body.classnames || []
const body = await request.json();
const sourceId: string = body.sourceId || "";
const rawClassnames: string[] = body.classnames || [];
if (!sourceId) return apiError('Missing sourceId', 400)
if (rawClassnames.length === 0) return apiError('No classnames to clone', 400)
if (!sourceId) return apiError("Missing sourceId", 400);
if (rawClassnames.length === 0) return apiError("No classnames to clone", 400);
const source = await getSource(sourceId)
if (!source) return apiError('Source not found', 404)
const source = await getSource(sourceId);
if (!source) return apiError("Source not found", 404);
// Fetch furnidata once so we can resolve entries by classname.
let allEntries: Awaited<ReturnType<typeof fetchSourceFurnidata>>
let allEntries: Awaited<ReturnType<typeof fetchSourceFurnidata>>;
try {
allEntries = await fetchSourceFurnidata(source.furnidataUrl)
allEntries = await fetchSourceFurnidata(source.furnidataUrl);
} catch (err) {
return apiError(`Failed to fetch furnidata: ${(err as Error).message}`, 502)
return apiError(`Failed to fetch furnidata: ${(err as Error).message}`, 502);
}
const entryMap = new Map(allEntries.map((e) => [e.classname, e]))
const entryMap = new Map(allEntries.map((e) => [e.classname, e]));
// Deduplicate classnames preserving order.
const seen = new Set<string>()
const items: BatchItem[] = []
const seen = new Set<string>();
const items: BatchItem[] = [];
for (const cn of rawClassnames) {
if (!seen.has(cn)) {
seen.add(cn)
items.push({ classname: cn })
seen.add(cn);
items.push({ classname: cn });
}
}
if (items.length > 500) return apiError('Too many classnames (max 500)', 400)
if (items.length > 500) return apiError("Too many classnames (max 500)", 400);
return runSseBatch<BatchItem>({
items,
concurrency: body.concurrency || 2,
labelOf: (it) => it.classname,
worker: async (it, _index, report) => {
const entry = entryMap.get(it.classname)
const entry = entryMap.get(it.classname);
if (!entry) {
return { ok: false, error: 'classname not found in source furnidata' }
return { ok: false, error: "classname not found in source furnidata" };
}
const result = await cloneSingleFurni({ source, entry, onProgress: report })
const result = await cloneSingleFurni({ source, entry, onProgress: report });
if (result.ok) {
logAudit({
userId: ctx.session.user.id,
action: 'clone_furni',
target: 'FurniAsset',
action: "clone_furni",
target: "FurniAsset",
targetId: 0,
after: { classname: it.classname, sourceId, sourceName: source.name },
})
});
}
return { ok: result.ok, warnings: result.warnings, error: result.error }
return { ok: result.ok, warnings: result.warnings, error: result.error };
},
})
})
});
});
+27 -27
View File
@@ -1,52 +1,52 @@
import { withAdmin } from '@/lib/api-handler'
import { apiError } from '@/lib/api-response'
import { PERMS } from '@/lib/permissions'
import { extractFurniIconPng } from '@/lib/services/clone-icon'
import { getSource } from '@/lib/services/clone-sources'
import { withAdmin } from "@/lib/api-handler";
import { apiError } from "@/lib/api-response";
import { PERMS } from "@/lib/permissions";
import { extractFurniIconPng } from "@/lib/services/clone-icon";
import { getSource } from "@/lib/services/clone-sources";
// In-process cache of extracted icons, keyed by source+classname.
// null = known-missing (don't refetch the bundle every render).
const iconCache = new Map<string, Buffer | null>()
const iconCache = new Map<string, Buffer | null>();
// GET ?source=<id>&classname=<cn>
// Fetches the source's .nitro bundle and returns the embedded furni icon as a
// PNG. Used by the clone preview grid for sources that don't serve standalone
// {classname}_icon.png files (icons live inside the .nitro).
export const GET = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request) => {
const sp = request.nextUrl.searchParams
const sourceId = sp.get('source')
const classname = sp.get('classname')
if (!sourceId || !classname) return apiError('Missing source or classname', 400)
if (!/^[\w\-.*]+$/.test(classname)) return apiError('Invalid classname', 400)
const sp = request.nextUrl.searchParams;
const sourceId = sp.get("source");
const classname = sp.get("classname");
if (!sourceId || !classname) return apiError("Missing source or classname", 400);
if (!/^[\w\-.*]+$/.test(classname)) return apiError("Invalid classname", 400);
const cacheKey = `${sourceId}:${classname}`
let icon = iconCache.get(cacheKey)
const cacheKey = `${sourceId}:${classname}`;
let icon = iconCache.get(cacheKey);
if (icon === undefined) {
const source = await getSource(sourceId)
if (!source) return apiError('Source not found', 404)
const source = await getSource(sourceId);
if (!source) return apiError("Source not found", 404);
try {
const res = await fetch(`${source.nitroBaseUrl}/${classname}.nitro`, {
signal: AbortSignal.timeout(15000),
})
});
if (!res.ok) {
iconCache.set(cacheKey, null)
return apiError('Bundle not found', 404)
iconCache.set(cacheKey, null);
return apiError("Bundle not found", 404);
}
icon = extractFurniIconPng(Buffer.from(await res.arrayBuffer()))
iconCache.set(cacheKey, icon)
icon = extractFurniIconPng(Buffer.from(await res.arrayBuffer()));
iconCache.set(cacheKey, icon);
} catch {
iconCache.set(cacheKey, null)
return apiError('Failed to fetch bundle', 502)
iconCache.set(cacheKey, null);
return apiError("Failed to fetch bundle", 502);
}
}
if (!icon) return apiError('No icon in bundle', 404)
if (!icon) return apiError("No icon in bundle", 404);
return new Response(new Uint8Array(icon), {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400',
"Content-Type": "image/png",
"Cache-Control": "public, max-age=86400",
},
})
})
});
});
+49 -49
View File
@@ -1,94 +1,94 @@
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 { getClonableClassnames, getCloneList, getCloneStats } from '@/lib/services/clone-import'
import { deleteSource, getSource, listSources, upsertSource } from '@/lib/services/clone-sources'
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 { getClonableClassnames, getCloneList, getCloneStats } from "@/lib/services/clone-import";
import { deleteSource, getSource, listSources, upsertSource } from "@/lib/services/clone-sources";
// GET ?action=sources → list presets
// GET ?source=<id>&search=&page=&perPage= → { items, meta, stats }
export const GET = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request) => {
const sp = request.nextUrl.searchParams
const sp = request.nextUrl.searchParams;
if (sp.get('action') === 'sources') {
return apiOk({ sources: await listSources() })
if (sp.get("action") === "sources") {
return apiOk({ sources: await listSources() });
}
const sourceId = sp.get('source')
if (!sourceId) return apiError('Missing source or action', 400)
const sourceId = sp.get("source");
if (!sourceId) return apiError("Missing source or action", 400);
const source = await getSource(sourceId)
if (!source) return apiError('Source not found', 404)
const source = await getSource(sourceId);
if (!source) return apiError("Source not found", 404);
// GET ?source=<id>&action=clonable → all not-yet-present classnames (for "clone all")
if (sp.get('action') === 'clonable') {
return apiOk({ classnames: await getClonableClassnames(source) })
if (sp.get("action") === "clonable") {
return apiOk({ classnames: await getClonableClassnames(source) });
}
const search = sp.get('search') || ''
const page = Math.max(Number.parseInt(sp.get('page') || '1', 10) || 1, 1)
const perPage = Math.min(Math.max(Number.parseInt(sp.get('perPage') || '50', 10) || 50, 1), 200)
const filterParam = sp.get('filter')
const filter = filterParam === 'missing' || filterParam === 'present' ? filterParam : 'all'
const search = sp.get("search") || "";
const page = Math.max(Number.parseInt(sp.get("page") || "1", 10) || 1, 1);
const perPage = Math.min(Math.max(Number.parseInt(sp.get("perPage") || "50", 10) || 50, 1), 200);
const filterParam = sp.get("filter");
const filter = filterParam === "missing" || filterParam === "present" ? filterParam : "all";
const [listResult, stats] = await Promise.all([
getCloneList({ source, search, page, perPage, filter }),
getCloneStats(source),
])
]);
return apiOk({ items: listResult.items, meta: listResult.meta, stats })
})
return apiOk({ items: listResult.items, meta: listResult.meta, stats });
});
// POST — upsert a source preset
export const POST = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request, ctx) => {
const body = await request.json()
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, id } = body as Record<string, string>
const body = await request.json();
const { name, furnidataUrl, nitroBaseUrl, iconBaseUrl, id } = body as Record<string, string>;
const isHttps = (u: string) => {
try {
return new URL(u).protocol === 'https:'
return new URL(u).protocol === "https:";
} catch {
return false
return false;
}
}
};
if (!name?.trim()) return apiError('name is required', 400)
if (!furnidataUrl?.trim()) return apiError('furnidataUrl is required', 400)
if (!nitroBaseUrl?.trim()) return apiError('nitroBaseUrl is required', 400)
if (!iconBaseUrl?.trim()) return apiError('iconBaseUrl is required', 400)
if (!name?.trim()) return apiError("name is required", 400);
if (!furnidataUrl?.trim()) return apiError("furnidataUrl is required", 400);
if (!nitroBaseUrl?.trim()) return apiError("nitroBaseUrl is required", 400);
if (!iconBaseUrl?.trim()) return apiError("iconBaseUrl is required", 400);
if (!isHttps(furnidataUrl) || !isHttps(nitroBaseUrl) || !isHttps(iconBaseUrl))
return apiError('Source URLs must be valid https:// URLs', 400)
return apiError("Source URLs must be valid https:// URLs", 400);
const entry = await upsertSource({ id, name, furnidataUrl, nitroBaseUrl, iconBaseUrl })
const entry = await upsertSource({ id, name, furnidataUrl, nitroBaseUrl, iconBaseUrl });
logAudit({
userId: ctx.session.user.id,
action: id ? 'clone_source_update' : 'clone_source_create',
target: 'CloneSource',
action: id ? "clone_source_update" : "clone_source_create",
target: "CloneSource",
targetId: 0,
after: { id: entry.id, name: entry.name },
})
});
return apiOk({ source: entry })
})
return apiOk({ source: entry });
});
// DELETE ?id=<id>
export const DELETE = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request, ctx) => {
const id = request.nextUrl.searchParams.get('id')
if (!id) return apiError('Missing id', 400)
const id = request.nextUrl.searchParams.get("id");
if (!id) return apiError("Missing id", 400);
const source = await getSource(id)
if (!source) return apiError('Source not found', 404)
const source = await getSource(id);
if (!source) return apiError("Source not found", 404);
await deleteSource(id)
await deleteSource(id);
logAudit({
userId: ctx.session.user.id,
action: 'clone_source_delete',
target: 'CloneSource',
action: "clone_source_delete",
target: "CloneSource",
targetId: 0,
after: { id, name: source.name },
})
});
return apiOk({ id })
})
return apiOk({ id });
});