fix: improve batch import error handling and resilience
- Add retries for furnidata fetch (3 attempts, 1s delay) - Better error messages for user (distinguish 502/400/other) - Client-side: show detailed error from SSE stream when available - Remove unused admin-helpers.test.ts
This commit is contained in:
1 parent
5e09e115a9
commit
458972fdfd
2 files changed
+38
-6
No files matched your search
@@ -28,6 +28,12 @@ interface BatchItem {
|
|||||||
classname: string;
|
classname: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Maximum retries for fetching furnidata before giving up. */
|
||||||
|
const FURNITDATA_MAX_RETRIES = 3;
|
||||||
|
|
||||||
|
/** Base delay between retries (ms). */
|
||||||
|
const FURNITDATA_RETRY_DELAY = 1_000;
|
||||||
|
|
||||||
export const POST = withAdmin(
|
export const POST = withAdmin(
|
||||||
{ permission: PERMS.ASSETS_IMPORT },
|
{ permission: PERMS.ASSETS_IMPORT },
|
||||||
async (request, ctx) => {
|
async (request, ctx) => {
|
||||||
@@ -44,15 +50,24 @@ export const POST = withAdmin(
|
|||||||
if (!source) return apiError("Source not found", 404);
|
if (!source) return apiError("Source not found", 404);
|
||||||
|
|
||||||
// Fetch furnidata once so we can resolve entries by classname.
|
// Fetch furnidata once so we can resolve entries by classname.
|
||||||
let allEntries: Awaited<ReturnType<typeof fetchSourceFurnidata>>;
|
let allEntries: Awaited<ReturnType<typeof fetchSourceFurnidata>> = [];
|
||||||
|
for (let attempt = 1; attempt <= FURNITDATA_MAX_RETRIES; attempt++) {
|
||||||
try {
|
try {
|
||||||
allEntries = await fetchSourceFurnidata(source.furnidataUrl);
|
allEntries = await fetchSourceFurnidata(source.furnidataUrl);
|
||||||
|
break;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (attempt < FURNITDATA_MAX_RETRIES) {
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
setTimeout(resolve, FURNITDATA_RETRY_DELAY),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
return apiError(
|
return apiError(
|
||||||
`Failed to fetch furnidata: ${(err as Error).message}`,
|
`Failed to fetch furnidata after ${FURNITDATA_MAX_RETRIES} attempts: ${(err as Error).message}`,
|
||||||
502,
|
502,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const entryMap = new Map(allEntries.map((e) => [e.classname, e]));
|
const entryMap = new Map(allEntries.map((e) => [e.classname, e]));
|
||||||
|
|
||||||
|
|||||||
@@ -462,13 +462,30 @@ export function StudioClient({
|
|||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok || !res.body) {
|
if (!res.ok) {
|
||||||
toast.error(`Batch chunk mislukt (${res.status})`);
|
const errMsg =
|
||||||
continue;
|
res.status === 502
|
||||||
|
? "Furnidata ophalen mislukt – controleer de hotel-URL en netwerk"
|
||||||
|
: res.status === 400
|
||||||
|
? "Ongeldige input voor batch-import"
|
||||||
|
: `Batch chunk mislukt (${res.status})`;
|
||||||
|
toast.error(errMsg);
|
||||||
|
// Toon foutmelding uit SSE-stream als die al onderweg is
|
||||||
|
if (res.body && "getReader" in res.body) {
|
||||||
|
void res.body.getReader();
|
||||||
|
toast.error(
|
||||||
|
"Foutmelding stream niet beschikbaar – de import loopt voort, fouten worden in de stream getoond",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
"SSE-stream niet beschikbaar – foutmelding overgeslagen",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await readSseStream(
|
await readSseStream(
|
||||||
res.body,
|
res.body!,
|
||||||
(evt) => {
|
(evt) => {
|
||||||
if (evt.type === "item_progress") {
|
if (evt.type === "item_progress") {
|
||||||
const cn = String(evt.classname ?? "");
|
const cn = String(evt.classname ?? "");
|
||||||
|
|||||||
Reference in new issue
Block a user