feat(export): server-rendered artifacts behind one signed download URL
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m27s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 16s
CI / ui (pull_request) Successful in 1m4s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m27s
The finished-game export now works identically on every platform: the client mints a signed relative URL (game.export_url) carrying its date locale, IANA time zone and localized non-play labels, resolves it against its own origin and hands it to the platform's native download — Telegram downloadFile, VKWebAppDownloadFile, or a plain browser anchor (also the desktop VK iframe). Both artifacts ride the route: the .gcg text (no more clipboard mode, except the legacy pre-8.0 Telegram fallback) and the PNG of the final position. The PNG is rasterized by a new internal 'renderer' sidecar (node:22-slim + skia-canvas + baked Liberation/Noto Color Emoji fonts) executing the SAME ui/src/lib/gameimage.ts the web project unit-tests, bundled at image build time — one renderer, no drift; the browser no longer draws or delivers bytes itself. The backend rebuilds the render payload from the journal + engine.AlphabetTable, verifies the HMAC (10-minute TTL, BACKEND_EXPORT_SIGN_KEY, constant-time, uniform 404s) on its public group, and streams the artifact as a named attachment; the gateway forwards /dl/* behind the per-IP public rate limiter (caddy @gateway matcher extended — the landing catch-all trap). Deploy: renderer service (compose + prod overlay + roll before backend + prod push list), EXPORT_SIGN_KEY env (TEST_/PROD_ secrets), CI runs the sidecar smoke in the ui job. Docs: ARCHITECTURE export-delivery section, FUNCTIONAL(+_ru), UI_DESIGN, TESTING, deploy/README.
This commit is contained in:
@@ -61,6 +61,36 @@ services:
|
||||
memory: 512M
|
||||
networks: [internal]
|
||||
|
||||
# The finished-game image-render sidecar: internal-only, called by the backend for the
|
||||
# PNG export artifact. It runs the same ui/src/lib/gameimage.ts the web client
|
||||
# unit-tests, on skia-canvas (renderer/README.md). node:22-slim carries a shell, so —
|
||||
# uniquely among the built services — it has a real container healthcheck the backend's
|
||||
# depends_on gates on.
|
||||
renderer:
|
||||
container_name: scrabble-renderer
|
||||
image: scrabble-renderer:latest
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: renderer/Dockerfile
|
||||
args:
|
||||
VERSION: ${APP_VERSION:-dev}
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
environment:
|
||||
RENDERER_PORT: "8090"
|
||||
healthcheck:
|
||||
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8090/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 12
|
||||
# A render peaks well under 100 MiB (a 2-MP canvas + skia); idle sits near 60 MiB.
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: "1.0"
|
||||
memory: 192M
|
||||
networks: [internal]
|
||||
|
||||
backend:
|
||||
container_name: scrabble-backend
|
||||
image: scrabble-backend:latest
|
||||
@@ -80,9 +110,15 @@ services:
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
renderer:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
# search_path=backend matches the migrations (00001 creates the schema).
|
||||
BACKEND_POSTGRES_DSN: postgres://${POSTGRES_USER:-scrabble}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-scrabble}?sslmode=disable&search_path=backend
|
||||
# The finished-game export: the render sidecar address + the HMAC key signing the
|
||||
# public download URLs (deploy env: TEST_/PROD_EXPORT_SIGN_KEY).
|
||||
BACKEND_RENDERER_URL: http://renderer:8090
|
||||
BACKEND_EXPORT_SIGN_KEY: ${EXPORT_SIGN_KEY:?set EXPORT_SIGN_KEY — the export download URL signing secret}
|
||||
# The pool caps at 25 conns (~28 backends) around 500 players; 40 gives headroom
|
||||
# for bursts. Postgres (2 cores / 512 MiB) handles it.
|
||||
BACKEND_POSTGRES_MAX_OPEN_CONNS: "40"
|
||||
|
||||
Reference in New Issue
Block a user