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:
openhands committed 2026-10-15 16:00:00 +02:00
1 parent 05c2706eaf
commit 9f024dc6ac
15 files changed
+529 -83

No files matched your search

@@ -18,6 +18,14 @@ interface ItemsShopPreviewProps {
onItemClick?: (item: CatalogItemData) => void;
}
/**
* The preview is a visual check, not an editor. Mounting a tile (image, badges,
* click handler) per offer made pages with hundreds of items crawl, so the grid
* shows a window and states how many are hidden. The table view is the place to
* work through the full list.
*/
const PREVIEW_TILE_LIMIT = 200;
export function ItemsShopPreview({
items,
furniRevisionMap,
@@ -38,15 +46,23 @@ export function ItemsShopPreview({
const gridClass = getShopGridClass(layout);
const tileSize = getShopTileSize(layout);
const visibleItems =
items.length > PREVIEW_TILE_LIMIT
? items.slice(0, PREVIEW_TILE_LIMIT)
: items;
return (
<div className="rounded-lg border bg-gradient-to-b from-[var(--admin-surface)] to-[var(--admin-canvas)] p-4">
<div className="mb-3 flex items-center justify-between text-xs text-[var(--admin-text-muted)]">
<span className="font-medium">Shop preview · {layout}</span>
<span>{items.length} items</span>
<span>
{items.length} items
{visibleItems.length !== items.length &&
` · showing first ${visibleItems.length}`}
</span>
</div>
<div className={cn("grid gap-2", gridClass)}>
{items.map((item) => {
{visibleItems.map((item) => {
const firstId = parseFirstItemId(item.itemIds);
const furni = firstId != null ? furniRevisionMap[firstId] : undefined;
@@ -62,6 +78,12 @@ export function ItemsShopPreview({
);
})}
</div>
{visibleItems.length !== items.length && (
<p className="mt-3 text-center text-xs text-[var(--admin-text-muted)]">
Preview capped at {PREVIEW_TILE_LIMIT} items — use the table view for
the rest.
</p>
)}
</div>
);
}