perf(app): batch catalog updates, tune caches, add captcha env fallback
- catalog: apply bulk offer edits in a single transaction with one RCON refresh instead of N round-trips, virtualize the items table past 150 rows and cap the shop preview at 200 tiles - cache: lift the public cache TTLs and the boot warm-up into env-tunable constants (src/lib/perf-ttls.ts; CMS_*_TTL_SEC, CMS_WARMUP_ENABLED) - auth: resolve the captcha provider, site key and secret from CAPTCHA_PROVIDER, TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY when the admin settings are unset; an explicit DB value (including "none") still wins, so Turnstile can be enabled per-deploy without touching the DB.
This commit is contained in:
1 parent
05c2706eaf
commit
9f024dc6ac
15 files changed
+529
-83
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
restoreDeletedCatalogItemsCommand,
|
||||
} from "@/features/catalog/server/item-deletes";
|
||||
import {
|
||||
bulkUpdateOffersCommand,
|
||||
createOfferCommand,
|
||||
moveBcOffersCommand,
|
||||
moveOffersCommand,
|
||||
@@ -399,6 +400,91 @@ export async function updateCatalogItem({
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* "Save All" for the inline table editor. One transaction, one RCON refresh,
|
||||
* one audit row — instead of a full per-item round-trip that took tens of
|
||||
* seconds on pages with hundreds of edited rows. A failing row rolls back the
|
||||
* whole batch, so the editor never ends up half-saved.
|
||||
*/
|
||||
export async function updateCatalogItemsBulk({
|
||||
updates,
|
||||
catalog = "normal",
|
||||
}: {
|
||||
updates: Array<{ id: number; catalogFields: Record<string, unknown> }>;
|
||||
catalog?: "normal" | "bc";
|
||||
}) {
|
||||
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||
if (!Array.isArray(updates) || updates.length === 0) {
|
||||
return { ok: false as const, error: "No items to update" };
|
||||
}
|
||||
if (updates.length > 500) {
|
||||
return { ok: false as const, error: "Max 500 items per bulk update" };
|
||||
}
|
||||
// BC rows go through their own command per item (no price columns, and the
|
||||
// BC command keeps its narrower field set); the loop still saves the
|
||||
// per-item RCON refreshes inside one export lock below.
|
||||
if (catalog === "bc") {
|
||||
const { updateBcItem } = await import("@/actions/catalog-bc");
|
||||
return await withCatalogExport(async () => {
|
||||
let savedCount = 0;
|
||||
let lastError = "";
|
||||
for (const update of updates) {
|
||||
const result = await updateBcItem({
|
||||
id: update.id,
|
||||
...(update.catalogFields as {
|
||||
itemIds?: string;
|
||||
catalogName?: string;
|
||||
orderNumber?: number;
|
||||
extradata?: string;
|
||||
}),
|
||||
});
|
||||
if (result.ok) savedCount++;
|
||||
else lastError = result.error ?? "Unable to update item";
|
||||
}
|
||||
if (savedCount > 0) {
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "bc_items_bulk_update",
|
||||
description: `Updated ${savedCount} BC catalog item(s)`,
|
||||
targetType: "catalog_item_bc",
|
||||
});
|
||||
}
|
||||
if (savedCount === 0 && lastError) {
|
||||
return { ok: false as const, error: lastError };
|
||||
}
|
||||
return {
|
||||
ok: true as const,
|
||||
data: { savedCount, failedCount: updates.length - savedCount },
|
||||
};
|
||||
});
|
||||
}
|
||||
return await withCatalogExport(async () => {
|
||||
try {
|
||||
await bulkUpdateOffersCommand(updates, staff.id);
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Unable to update catalog items",
|
||||
};
|
||||
}
|
||||
await sendCatalogUpdate();
|
||||
await logStaffActivity({
|
||||
staffId: staff.id,
|
||||
action: "catalog_items_bulk_update",
|
||||
description: `Updated ${updates.length} catalog item(s)`,
|
||||
targetType: "catalog_item",
|
||||
});
|
||||
revalidatePath("/admin/catalog");
|
||||
return {
|
||||
ok: true as const,
|
||||
data: { savedCount: updates.length, failedCount: 0 },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export async function translateCatalogItems(input: {
|
||||
/** `id` is items_base.id (not catalog_items.id) */
|
||||
items: Array<{ id: number; publicName: string; description?: string }>;
|
||||
|
||||
Reference in new issue
Block a user