Ilia Denisov 13be7c3d9a
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m12s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m53s
feat(ads): VK post-move interstitial + retire deprecated hint_balance/paid_account
Interstitial video after a confirmed play or a hint, VK-only, offline
banner-only. The gate is client-mirrored: the backend puts the config
cooldowns (global 5m / vs_ai 30m / hint 1m) and a suppressed flag (the
no-ads / no_banner gate, same as the banner) on Profile.ads via adsFor;
the client self-gates on a per-kind last-shown time in localStorage, with
the VITE_ADS_STUB contour "ad fired" toast. Never fires after a pass,
exchange or resign. maybeShowInterstitial + vkShowInterstitial; the codec
and gateway transcode carry the ads block.

Also retires the deprecated accounts.hint_balance / paid_account domain
usage (expand-contract, code only — the columns stay for a later DROP so
image rollback stays DB-safe): drop the Account fields and their scan, the
dead account.SpendHint, account.GrantHints and the admin grant-hints
action; the in-game hint display now comes wholly from the payments hint
benefit. Banner eligibility and account merge no longer read the legacy
flags.

Tests: ads.test.ts (the client-mirrored gate), the codec ads round-trip,
the gateway ads transcode test, and a profile ads-config integration test
(cooldowns + suppressed under no-ads / no_banner). Docs: PAYMENTS §10 (+ru)
interstitial, the decision amends, backend README, the plan.
2026-07-10 02:48:10 +02:00

scrabble-game

Multiplatform Scrabble game. Players arrive from a platform (Telegram first; later VK/MAX/iOS/Android) or from standalone web (email / guest). The game supports English Scrabble, Russian Scrabble and Эрудит.

Components

  • gateway — the only public ingress: anti-abuse, platform authentication (resolves the player and injects X-User-ID), routing to backend, and an admin surface behind Basic Auth.
  • backend — internal-only service that owns every domain concern and embeds the scrabble-solver engine library in-process.
  • ui — pure-HTML5 client (plain Svelte 5 + TypeScript + Vite) over Connect-RPC
    • FlatBuffers, embeddable in platform webviews and packageable to native via Capacitor. See ui/README.md.
  • platform/* — per-platform side-services (e.g. the Telegram bot).

Documentation (sources of truth)

Build & test

go build ./backend/... ./pkg/... ./gateway/...  # per module (the workspace spans several)
go vet ./backend/... ./pkg/... ./gateway/...
gofmt -l .                                       # must print nothing
go test -count=1 ./backend/... ./pkg/... ./gateway/...   # unit tests
go test -tags=integration -count=1 -p=1 ./backend/...    # + Postgres (needs Docker)

The integration-tagged tests start a throwaway postgres:17-alpine container via testcontainers-go and require a reachable Docker daemon; they live in the backend module. The wire contracts in pkg and the Connect edge in gateway have committed generated code (regenerate dev-time with make -C pkg gen / make -C gateway gen).

Run the backend locally

The backend now owns persistence, so it needs Postgres and applies its embedded migrations at startup:

docker run -d --name scrabble-pg -e POSTGRES_PASSWORD=dev -p 5432:5432 postgres:17-alpine
BACKEND_POSTGRES_DSN='postgres://postgres:dev@localhost:5432/postgres?search_path=backend&sslmode=disable' \
  go run ./backend/cmd/backend     # HTTP API + probes on :8080, push gRPC on :9090

Run the gateway locally

The gateway is the public edge; point it at a running backend:

GATEWAY_BACKEND_HTTP_URL=http://localhost:8080 \
GATEWAY_BACKEND_GRPC_ADDR=localhost:9090 \
  go run ./gateway/cmd/gateway     # Connect/h2c edge on :8081

Key environment: BACKEND_HTTP_ADDR (default :8080), BACKEND_LOG_LEVEL (debug|info|warn|error, default info), BACKEND_POSTGRES_DSN (required). The full configuration surface and the go-jet regeneration step live in backend/README.md.

Run the UI locally

cd ui && pnpm install
pnpm start     # mock mode: lobby -> game with no backend, on http://localhost:5173
pnpm dev       # against a running gateway (Vite proxies the RPC path to :8081)

pnpm check (type-check), pnpm test:unit (Vitest), pnpm test:e2e (Playwright smoke vs the mock), pnpm build (static bundle). Details — including the committed edge codegen (pnpm codegen) — are in ui/README.md.

Deploy (deploy/)

The full contour is deploy/docker-compose.yml: backend + gateway (with the UI embedded via go:embed, baked in by its node build stage) + Postgres + the Telegram connector (with a VPN sidecar) + an observability stack (OTel Collector → Prometheus + Tempo → Grafana) + a front caddy that owns a single /_gm Basic-Auth (admin console + Grafana). The Go services build from multi-stage distroless */Dockerfile.

docker build --build-arg DICT_VERSION=v1.3.0 -f backend/Dockerfile -t scrabble-backend .  # DICT_VERSION required; pulls that DAWG release artifact
docker build -f gateway/Dockerfile -t scrabble-gateway .   # node stage builds + embeds the UI
docker compose -f deploy/docker-compose.yml config         # validate (needs the TEST_/PROD_ env)

CI auto-deploys the test contour on a PR into — or push to — development (.gitea/workflows/ci.yaml); the prod contour is a manual deploy after development → master. Env reference: deploy/.env.example; the topology and the two-contour model are in docs/ARCHITECTURE.md §13.

S
Description
No description provided
Readme 26 MiB
v1.1.0 Latest
2026-06-22 07:27:41 +00:00
Languages
Go 57.8%
TypeScript 29.2%
Svelte 8.7%
JavaScript 1.3%
Go Template 1.2%
Other 1.7%