feat(account): hide deleted accounts and clean up their rows
Build, Test & Push Core Service / quality-check (push) Successful in 1m2s
Build, Test & Push Core Service / docker (push) Successful in 1m20s

Auth marks a deleted user in deleted_user (no sync, not in landing stats or pile snapshots) and later purges the rows through /internal/accounts with a service token. Prune removes rows of user ids Auth does not know. Dry run by default.
This commit is contained in:
lobo committed 2026-09-27 11:46:22 +02:00
1 parent 88aae0101f
commit 351cda411d
28 files changed
+941 -16

No files matched your search

+19 -1
View File
@@ -75,6 +75,9 @@ until Core restarts. `JWT_PUBLIC_KEY` is used only until the first JWKS load wor
frontend origin.
A token with a `typ` claim (for example a profile ticket) is rejected on normal endpoints; the profile ticket is
accepted only by `GET /api/public/profiles/library` in the `X-Profile-Ticket` header.
`/internal/**` has its own filter chain: it accepts only a service token of Auth (`typ=service`, with `exp`, sent
as `Bearer`), never a login, also not in the dev profile. Traefik exposes the path, so the signed token is its
only guard.
## API
@@ -93,6 +96,8 @@ accepted only by `GET /api/public/profiles/library` in the `X-Profile-Ticket` he
| `GET` | `/api/library/sync-status`, `/summary`, `/activity`, `/wishlist`, `/steam-profile`, `/games-count` | JWT |
| `GET` | `/api/public/profiles/library` | profile ticket |
| `GET` | `/api/stats/public` | public |
| `POST`, `PUT` | `/internal/accounts/deleted` (`{userIds}`; POST adds, PUT replaces the whole set; `204`) | service token |
| `POST` | `/internal/accounts/purge` (`{userIds}`), `/internal/accounts/prune` (`{knownUserIds}`, not empty) | service token |
| `GET` | `/api/version` (`{commit, builtAt}` of the running image, from the CI build; `dev` locally) | public |
## Tech stack
@@ -120,6 +125,7 @@ flowchart LR
end
Stats["stats"]
Version["version"]
Account["account"]
Steam["steam_adapter"]
Igdb["igdb_adapter"]
Speedrun["speedrun_adapter"]
@@ -136,6 +142,8 @@ flowchart LR
Speedrun --> SpeedrunApi["speedrun.com API"]
LibRepo --> Db[("PostgreSQL tbp_core")]
Stats --> Db
Auth -->|"service token<br/>/internal/accounts"| Account
Account --> Db
```
| Module | Content |
@@ -143,6 +151,7 @@ flowchart LR
| `library` | Sync, backlog, manual games, playtime history, wishlist, achievements, public profile data |
| `stats` | Public numbers for the landing page |
| `version` | `GET /api/version`: commit and build time of the running image |
| `account` | Deleted accounts, driven by Auth: tombstones in `deleted_user`, purge and orphan prune of the per-user rows |
| `steam_adapter` | Steam Web API client, availability check of Steam CDN pictures |
| `igdb_adapter` | IGDB client (Twitch OAuth): time to beat, game tags for moods, release years, game search |
| `speedrun_adapter` | speedrun.com client: game search by name, verified runs |
@@ -169,7 +178,8 @@ Inside a module: `api` (controllers, DTOs), `service` (business logic), `domain`
Afterwards it writes every user's pile size of the day to `backlog_snapshot`, also for users without Steam, and
dates finished games by the unlock of their ending achievement
(`FinishedEstimateService`, `user_game.finished_at_estimate`).
3. `LibrarySyncWorker` runs in the background. `user_steam_sync` lets only one run per user start.
3. `LibrarySyncWorker` runs in the background. `user_steam_sync` lets only one run per user start, and none for
a user in `deleted_user`.
4. The worker loads the wishlist and the owned games from Steam. It updates `steam_game` and `user_game`, adds
the playtime growth of the day to `user_game_playtime_day`, and soft-deletes Steam games the user no longer
owns. The growth counts at most Steam's playtime of the last two weeks, so a game seen for the first time brings
@@ -186,6 +196,14 @@ The frontend polls `GET /api/library/sync-status` until the run ends.
At 01:30 `ImageRepairService` repairs the pictures reported through `POST /api/library/broken-images` (table
`broken_image`, one row per game and kind). Reports only count for Steam games the user owns.
### Deleted accounts
Auth drives it (see Auth's readme). `POST /internal/accounts/deleted` adds a tombstone in `deleted_user`: the user
gets no more syncs and leaves the landing stats and the pile snapshots at once. Auth's nightly job sends the whole
flagged set (`PUT`), then `purge` for users flagged 30 days ago (only tombstoned ones), then `prune` with all its
user ids, which removes the rows of users Auth does not know (refused for more than
`app.account-cleanup.max-orphans`, default 5). `app.account-cleanup.dry-run` (default `true`) only counts the rows.
### Manual games next to Steam games
A game the user adds by hand comes from IGDB (`ManualGameService`). Steam games and manual games share