fix(admin): harden clothing/pets/effects/clone imports
Align grids on data.items, only treat SSE done as success, hard-fail clothing sets when libs fail, and add Cancel via AbortController. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
9c4949186c
commit
16191cef14
8 files changed
+344
-173
No files matched your search
@@ -87,4 +87,20 @@ describe("importClothingSet", () => {
|
||||
const r = await importClothingSet({ setType: "ch", setId: 999999 });
|
||||
expect(r.ok).toBe(false);
|
||||
});
|
||||
|
||||
it("does not write set data when a required library import fails", async () => {
|
||||
mergeSet.mockClear();
|
||||
mergePalette.mockClear();
|
||||
importSingleFigure.mockResolvedValueOnce({
|
||||
ok: false,
|
||||
lib: "shirt_M_x",
|
||||
warnings: [],
|
||||
error: "nitro missing",
|
||||
});
|
||||
const r = await importClothingSet({ setType: "ch", setId: 5000 });
|
||||
expect(r.ok).toBe(false);
|
||||
expect(r.error).toMatch(/required figure libraries failed/i);
|
||||
expect(mergeSet).not.toHaveBeenCalled();
|
||||
expect(mergePalette).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -133,6 +133,8 @@ export async function importClothingSet(params: {
|
||||
|
||||
await ensureFigureDirs();
|
||||
|
||||
let hardFail = false;
|
||||
|
||||
for (const libId of requiredLibIds) {
|
||||
const officialLib = officialLibs.find((l) => l.id === libId);
|
||||
if (!officialLib) continue;
|
||||
@@ -141,18 +143,31 @@ export async function importClothingSet(params: {
|
||||
onProgress?.(`importing library ${libId}`);
|
||||
const result = await importSingleFigure({ lib: libId, onProgress });
|
||||
if (!result.ok) {
|
||||
hardFail = true;
|
||||
warnings.push(
|
||||
`Library import failed for ${libId}: ${result.error ?? "unknown error"}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (result.warnings.length) {
|
||||
warnings.push(...result.warnings);
|
||||
}
|
||||
localLibIds.add(libId);
|
||||
}
|
||||
|
||||
await mergeLibrary(officialLib);
|
||||
}
|
||||
|
||||
if (hardFail) {
|
||||
return {
|
||||
ok: false,
|
||||
setId,
|
||||
warnings,
|
||||
error:
|
||||
"One or more required figure libraries failed to import — set was not written",
|
||||
};
|
||||
}
|
||||
|
||||
const mappedSet = mapOfficialSet(set);
|
||||
await mergeSet(setType, st.paletteId, mappedSet as never);
|
||||
|
||||
|
||||
+19
-5
@@ -50,6 +50,7 @@ export async function runSseImport(
|
||||
onDone: (classname: string) => void,
|
||||
onComplete: (succeeded: number, failed: number) => void,
|
||||
signal?: AbortSignal,
|
||||
onFailed?: (classname: string, error?: string) => void,
|
||||
): Promise<void> {
|
||||
const res = await adminFetch(url, {
|
||||
method: "POST",
|
||||
@@ -57,8 +58,19 @@ export async function runSseImport(
|
||||
body: JSON.stringify(body),
|
||||
signal,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const data = await res.json().catch(() => ({}));
|
||||
toast.error(
|
||||
typeof data.error === "string"
|
||||
? data.error
|
||||
: `Import failed (${res.status})`,
|
||||
);
|
||||
onComplete(0, 0);
|
||||
return;
|
||||
}
|
||||
if (!res.body) {
|
||||
toast.error("No response stream");
|
||||
onComplete(0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,11 +80,13 @@ export async function runSseImport(
|
||||
await readSseStream(
|
||||
res.body,
|
||||
(evt) => {
|
||||
if (
|
||||
evt.type === "item_progress" &&
|
||||
(evt.status === "done" || evt.status === "failed")
|
||||
) {
|
||||
onDone(String(evt.classname ?? ""));
|
||||
if (evt.type === "item_progress") {
|
||||
const classname = String(evt.classname ?? "");
|
||||
if (evt.status === "done") {
|
||||
onDone(classname);
|
||||
} else if (evt.status === "failed") {
|
||||
onFailed?.(classname, String(evt.error ?? ""));
|
||||
}
|
||||
}
|
||||
if (evt.type === "batch_complete") {
|
||||
succeeded = Number(evt.succeeded ?? 0);
|
||||
|
||||
Reference in new issue
Block a user