e9f836db87
Tests · Go / test (push) Successful in 9s
Tests · Integration / integration (push) Successful in 10s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 8s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Service-agnostic refinement of the owner's idea: the sign-in service returns a set of supported game languages with the user identity, and the lobby gates the New Game variant choice by it (en -> English; ru -> Russian + Эрудит). - Connector hosts two bots in one container (one per service language, each its own token + game channel; the same telegram_id spans both). ValidateInitData tries each token and returns the validating bot's service_language + supported_languages. Per-language config (TELEGRAM_BOT_TOKEN_EN/_RU, channels). - supported_languages rides the Session (fbs, session-scoped, not persisted); the UI offers only the matching variants on New Game — gating only the START of a new game (auto-match + friend invite), not accept/open/play; backend does not enforce. - service_language persisted (accounts.service_language, migration 00010, written every login, last-login-wins) and routes the user-facing Notify push back through the right bot (push-target coalesces with preferred_language). - Admin SendToUser/SendToGameChannel gain an operator-chosen language selector in the console (unrelated to ValidateInitData). - Non-Telegram logins carry the gateway default set (GATEWAY_DEFAULT_SUPPORTED_LANGUAGES, all variants). Wire (committed regen): ValidateInitDataResponse +service_language +supported_languages; Session +supported_languages; SendToUser/SendToGameChannel +language. Docs (ARCHITECTURE/FUNCTIONAL/_ru/READMEs) + PLAN updated; stage marked done.
112 lines
5.6 KiB
Markdown
112 lines
5.6 KiB
Markdown
# 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`](../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`](../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`,
|
|
`stats.get`, `game.gcg`, and the `notify` live event — all via the identical
|
|
transcode pattern (`transcode_social.go`). **Stage 11** added account linking & merge
|
|
— `link.email.request/confirm/merge` and `link.telegram.confirm/merge`
|
|
(`transcode_link.go`); the telegram ops validate the **Login Widget** payload via the
|
|
connector (`ValidateLoginWidget`) and forward the trusted `external_id`. These
|
|
**superseded** the Stage 8 `email.bind.*` ops, which were removed.
|
|
|
|
## 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_DEFAULT_SUPPORTED_LANGUAGES` | `en,ru` | New Game variant gating set placed on the Session for non-platform logins (web / email / guest); a deployment may narrow it |
|
|
| `GATEWAY_SESSION_TTL` | `10m` | cached session lifetime |
|
|
| `GATEWAY_SESSION_CACHE_MAX` | `50000` | cached session cap |
|
|
| `GATEWAY_PUSH_HEARTBEAT_INTERVAL` | `15s` | live-stream keep-alive |
|
|
| `GATEWAY_SERVICE_NAME` | `scrabble-gateway` | OpenTelemetry `service.name` |
|
|
| `GATEWAY_OTEL_TRACES_EXPORTER` | `none` | `none`, `stdout` or `otlp` (gRPC; endpoint from `OTEL_EXPORTER_OTLP_*`) |
|
|
| `GATEWAY_OTEL_METRICS_EXPORTER` | `none` | `none`, `stdout` or `otlp` |
|
|
|
|
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
|
|
|
|
```sh
|
|
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`):
|
|
|
|
```sh
|
|
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`](../pkg) (`make -C pkg fbs`).
|
|
|
|
## Tests
|
|
|
|
```sh
|
|
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.
|