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

+34
View File
@@ -103,6 +103,27 @@ const schema = z
// Redis — strongly recommended in production (required for multi-instance).
// Without it, rate limits / shared caches are in-process only.
REDIS_URL: z.string().optional(),
// ── Cache TTL tuning (seconds; see src/lib/perf-ttls.ts) ──────────────
// Defaults match the previous hard-coded values; set only to tune.
CMS_HOME_PAYLOAD_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_NEWS_LIST_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_NEWS_ARCHIVE_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_SEARCH_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_ONLINE_COUNT_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_COUNTERS_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_SITE_SETTINGS_TTL_SEC: z.coerce.number().int().positive().optional(),
CMS_SITE_SETTINGS_MEMORY_TTL_SEC: z.coerce
.number()
.int()
.positive()
.optional(),
// Boot-time cache warm-up (priming hot keys after each deploy). Set to
// "false"/"0" to skip — useful on hosts where the boot DB burst is
// undesirable (e.g. tiny containers right after a restore).
CMS_WARMUP_ENABLED: z
.string()
.optional()
.transform((value) => value !== "false" && value !== "0"),
// App-layer anti-DDoS gate (proxy rate limiter). On by default in
// production; set to "false" or "0" to disable without removing it.
ANTI_DDOS_ENABLED: z
@@ -151,6 +172,19 @@ const schema = z
.string()
.optional()
.transform((value) => value !== "false" && value !== "0"),
// ── CAPTCHA (Turnstile) — env fallback for the DB-backed settings ─────
// The admin panel (housekeeping → settings) wins when it has values; these
// let a deploy enable Turnstile without touching the DB. An explicit DB
// value — including "none" — always overrides the env fallback.
CAPTCHA_PROVIDER: z.preprocess(
(value) => (value === "" ? undefined : value),
z.enum(["none", "turnstile", "recaptcha", "hcaptcha"]).optional(),
),
// Public site key (rendered in the widget via server → client props).
TURNSTILE_SITE_KEY: z.string().optional(),
// Server-only secret used against Cloudflare's siteverify endpoint.
// Never NEXT_PUBLIC_: verification would become client-trusting.
TURNSTILE_SECRET_KEY: z.string().optional(),
})
.superRefine((data, ctx) => {
if (data.NODE_ENV !== "production") return;