From e7a172f52f731a5e489d5fdea3adafd68cb7c6fd Mon Sep 17 00:00:00 2001 From: Heidi Date: Sat, 20 Jun 2026 00:14:16 +0100 Subject: [PATCH] ai generated solutions to our ai generated problems --- README.md | 5 ++++- example.env | 1 + frontend/public/data/README.md | 4 +++- frontend/src/App.jsx | 2 +- server.cjs | 9 +++++++-- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 68ea7bb..c61834f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,9 @@ the upstream response, rate limits callers, and caches successful GET responses. Public TSS reads are also written to a bounded JSON snapshot cache and served at both their `/api/tss/*` route and matching `/data/*` path. Fresh snapshots return without touching the backend; stale snapshots are served immediately while the -server refreshes them in the background. All responses +server refreshes them in the background. The frontend uses `/api/tss/*` by +default so it can hit this server-side cache directly; set `VITE_STATIC_DATA=true` +only if you explicitly want the client to try `/data/*` first. All responses ship `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy`, `Permissions-Policy`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, HSTS (over HTTPS), and HTML responses include a Content Security Policy that @@ -116,6 +118,7 @@ PUBLIC_DATA_CACHE_DIR=~/tsswebstorage/public-data PUBLIC_DATA_CACHE_FRESH_MS=300000 PUBLIC_DATA_CACHE_STALE_MS=86400000 PUBLIC_DATA_PREWARM_INTERVAL_MS=300000 +VITE_STATIC_DATA=false API_RATE_LIMIT_WINDOW_MS=60000 API_RATE_LIMIT_MAX=120 ``` diff --git a/example.env b/example.env index a4ac672..e866079 100644 --- a/example.env +++ b/example.env @@ -61,3 +61,4 @@ DISCORD_INCLUDE_PATCH=true # TURNSTILE_SECRET_KEY is the server-only secret used to call the Siteverify endpoint. VITE_TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= +VITE_STATIC_DATA=false diff --git a/frontend/public/data/README.md b/frontend/public/data/README.md index ba13e33..5dd8a4d 100644 --- a/frontend/public/data/README.md +++ b/frontend/public/data/README.md @@ -1,6 +1,8 @@ # Static Public Data -The frontend tries these JSON snapshots before falling back to the live API: +The frontend can try these JSON snapshots before falling back to the live API +when `VITE_STATIC_DATA=true`. By default, the app requests `/api/tss/*` directly +and lets the web server answer from the same public data cache. - `/data/leaderboard-teams.json` - `/data/leaderboard-players.json` diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 02d560a..49b751c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -51,7 +51,7 @@ const siteVersion = '1.0.1' const turnstileSiteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || '' const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '') -const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'true').toLowerCase() !== 'false' +const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true' const missingStaticDataPaths = new Set() const defaultAnalyticsPreferences = { diff --git a/server.cjs b/server.cjs index 9af838a..a704b19 100644 --- a/server.cjs +++ b/server.cjs @@ -54,6 +54,8 @@ const PUBLIC_DATA_CACHE_DIR = path.resolve( const PUBLIC_DATA_CACHE_FRESH_MS = Number(process.env.PUBLIC_DATA_CACHE_FRESH_MS || 5 * 60 * 1000) const PUBLIC_DATA_CACHE_STALE_MS = Number(process.env.PUBLIC_DATA_CACHE_STALE_MS || 24 * 60 * 60 * 1000) const PUBLIC_DATA_PREWARM_INTERVAL_MS = Number(process.env.PUBLIC_DATA_PREWARM_INTERVAL_MS || PUBLIC_DATA_CACHE_FRESH_MS) +const PUBLIC_DATA_CACHE_MAX_AGE_SECONDS = Math.max(0, Math.floor(PUBLIC_DATA_CACHE_FRESH_MS / 1000)) +const PUBLIC_DATA_STALE_REVALIDATE_SECONDS = Math.max(0, Math.floor(PUBLIC_DATA_CACHE_STALE_MS / 1000)) const API_RATE_LIMIT_WINDOW_MS = Number(process.env.API_RATE_LIMIT_WINDOW_MS || 60000) const API_RATE_LIMIT_MAX = Number(process.env.API_RATE_LIMIT_MAX || 120) const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_SECRET_KEY || '' @@ -293,7 +295,7 @@ function sendPublicDataFile(req, res, filePath, status = 200, extraHeaders = {}) send(res, status, data, { 'content-type': 'application/json; charset=utf-8', - 'cache-control': 'public, max-age=30, stale-while-revalidate=300', + 'cache-control': `public, max-age=${PUBLIC_DATA_CACHE_MAX_AGE_SECONDS}, stale-while-revalidate=${PUBLIC_DATA_STALE_REVALIDATE_SECONDS}`, ...extraHeaders, }) }) @@ -1948,8 +1950,11 @@ function proxyRequest(req, res) { const headers = { ...proxyRes.headers, ...securityHeaders(req), - 'cache-control': publicDataFile ? 'public, max-age=30, stale-while-revalidate=300' : 'private, max-age=15', + 'cache-control': publicDataFile + ? `public, max-age=${PUBLIC_DATA_CACHE_MAX_AGE_SECONDS}, stale-while-revalidate=${PUBLIC_DATA_STALE_REVALIDATE_SECONDS}` + : 'private, max-age=15', } + if (publicDataFile) headers['x-tssbot-cache'] = 'public-data-miss' delete headers['access-control-allow-origin'] delete headers['access-control-allow-credentials']