Release: Quarkus backend, Keycloak login, game history, clock, bot, tutorial and rematch #3

Merged
jhalitaksoy merged 88 commits from develop into main 2026-10-17 03:23:38 +03:00
4 changed files with 44 additions and 10 deletions
Showing only changes of commit b94c9336dd - Show all commits

28
TODO.md
View File

@ -18,6 +18,13 @@ The web app logs in with Keycloak; the mobile app still uses the legacy `?userKe
- [ ] Delete the unused copy of the WebSocket client in `apps/mobile/src/rtmt` - [ ] Delete the unused copy of the WebSocket client in `apps/mobile/src/rtmt`
- [ ] Test on a device - [ ] Test on a device
### Game history on mobile
The web app has game history (see `docs/specs/game-history`); the mobile app needs Keycloak login first.
- [ ] Ongoing-game banner, live and popular games, recent games on the home screen
- [ ] Move list and looking back during a live game; replay of finished games
### After the mobile release ### After the mobile release
- [ ] Turn off `LEGACY_USERKEY_AUTH` once old app versions are no longer in use; this also removes `/register` - [ ] Turn off `LEGACY_USERKEY_AUTH` once old app versions are no longer in use; this also removes `/register`
@ -25,10 +32,18 @@ The web app logs in with Keycloak; the mobile app still uses the legacy `?userKe
### Later ### Later
- [ ] Let guests turn their guest user into an account without changing identity (Keycloak required action) - [ ] Let guests turn their guest user into an account without changing identity (Keycloak required action)
- [ ] Stats and game history for logged in users (needs a database) - [ ] Statistics for logged in users (wins, losses), now that games are saved
- [ ] Social logins (Keycloak identity providers) - [ ] Social logins (Keycloak identity providers)
- [ ] Restrict the Keycloak admin console in production - [ ] Restrict the Keycloak admin console in production
## Game history
- [ ] A page with all of a user's games (the home page shows the last 3)
- [ ] Show games of deleted accounts differently, once accounts can be deleted
- [ ] Live game state, previews and spectators assume a single backend instance; share them (e.g. Valkey pub/sub)
before running more than one
- [ ] If Valkey is unreachable when the backend starts, live games are not restored until the next restart
## Deployment ## Deployment
- [ ] Build the backend and frontend images from the repo root in CI: - [ ] Build the backend and frontend images from the repo root in CI:
@ -37,6 +52,9 @@ The web app logs in with Keycloak; the mobile app still uses the legacy `?userKe
- [ ] Deploy Keycloak (`apps/.env` with `KEYCLOAK_DB_PASSWORD`, `KEYCLOAK_ADMIN_PASSWORD`, - [ ] Deploy Keycloak (`apps/.env` with `KEYCLOAK_DB_PASSWORD`, `KEYCLOAK_ADMIN_PASSWORD`,
`KEYCLOAK_HOSTNAME=https://auth.playmancala.app`) and reload nginx with the new `auth.playmancala.app` block `KEYCLOAK_HOSTNAME=https://auth.playmancala.app`) and reload nginx with the new `auth.playmancala.app` block
- [ ] Deploy the backend before the web app; the new web app needs token auth on the server - [ ] Deploy the backend before the web app; the new web app needs token auth on the server
- [ ] Back up the Postgres volume (Keycloak and saved games), e.g. a daily `pg_dumpall`
- [ ] Set `MANCALA_DB_PASSWORD` and `POSTGRES_PASSWORD` in `apps/.env` on the server; the `postgres` volume replaces
`keycloak-db`, so Keycloak starts with a fresh database there
## Security ## Security
@ -48,16 +66,10 @@ The web app logs in with Keycloak; the mobile app still uses the legacy `?userKe
## Bugs ## Bugs
- [ ] `GameStoreImpl.getSpeactatingGameIdsByUser` loops with `for...in` over an iterator, so it never finds
anything and spectators are never removed on disconnect; use `for...of`
- [ ] Finished and abandoned games are never removed from `gameStore` (`deleteGame` only removes the user
mappings), so they stay in memory until restart
- [ ] `PitAnimator.animatePit`: `previousPitIndex > 0` should be `>= 0`; a one-stone move from pit 0 does not - [ ] `PitAnimator.animatePit`: `previousPitIndex > 0` should be `>= 0`; a one-stone move from pit 0 does not
empty that pit during the animation empty that pit during the animation
- [ ] `PitAnimator.dispose` removes a newly bound function, so the theme listener is never removed - [ ] `PitAnimator.dispose` removes a newly bound function, so the theme listener is never removed
- [ ] Sowing puts stones into the opponent's store; check against the intended Mangala rules and add a test - [ ] Sowing puts stones into the opponent's store; check against the intended Mangala rules and add a test
- [ ] `MatchMaker.join` does not check whether the player is already in a game
- [ ] `GameManager.startGame` sends the connection info twice
- [ ] `onGameError` broadcasts an `Error`, which serializes to `{}` - [ ] `onGameError` broadcasts an `Error`, which serializes to `{}`
## Maintenance ## Maintenance
@ -65,7 +77,7 @@ The web app logs in with Keycloak; the mobile app still uses the legacy `?userKe
- [ ] The web and mobile apps duplicate components and stores; move shared code into `packages/core` - [ ] The web and mobile apps duplicate components and stores; move shared code into `packages/core`
- [ ] Mixed lockfiles: `packages/*` and `apps/mobile` have both `yarn.lock` and `package-lock.json` - [ ] Mixed lockfiles: `packages/*` and `apps/mobile` have both `yarn.lock` and `package-lock.json`
- [ ] Align TypeScript and Jest versions across packages - [ ] Align TypeScript and Jest versions across packages
- [ ] Tests for `GameManager`, `MatchMaker` and `PitAnimator` - [ ] Tests for `MatchMaker` and `PitAnimator`
- [ ] CI that runs the tests - [ ] CI that runs the tests
- [ ] Pre-existing type errors: 4 in the frontend, 11 in mobile; the frontend has no `tsconfig.json`, so nothing - [ ] Pre-existing type errors: 4 in the frontend, 11 in mobile; the frontend has no `tsconfig.json`, so nothing
type-checks it type-checks it

