fix(studio): harden organize-imports against long-running queries and hangs
The original GET route used a correlated NOT EXISTS / FIND_IN_SET subquery over the entire catalog_items table for every recent import audit entry, causing server timeouts when the audit log or catalog grew large. The per-item host-page validation inside the create action also issued one SELECT + one UPDATE per moved offer. Changes: - GET /api/admin/import/organize: replace the correlated subquery with a bounded candidate list and a JS-side placed-set check, then resolve all needed base items in a single indexed SELECT. This bounds the query cost regardless of catalog or audit log size. - organizeImportFurni action: validate mover ids in one SELECT, then batch every move per group into a single UPDATE with a CASE expression instead of one UPDATE per item. - OrganizeImportsDialog: add a 45-second abort timeout on the fetch and a distinct load-error state so the UI never silently hangs. - Add 'loadError' translation key (en + nl).
This commit is contained in:
1 parent
01a85ebcd0
commit
19dc0347df
5 files changed
+181
-66
No files matched your search
+54
-15
@@ -451,9 +451,37 @@ export async function organizeImportFurni(input: {
|
||||
.where(inArray(ItemsBase.id, baseIds));
|
||||
const baseMap = new Map(bases.map((b) => [b.id, b]));
|
||||
|
||||
// Verify every offer we're about to move still sits inside the import
|
||||
// tree in ONE query, so we never hijack an unrelated catalog offer.
|
||||
const moverIds = [
|
||||
...new Set(
|
||||
group.items
|
||||
.map((r) => Number(r.catalogItemId))
|
||||
.filter((n) => Number.isFinite(n) && n > 0),
|
||||
),
|
||||
];
|
||||
const validMoveIds = new Set<number>();
|
||||
if (moverIds.length > 0) {
|
||||
const [hostRows] = (await db.execute(sql`
|
||||
SELECT id, page_id FROM catalog_items WHERE id IN (${sql.join(
|
||||
moverIds.map((id) => sql`${id}`),
|
||||
sql`, `,
|
||||
)})
|
||||
`)) as unknown as [Array<{ id: number; page_id: number }>, unknown];
|
||||
for (const row of hostRows) {
|
||||
if (importPageIds.has(Number(row.page_id))) {
|
||||
validMoveIds.add(Number(row.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nextOrder = 1;
|
||||
let moved = 0;
|
||||
let added = 0;
|
||||
|
||||
const moveCaseOrder: string[] = [];
|
||||
const moveCaseName: string[] = [];
|
||||
const moveIds: number[] = [];
|
||||
for (const row of group.items) {
|
||||
const base = baseMap.get(row.itemId);
|
||||
if (!base) continue;
|
||||
@@ -461,23 +489,21 @@ export async function organizeImportFurni(input: {
|
||||
base.publicName || base.itemName || String(row.itemId);
|
||||
|
||||
if (row.catalogItemId) {
|
||||
// Verify the offer currently sits inside the import tree before
|
||||
// moving it, so we never hijack an unrelated catalog offer.
|
||||
const [host] = (await db.execute(sql`
|
||||
SELECT page_id FROM catalog_items WHERE id = ${row.catalogItemId}
|
||||
`)) as unknown as [Array<{ page_id: number }>, unknown];
|
||||
const hostPageId = Number(host[0]?.page_id ?? -1);
|
||||
if (!importPageIds.has(hostPageId)) {
|
||||
console.error(
|
||||
`[organizeImportFurni] skipping offer #${row.catalogItemId} on non-import page #${hostPageId}`,
|
||||
);
|
||||
if (!validMoveIds.has(Number(row.catalogItemId))) {
|
||||
// Offer no longer lives in the import tree (moved elsewhere or
|
||||
// deleted) — skip it instead of duplicating it.
|
||||
continue;
|
||||
}
|
||||
await db.execute(sql`
|
||||
UPDATE catalog_items
|
||||
SET page_id = ${pageId}, order_number = ${nextOrder}, catalog_name = ${catalogName}
|
||||
WHERE id = ${row.catalogItemId}
|
||||
`);
|
||||
moveCaseOrder.push(
|
||||
`WHEN ${Number(row.catalogItemId)} THEN ${nextOrder}`,
|
||||
);
|
||||
moveCaseName.push(
|
||||
`WHEN ${Number(row.catalogItemId)} THEN '${catalogName.replace(
|
||||
/['\\]/g,
|
||||
"\\$&",
|
||||
)}'`,
|
||||
);
|
||||
moveIds.push(Number(row.catalogItemId));
|
||||
moved++;
|
||||
} else {
|
||||
await insertCatalogItemRow({
|
||||
@@ -502,6 +528,19 @@ export async function organizeImportFurni(input: {
|
||||
nextOrder++;
|
||||
}
|
||||
|
||||
if (moveIds.length > 0) {
|
||||
await db.execute(sql`
|
||||
UPDATE catalog_items
|
||||
SET page_id = ${pageId},
|
||||
order_number = CASE id ${sql.raw(moveCaseOrder.join(" "))} END,
|
||||
catalog_name = CASE id ${sql.raw(moveCaseName.join(" "))} END
|
||||
WHERE id IN (${sql.join(
|
||||
moveIds.map((id) => sql`${id}`),
|
||||
sql`, `,
|
||||
)})
|
||||
`);
|
||||
}
|
||||
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_page_create",
|
||||
|
||||
Reference in new issue
Block a user