ADR-0005 · Шлюз ингеста на Koyeb
Статус: принято и реализовано · 2026-06-23
Контекст. Публичный браузер не должен держать INGEST_TOKEN, поэтому между SPA и аналитикой нужен токен-шлюз. Аналитика выставлена наружу POST-only как sync.jc.id.lv (NPM: location = /api/games + limit_except POST, без IP-ограничений). Есть бесплатный план на app.koyeb.com.
Решение. Токен-шлюз на Koyeb (free, репо dicechess-ingest-gateway, Node 26, голый node:http). Держит Bearer-токен, применяет CORS-allowlist + rate-limit + структурную проверку (source=playsite) и форвардит на sync.jc.id.lv с Bearer. Авторитетная валидация — реплей движком в самой аналитике (422); локальный пред-реплей в шлюз НЕ добавляли (чтобы не расходиться по версии движка). Статусы 200/201/400/422 пробрасываются клиенту, остальное → 502.
Реализация (проверено вживую). yummy-corene-rabestro-44de89b4.koyeb.app. End-to-end подтверждено реальными партиями: POST → 201, записи source='playsite' в аналитике. Гоча при отладке: тестовые id были не-UUID → аналитика отдаёт 400 (id это UUID), а ранний шлюз мапил 400→502, и Cloudflare-эдж Koyeb маскировал это в «error code: 502» — ложный «сетевой» след. Лог NPM (proxy-host-11_access.log) показал, что запросы Koyeb доходят → диагноз развернулся.
Последствия.
- Разгружаем homelab; managed HTTPS-эндпоинт; токен только в шлюзе, в браузере — никогда.
- free-план Koyeb спит при простое → cold start (~12с на первую партию) — приемлемо, ингест асинхронный (IndexedDB-outbox).
- Шлюз crash-proof (обработчик всегда отвечает) + логирует upstream-ошибки; upstream-таймаут 10с→504.
- С фазы 3 ингест уезжает на серверное ядро игры — браузерное реле уходит.
Альтернатива (отклонена). Шлюз на homelab (rpi4, LAN-доступ к аналитике) — не понадобился: Koyeb достукивается до sync.jc.id.lv штатно (сеть/NPM-конфиг исправны).