Server-rendered admin console in the backend at /_gm (internal/adminconsole), fronted on the gateway's public listener by Basic-Auth + a verbatim reverse proxy (mounted on the edge mux below the h2c wrap). A same-origin check guards its POSTs; no operator identity is tracked. This supersedes the Stage 6 gateway-fronts- /api/v1/admin model: GATEWAY_ADMIN_ADDR and the backend /api/v1/admin ping are dropped and gateway/internal/admin is repurposed to the verbatim proxy. - Complaints: migration 00008 (+ jetgen) adds disposition/resolution_note/ resolved_at/applied_in_version + the deferred status CHECK; resolution feeds a query-derived pending dictionary-change pipeline (marked applied after a reload). - Dictionary hot-reload: per-version subdir BACKEND_DICT_DIR/<version>/ via the new Registry.LoadAvailable; engine.OpenWithVersions restores resident versions on restart. Partially addresses TODO-2. - Broadcasts: a backend Telegram-connector client (internal/connector, BACKEND_CONNECTOR_ADDR) for SendToUser / SendToGameChannel (discharges the Stage 9 forward-note). - Admin reads: account.ListAccounts/CountAccounts/Identities and game.ListGames/CountGames/GameByID/ListComplaints/GetComplaint/CountComplaints/ ResolveComplaint/DictionaryChanges/MarkChangesApplied. - Tests: adminconsole render, engine reload, same-origin guard, gateway verbatim proxy + h2c console mount, inttest complaint pipeline + list/count + /_gm console. - Docs: PLAN (Stage 10 done + refinements + TODO-2), ARCHITECTURE §1/§5/§6/§12/§13, FUNCTIONAL (+_ru), TESTING, backend/gateway READMEs.
4.9 KiB
gateway
The Scrabble platform's only public ingress (module scrabble/gateway). It
terminates the client's Connect-RPC + FlatBuffers traffic over HTTP/2
cleartext (h2c), authenticates the originating credential, mints/resolves a
thin opaque session, rate-limits, injects X-User-ID when forwarding to the
backend over REST/JSON, and bridges the backend's gRPC push stream to each
client's in-app live channel. It also serves the backend's admin console at /_gm
on its public listener behind HTTP Basic-Auth. See
../docs/ARCHITECTURE.md §2, §3, §10, §12.
Package layout
cmd/gateway/ # main: config -> backend client -> session cache ->
# push hub -> Connect h2c server (+ admin) -> serve
proto/edge/v1/ # Connect envelope contract (committed generated Go)
internal/config/ # GATEWAY_* env config
internal/backendclient/ # typed REST client (+ X-User-ID) and push gRPC client
internal/session/ # in-memory session cache (LRU/TTL, backend fallback)
internal/ratelimit/ # token-bucket limiter (golang.org/x/time/rate)
internal/connector/ # gRPC client to the Telegram connector (initData validate, out-of-app push) + routing
internal/push/ # live-event fan-out hub (per-user client streams)
internal/transcode/ # FlatBuffers<->REST bridge + message_type registry
internal/connectsrv/ # the Connect Gateway service over h2c
internal/admin/ # Basic-Auth reverse proxy mounting the backend admin console at /_gm (verbatim)
The FlatBuffers payloads and the backend push proto are the shared wire
contracts in ../pkg.
Transport contract
A single Gateway Connect service: Execute(message_type, payload, request_id)
for unary operations and Subscribe for the live stream. The payload bytes are
FlatBuffers tables (scrabble/pkg/fbs); the gateway transcodes them to and from
the backend's JSON. The session token rides in Authorization: Bearer; auth.*
operations are unauthenticated and return the minted token. A unary domain
outcome rides back in ExecuteResponse.result_code (HTTP 200); only edge
failures become Connect error codes.
auth.telegram validates the Mini App initData by calling the Telegram connector
(GATEWAY_CONNECTOR_ADDR), which holds the bot token; the gateway also routes
out-of-app push to that connector for recipients with no live in-app stream
(ARCHITECTURE.md §10). When GATEWAY_CONNECTOR_ADDR is unset, both are disabled.
The Stage 6 message-type slice: auth.telegram, auth.guest,
auth.email.request, auth.email.login, profile.get, game.submit_play,
game.state, lobby.enqueue, lobby.poll, chat.post; live events
your_turn, opponent_moved, chat_message, nudge, match_found. Stage 7
added the play-loop ops; Stage 8 added the social/account/history ops —
friends.* (list/incoming/request/respond/cancel/unfriend/code.issue/code.redeem),
blocks.*, invitation.* (list/create/accept/decline/cancel), profile.update,
email.bind.*, stats.get, game.gcg, and the notify live event — all via the
identical transcode pattern (transcode_social.go).
Configuration
| Variable | Default | Notes |
|---|---|---|
GATEWAY_HTTP_ADDR |
:8081 |
public Connect/h2c listener (also serves the admin console at /_gm) |
GATEWAY_LOG_LEVEL |
info |
zap level |
GATEWAY_BACKEND_HTTP_URL |
http://localhost:8080 |
backend REST base URL |
GATEWAY_BACKEND_GRPC_ADDR |
localhost:9090 |
backend push gRPC address |
GATEWAY_BACKEND_TIMEOUT |
5s |
per backend REST call |
GATEWAY_ADMIN_USER / GATEWAY_ADMIN_PASSWORD |
unset | enable + guard the admin console at /_gm |
GATEWAY_CONNECTOR_ADDR |
unset | Telegram connector gRPC address (enables initData validation + out-of-app push) |
GATEWAY_SESSION_TTL |
10m |
cached session lifetime |
GATEWAY_SESSION_CACHE_MAX |
50000 |
cached session cap |
GATEWAY_PUSH_HEARTBEAT_INTERVAL |
15s |
live-stream keep-alive |
Rate-limit defaults (built-in): public 30/min·IP (burst 10), authenticated 120/min·user (burst 40), admin 60/min·IP (burst 20), email-code 5/10 min·IP.
Run
GATEWAY_BACKEND_HTTP_URL=http://localhost:8080 \
GATEWAY_BACKEND_GRPC_ADDR=localhost:9090 \
go run ./gateway/cmd/gateway # Connect edge on :8081
Generated code
The Connect envelope Go is committed under proto/edge/v1. Regenerate after
editing the .proto (dev-time, like backend/cmd/jetgen):
make -C gateway tools # go install protoc-gen-go + protoc-gen-connect-go
make -C gateway gen # buf generate (local plugins)
The FlatBuffers payloads are generated in ../pkg (make -C pkg fbs).
Tests
go test -count=1 ./gateway/...
All gateway tests are hermetic: no real network, a fake backend (httptest) and
credential fixtures. There is no integration (Docker) suite — the gateway holds
no database.