View File

@ -47,3 +47,25 @@ User ids of Keycloak users are the token subject prefixed with `kc:`.
| `KEYCLOAK_JWKS_URL` | issuer + `/protocol/openid-connect/certs` | Where to fetch signing keys, if the backend reaches Keycloak at a different address | | `KEYCLOAK_JWKS_URL` | issuer + `/protocol/openid-connect/certs` | Where to fetch signing keys, if the backend reaches Keycloak at a different address |
| `KEYCLOAK_CLIENT_IDS` | `mancala-web` | Comma-separated clients whose tokens are accepted | | `KEYCLOAK_CLIENT_IDS` | `mancala-web` | Comma-separated clients whose tokens are accepted |
| `LEGACY_USERKEY_AUTH` | `true` | Also accept old clients that connect with `?userKey=<uuid>` and the `/register` endpoint. Turn off once no released client uses it, because a userKey is not a secret the server can check | | `LEGACY_USERKEY_AUTH` | `true` | Also accept old clients that connect with `?userKey=<uuid>` and the `/register` endpoint. Turn off once no released client uses it, because a userKey is not a secret the server can check |
## Saved games and live games
Finished games are saved in Postgres; live games are copied to Valkey after every change and restored when the
backend starts, so a restart or deploy does not end them. The design is in `docs/specs/game-history`.
| Variable | Default | Purpose |
|---|---|---|
| `DATABASE_URL` | `postgres://mancala:mancala@localhost:5433/mancala` | Saved games; migrations run on startup |
| `VALKEY_URL` | `redis://localhost:6379` | Live game state and short-lived caches |
The backend keeps running when either is down: games are still played, finished games are saved once Postgres is
back, and lists are served without the missing data.
| Route | Auth | Response |
|---|---|---|
| `GET /game/:id` | – | A live game, or `{ status: "finished", record }` |
| `GET /games/popular?limit=5` | – | `{ kind: "live" \| "finished", games }` |
| `GET /games/me/active` | Bearer token | `{ game }` or `{ game: null }` |
| `GET /games/me?limit=3` | Bearer token | `{ games }`, newest first |
Tests use Docker (testcontainers) for Postgres and Valkey.

View File

@ -1,6 +1,6 @@
# Game history and live games: requirements # Game history and live games: requirements
Status: approved, in development (branch `feature/game-history`, based on `feature/keycloak-auth`). Status: implemented on branch `feature/game-history` (based on `feature/keycloak-auth`).
Scope: backend and web app. The mobile app is out of scope (see `TODO.md`). Scope: backend and web app. The mobile app is out of scope (see `TODO.md`).
## Goals ## Goals

View File

@ -22,4 +22,4 @@ Work through the tasks in order; each ends with its tests passing and a commit.
rejection message. rejection message.
- [x] 10. **Live game history**: `MoveList`, review mode with `ReviewBanner` and keyboard, timeout countdown. - [x] 10. **Live game history**: `MoveList`, review mode with `ReviewBanner` and keyboard, timeout countdown.
- [x] 11. **Replay**: `ReplayView` with controls, speed, timeline, `?move=`, own-side orientation, duration. - [x] 11. **Replay**: `ReplayView` with controls, speed, timeline, `?move=`, own-side orientation, duration.
- [ ] 12. **End to end and docs**: Chrome tests from `design.md`, README and `TODO.md` updates. - [x] 12. **End to end and docs**: Chrome tests from `design.md`, README and `TODO.md` updates.