Stage 1: backend foundation (Postgres, sessions, accounts, OTel)
- internal/postgres: pgx-over-database/sql pool (otelsql), embedded goose
migrations into schema 'backend', committed go-jet code + cmd/jetgen tool.
- internal/account: durable accounts + unified telegram/email identities
(UUIDv7 keys), find-or-create provisioning with unique-conflict handling.
- internal/session: opaque 256-bit tokens stored as a SHA-256 hash, revoke-only
(no TTL); write-through cache gating /readyz; store + service.
- internal/telemetry: OTel tracer/meter providers (none/stdout) + request-timing
middleware; internal/config gains Postgres + OTel env loading.
- internal/server: /api/v1 {public,user,internal,admin} skeleton + X-User-ID
middleware; /readyz checks DB ping + cache; main wires
telemetry -> db+migrate -> warm cache -> server.
- Tests: unit + integration (build tag 'integration', testcontainers
postgres:17) for migrations, accounts, sessions, readyz; new integration.yaml.
- Docs: ARCHITECTURE, TESTING, PLAN refinements, root + backend READMEs.
Session/account REST handlers deferred to Stage 6 (gateway); OTLP + dashboards
to Stage 11.
This commit is contained in:
+34
-14
@@ -71,8 +71,11 @@ arrive from a platform rather than completing a mandatory registration).
|
||||
(`session_id`).
|
||||
- The client holds `session_id` in memory for the app session (browser/OS
|
||||
storage is optional and may be unavailable; losing it means re-login).
|
||||
- The gateway caches `session → user_id` and injects `X-User-ID`. Sessions are
|
||||
revocable. Session records live in `backend`.
|
||||
- The gateway caches `session → user_id` and injects `X-User-ID`. Session
|
||||
records live in `backend`, which stores only a **SHA-256 hash** of the opaque
|
||||
token (never the plaintext), keeps a warmed in-memory cache for fast
|
||||
resolution, and treats sessions as **revoke-only** — they have no TTL and live
|
||||
until explicitly revoked (`status` → `revoked`).
|
||||
- **Guest** = ephemeral web session (no platform, no email): session-only,
|
||||
nothing persisted; restricted to auto-match, with no friends and no
|
||||
stats/history. Platform users are auto-provisioned **durable** accounts.
|
||||
@@ -82,6 +85,10 @@ arrive from a platform rather than completing a mandatory registration).
|
||||
- One internal account may carry several **platform identities**
|
||||
(`telegram`, `vk`, …) plus an optional **email** identity. First contact from
|
||||
a platform auto-provisions a durable account bound to that platform identity.
|
||||
Concretely, platform and email identities share one `identities` table keyed by
|
||||
a unique `(kind, external_id)`; email is an identity with `kind=email` and a
|
||||
`confirmed` flag (the confirm-code flow lands later). Accounts and identities
|
||||
use application-generated **UUIDv7** primary keys.
|
||||
- **Linking** is initiated from an authenticated profile: choose a platform →
|
||||
complete that platform's web-auth confirm → attach the identity to the
|
||||
current account.
|
||||
@@ -155,9 +162,15 @@ within 10 seconds. Designed to be indistinguishable from a person.
|
||||
|
||||
## 9. Persistence
|
||||
|
||||
- Single Postgres database, schema `backend`; `backend` is the only writer.
|
||||
pgx pool; queries via go-jet *(introduced when the first real query lands)*;
|
||||
migrations embedded and applied with `pressly/goose/v3` at startup *(planned)*.
|
||||
- Single Postgres database, schema `backend`; `backend` is the only writer. The
|
||||
"pgx pool" is a `database/sql` handle backed by the pgx stdlib driver and
|
||||
instrumented with otelsql; type-safe queries use **go-jet** (code generated
|
||||
into `internal/postgres/jet` and committed, regenerated by `cmd/jetgen`).
|
||||
Migrations are embedded SQL applied with `pressly/goose/v3` at startup. Primary
|
||||
keys are application-generated **UUIDv7**.
|
||||
- Stage 1 tables: `accounts` (durable internal accounts), `identities`
|
||||
(platform/email identities, unique `(kind, external_id)`) and `sessions`
|
||||
(revoke-only opaque-token hashes).
|
||||
- **Active game state** is stored structurally with the `dict_version` pinned.
|
||||
- **Statistics** (computed on finish): wins, losses, max points in a game, max
|
||||
points for a single word.
|
||||
@@ -185,12 +198,17 @@ delivery fans out to the appropriate channel.
|
||||
|
||||
## 11. Observability
|
||||
|
||||
- Structured logging with `go.uber.org/zap` (JSON). OpenTelemetry traces and
|
||||
metrics, with a Prometheus pull endpoint where configured *(introduced with
|
||||
the first real workload)*.
|
||||
- Per-request server-side timing via middleware from day one. A client-measured
|
||||
RTT piggybacked on the next request is a later enhancement, not MVP.
|
||||
- Unauthenticated `GET /healthz` (liveness) and `GET /readyz` (readiness).
|
||||
- Structured logging with `go.uber.org/zap` (JSON). OpenTelemetry tracer and
|
||||
meter providers are wired (Stage 1), env-gated by
|
||||
`BACKEND_OTEL_{TRACES,METRICS}_EXPORTER` with a default of `none` (so no
|
||||
collector is required locally or in CI); `stdout` is available for debugging
|
||||
and the Postgres pool is instrumented with otelsql. OTLP export, a Prometheus
|
||||
pull endpoint, and dashboards arrive with the first real workload.
|
||||
- Per-request server-side timing via gin middleware from day one (the access log
|
||||
carries method, route, status, latency and the active trace id). A
|
||||
client-measured RTT piggybacked on the next request is a later enhancement.
|
||||
- Unauthenticated `GET /healthz` (liveness) and `GET /readyz` (readiness — the
|
||||
database answers a bounded ping and the session cache is warmed).
|
||||
|
||||
## 12. Security boundaries
|
||||
|
||||
@@ -219,8 +237,10 @@ is something to deploy.
|
||||
- Trunk is **`master`**; feature work happens on `feature/*` branches merged via
|
||||
PR with a green CI gate (from Stage 1 onward — the genesis commit necessarily
|
||||
lands on `master`).
|
||||
- `.gitea/workflows/` holds the CI. `go-unit.yaml` runs gofmt/vet/build/test on
|
||||
Go changes; more workflows (ui-test, integration, deploy) are added with the
|
||||
components they cover.
|
||||
- `.gitea/workflows/` holds the CI. `go-unit.yaml` runs gofmt/vet/build/unit-test
|
||||
on Go changes; `integration.yaml` runs the Postgres-backed tests behind the
|
||||
`integration` build tag (testcontainers `postgres:17-alpine`, Ryuk disabled,
|
||||
serial). Further workflows (ui-test, deploy) are added with the components they
|
||||
cover.
|
||||
- After any push, the run is watched to green before a stage is declared done
|
||||
(`python3 ~/.claude/bin/gitea-ci-watch.py`).
|
||||
|
||||
+5
-2
@@ -8,8 +8,11 @@ tests or touching CI.
|
||||
- **Go unit tests** — table-driven where it helps; `testing` + standard library.
|
||||
Every functional change ships with regression coverage. Run:
|
||||
`go test -count=1 ./backend/...` (the module list grows with the workspace).
|
||||
- **Integration** *(introduced with Postgres in Stage 1)* — `testcontainers-go`
|
||||
spins real dependencies (Postgres). Slow; a separate CI workflow.
|
||||
- **Integration** *(Stage 1+)* — Postgres-backed tests behind the `integration`
|
||||
build tag spin a throwaway `postgres:17-alpine` via `testcontainers-go`. They
|
||||
live in `backend/internal/inttest` and run with
|
||||
`go test -tags=integration -count=1 -p=1 ./backend/...` (needs Docker), guarded
|
||||
by a separate CI workflow (`integration.yaml`; Ryuk disabled, serial). Slow.
|
||||
- **UI** *(introduced with the UI in Stage 7)* — Vitest (unit) + Playwright
|
||||
(e2e), mirroring the chosen plain-Svelte + Vite toolchain.
|
||||
- **Engine** — correctness of scoring and move generation is owned by
|
||||
|
||||
Reference in New Issue
Block a user