Public Access
feat(library): add stats page data and pile trend
Log every status change with a trigger, so no code path can miss it. Store IGDB genres with the moods. Write a pile snapshot per user each night. The sync moves an untouched pile game with new playtime to PLAYING in one guarded update, so a status the user picks wins. Old finished games get their year from the ending achievement. GET /api/library/stats serves the groups; the public profile shows only the groups in the profile ticket.
This commit is contained in:
1 parent
b959542e23
commit
0fa2a37f47
48 files changed
+2069
-60
No files matched your search
@@ -43,6 +43,15 @@ To Be Played is split into three repositories:
|
||||
picker, Pile of Shame, activity, achievement summary, public profile and landing page stats. The sync still
|
||||
updates its playtime, but no achievements, time to beat, moods or speedrun data are fetched for it. The library
|
||||
lists hidden games with `hidden=true`.
|
||||
- **Stats**: `UserStatsService` builds the numbers of the stats page per group, so every page asks only for the
|
||||
groups it shows: totals and statuses; the pile (time to beat, weeks "at your pace" of the last 90 days, years of
|
||||
40-hour weeks, oldest unplayed game); most played and most loyal game (most months with playtime); top IGDB genres
|
||||
and moods; moon trips (Apollo 11, 195.3 h), longest day, share under one hour; finished games per year (from the
|
||||
status history); the Pile of Shame trend (90 days of `backlog_snapshot`, plus "this month: added / done", where the
|
||||
first sync of a Steam account is no "added", see `user_steam_sync.history_since`). Every number counts only listed games.
|
||||
- **Status history**: a database trigger logs every status change of a library game in `user_game_status_change`
|
||||
(old and new status, time, whether the user chose it), whatever code path made it. It starts with V14; older
|
||||
changes are not known.
|
||||
- **Wishlist**: synced from Steam with store price and discount.
|
||||
- **Broken image repair**: the frontend reports a Steam cover or achievement icon that does not load. At 01:30
|
||||
(Europe/Berlin) a job checks up to 200 reports. A cover that loads again is left alone; else the store's hashed
|
||||
@@ -53,7 +62,8 @@ To Be Played is split into three repositories:
|
||||
feature is off and the rest works.
|
||||
- **Public profile data**: totals, status counts, games in progress and finished games with rating, perfect games
|
||||
and average completion. Readable only
|
||||
with a profile ticket issued by Auth. Never returns notes, priority or the Steam ID.
|
||||
with a profile ticket issued by Auth. Never returns notes, priority or the Steam ID. `stats` holds only the stat
|
||||
groups the user shares (the ticket's `profileStats` claim, set in Auth); none by default.
|
||||
|
||||
## Authentication
|
||||
|
||||
@@ -78,6 +88,7 @@ accepted only by `GET /api/public/profiles/library` in the `X-Profile-Ticket` he
|
||||
| `POST` | `/api/library/sync` | JWT |
|
||||
| `POST` | `/api/library/broken-images` (`{appid, kind}`, kind `COVER` or `ACHIEVEMENT_ICONS`; `204`) | JWT |
|
||||
| `GET` | `/api/library/picker?maxMinutes=&mood=&count=&exclude=` | JWT |
|
||||
| `GET` | `/api/library/stats?groups=` (`OVERVIEW`, `PILE`, `TOP_GAMES`, `TASTE`, `HABITS`, `FINISHED`, `TREND`; none = all) | JWT |
|
||||
| `GET` | `/api/library/achievements/summary` | JWT |
|
||||
| `GET` | `/api/library/sync-status`, `/summary`, `/activity`, `/wishlist`, `/steam-profile`, `/games-count` | JWT |
|
||||
| `GET` | `/api/public/profiles/library` | profile ticket |
|
||||
@@ -152,12 +163,16 @@ Inside a module: `api` (controllers, DTOs), `service` (business logic), `domain`
|
||||
`POST /api/library/sync` starts one at once, but at most every 60 seconds (else `429` with `Retry-After`).
|
||||
2. A nightly job (23:55, Europe/Berlin) syncs every account used in the last 14 days, one after the other. A
|
||||
request without a `steamId` claim (the user unlinked the Steam account in Auth) takes the user out of it.
|
||||
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.
|
||||
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. When Steam returns no games, the library stays as it is.
|
||||
5. `TimeToBeatService` and `MoodService` ask IGDB for missing or old time-to-beat and mood data, for Steam games
|
||||
and manual games. Hidden games only get their playtime updated.
|
||||
owns. A BACKLOG game with new playtime becomes PLAYING, unless the user chose its status. When Steam returns no
|
||||
games, the library stays as it is.
|
||||
5. `TimeToBeatService` and `MoodService` ask IGDB for missing or old time-to-beat, mood and genre data, for Steam
|
||||
games and manual games. Hidden games only get their playtime updated.
|
||||
6. After the commit, `LibrarySyncedEvent` starts `AchievementSyncService` and `SpeedrunService` (backlog games only)
|
||||
in the background. The achievement sync also fetches Steam's global unlock rates with each schema, and sets
|
||||
FINISHED for a game whose ending achievement is unlocked while the user never chose its status.
|
||||
|
||||
Reference in new issue
Block a user