New public ingress and the first network edge. Framework + a vertical slice of operations end-to-end; remaining ops reuse the same transcode pattern in Stage 7. Contracts (new module scrabble/pkg): - push.proto (backend->gateway gRPC server-stream) + scrabble.fbs (FlatBuffers edge payloads), committed generated Go; buf/flatc Makefiles (dev-time codegen). Backend: - REST handlers on the /api/v1 groups: internal session endpoints (telegram/guest/email login -> mint, resolve, revoke) and the user slice (profile, submit_play, state, lobby enqueue/poll, chat). - internal/notify in-process Publisher hub + internal/pushgrpc gRPC server (BACKEND_GRPC_ADDR) streaming your_turn/opponent_moved/chat/nudge/match_found; emission in game.commit, social, matchmaker. - migration 00005 accounts.is_guest; guests are durable rows excluded from stats; ProvisionGuest; email-as-login (RequestLoginCode/LoginWithCode). Gateway (new module scrabble/gateway): - Connect Gateway service over h2c (Execute + Subscribe), FlatBuffers<->JSON transcode registry, Telegram initData HMAC validator (seam), session cache, token-bucket rate limiter (3 classes), push fan-out hub, backend REST + push gRPC client, admin Basic-Auth reverse proxy. go.work: use ./pkg, ./gateway + replace scrabble/pkg. CI: gateway/**, pkg/** path filters; unit build/vet/test span all three modules. Docs (PLAN, ARCHITECTURE, FUNCTIONAL+ru, TESTING, READMEs) updated; gateway/pkg unit tests + guest/email-login integration tests.
4.1 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 fronts the backend admin API 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/auth/ # Telegram initData HMAC validator (seam + fixtures)
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 to the backend admin API
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.
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. Further
operations follow the same transcode pattern (added in Stage 7).
Configuration
| Variable | Default | Notes |
|---|---|---|
GATEWAY_HTTP_ADDR |
:8081 |
public Connect/h2c listener |
GATEWAY_ADMIN_ADDR |
:8082 |
admin proxy listener (enabled only with creds) |
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 proxy |
GATEWAY_TELEGRAM_BOT_TOKEN |
unset | enable the Telegram auth path |
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.