Public Access
feat(library): show play per platform on game detail
Store PSN play per title. A PS title on a Steam row keeps its minutes. Steam rtime_last_played now fills last played. Dropped? questions read the newest PSN last played too.
This commit is contained in:
1 parent
413381d404
commit
1ff1e061c7
30 files changed
+707
-33
No files matched your search
@@ -218,7 +218,7 @@ ArchUnit role rules (`RoleAnnotationRules`) and `src/test/resources/contract/aut
|
||||
| `POST` | `/api/rewards/purchases` (`{item}`; `200` wallet, `409 ALREADY_OWNED` / `NOT_ENOUGH_POINTS` / `NOT_FOR_SALE`) | JWT |
|
||||
| `GET` | `/api/rewards/progress/{track}/games?page=`, `/api/rewards/history/{kind}/{ref}/games?page=` (the games behind a track or a `MONTH` / `STREAK` bonus; `400 NOT_A_GAME_LIST`) | JWT |
|
||||
| `PUT` | `/api/rewards/equipped/{slot}` (`{item}`, null = take off; `200` wallet, `409 NOT_OWNED`, `400 WRONG_SLOT`) | JWT |
|
||||
| `GET` | `/api/library/games` (one row per set, the counted edition, with `platforms[]` of all its editions: Steam, PlayStation, then manual; `name` matches any edition; `source=STEAM,PSN` keeps rows with a platform of that source, a PlayStation title on a Steam row counts as `PSN`; ignored with `hidden=true`, which lists every hidden row; `400` on an unknown source; an old `hideDuplicates` param is ignored), `/api/library/games/{appid}` | JWT |
|
||||
| `GET` | `/api/library/games` (one row per set, the counted edition, with `platforms[]` of all its editions: Steam, PlayStation, then manual, and `totalPlaytimeMinutes` (all platforms; `playtimeMinutes` stays the counted edition's own); `name` matches any edition; `source=STEAM,PSN` keeps rows with a platform of that source, a PlayStation title on a Steam row counts as `PSN`; ignored with `hidden=true`, which lists every hidden row; `400` on an unknown source; an old `hideDuplicates` param is ignored), `/api/library/games/{appid}` | JWT |
|
||||
| `PATCH` | `/api/library/games/{appid}/backlog` (only the sent fields; `null` clears one) | JWT |
|
||||
| `PUT`, `DELETE` | `/api/library/games/{appid}/corrections/{field}` (`{value}`; `name`, `image`, `speedrun`, `moods`, `endless`, `genres`, `time_to_beat` = `{normallyMinutes, completelyMinutes}`; `204`, `400 INVALID_CORRECTION`) | JWT, AUDITOR |
|
||||
| `GET` | `/api/library/catalog?q=&page=&size=` (every game some user still has, by name part, `PageDto` of `{appid, name, imageUrl, platforms}`; `q` at most 100 characters; size 20, 40 or 80) | JWT, AUDITOR |
|
||||
@@ -244,7 +244,7 @@ ArchUnit role rules (`RoleAnnotationRules`) and `src/test/resources/contract/aut
|
||||
| `GET`, `DELETE` | `/api/library/psn-account` (`{enabled, linked, linkedAt, expiresAt, reviewImportId}`: the PSN import waiting for review, else null), unlink (`204`, deletes the stored token) | JWT |
|
||||
| `GET`, `DELETE` | `/api/library/imports/{id}/undo-check` (`{games, changed}`), `/api/library/imports/{id}/games` (undo; `204`, `409 IMPORT_NOT_SAVED`) | JWT |
|
||||
| `POST` | `/api/library/sync` | JWT |
|
||||
| `GET` | `/api/library/games/{appid}` (detail; slice 3: `achievements[]` also hold trophies with `kind` `STEAM`\|`PSN`, `setId`, `trophyType`, `group`, `rarity`; new `achievementSets[]` = `{id, kind, platform, name, total, unlocked, perfect, visible, listed}`, Steam first, then PS5 before PS4; `hasAchievements` = any set; the top-level counts stay Steam only) | JWT |
|
||||
| `GET` | `/api/library/games/{appid}` (detail; slice 3: `achievements[]` also hold trophies with `kind` `STEAM`\|`PSN`, `setId`, `trophyType`, `group`, `rarity`; new `achievementSets[]` = `{id, kind, platform, name, total, unlocked, perfect, visible, listed}`, Steam first, then PS5 before PS4; `hasAchievements` = any set; the top-level counts stay Steam only; `platforms[]` = `{source, platform, appid, playtimeMinutes, lastPlayedAt, achievementsUnlocked, achievementsTotal, perfect}` one line per platform of the entry, Steam, PS5, PS4, manual, each with its own progress, `appid` = the edition whose detail lists them; `totalPlaytimeMinutes` = their sum) | JWT |
|
||||
| `POST` | `/api/library/broken-images` (`{appid, kind}`, kind `COVER` or `ACHIEVEMENT_ICONS`; `204`) | JWT |
|
||||
| `GET` | `/api/library/backlog?limit=&released=` (`{games}` in the user's order; `limit` 1-20, none = all) | JWT |
|
||||
| `PUT` | `/api/library/backlog` (`{appids}` = the whole order; `204`) | JWT |
|
||||
@@ -385,7 +385,8 @@ Inside a module: `api` (controllers, DTOs), `service` (business logic), `domain`
|
||||
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
|
||||
no old playtime, and one game gets at most 24 hours per day. A BACKLOG game with new playtime becomes PLAYING, unless the user chose its status. When Steam returns no
|
||||
no old playtime, and one game gets at most 24 hours per day. Steam's `rtime_last_played` (unix seconds, 0 = never)
|
||||
is stored in `user_game.last_played_at`; a missing or zero value keeps the old one. 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.
|
||||
@@ -394,7 +395,8 @@ Inside a module: `api` (controllers, DTOs), `service` (business logic), `domain`
|
||||
FINISHED for a game whose ending achievement is unlocked while the user never chose its status.
|
||||
|
||||
The PSN step (`PsnSyncStep`, then `PsnTrophyStep`, see "PlayStation link") runs after the Steam fetch and before
|
||||
the transaction; its `ADDED`, `PLAYTIME` and trophy `ACHIEVEMENTS` changes join the same `sync_result`.
|
||||
the transaction; its `ADDED`, `PLAYTIME` and trophy `ACHIEVEMENTS` changes join the same `sync_result`. It also saves
|
||||
the play of every title (`PsnTitleRepository.savePlays`, "save title play" in the diagram below).
|
||||
|
||||
The frontend polls `GET /api/library/sync-status` until the run ends.
|
||||
|
||||
@@ -412,6 +414,7 @@ sequenceDiagram
|
||||
participant F as FollowDigestService
|
||||
participant D as Db
|
||||
W->>P: run (titles, trophy summaries, changed lists)
|
||||
P->>D: save title play (user_psn_title)
|
||||
P-->>W: ADDED, PLAYTIME, ACHIEVEMENTS (trophy gains)
|
||||
W->>D: sync_result, sync_change (ADDED, STATUS, WISHLIST, PSN changes)
|
||||
W-->>A: LibrarySyncedEvent
|
||||
@@ -560,12 +563,22 @@ IGDB id; remakes (8) keep their own id. `null` when IGDB does not know the game.
|
||||
drill-downs. There is no "Hide duplicates" filter any more; a hidden game is its own row under `hidden=true`. The
|
||||
friends row of the picker matches a friend's game to the caller's entry through `game_link`. Kept per edition on
|
||||
purpose: the hidden list, the platform stat, the average achievement completion (per edition; the tile's game counts are per entry), `sumPlaytime` and the
|
||||
community stats of the landing page. Card playtime is the counted edition's own; the set total comes with the
|
||||
detail page. Pile Points pay a set once (see "Pile Points"), and the
|
||||
community stats of the landing page. The card playtime is the set total (`entryPlaytimes`, see "Detail per platform"). Pile Points pay a set once (see "Pile Points"), and the
|
||||
`MONTH` bonus reads `backlog_snapshot`, which counts a set once.
|
||||
- **Detail per platform** (V44): the PSN step stores platform, minutes and last played per title in `user_psn_title`
|
||||
(`LINKED` and `STEAM_OWNED`, every sync, whatever row the title is on); the view `psn_title_play` resolves a merged
|
||||
key through `game_alias`. `PlatformPlay.lines` is the one rule for the detail lines: a Steam or manual row is one
|
||||
line, a PlayStation row is its titles (or one line from the row before the first sync after V44), a manual row is
|
||||
replaced by the titles of its own platform, and every other title linked to a Steam or manual row adds its
|
||||
PlayStation line. The total is their sum; the card total
|
||||
(`GameLinkRepository.entryPlaytimes`) follows the same rule in SQL, a test compares both. Each line carries its own
|
||||
achievement or trophy progress; the full list stays on the edition's own detail page. Still per row, not per
|
||||
platform: the playtime history (PSN minutes on a Steam row are not in it), the stats sums (most played, total
|
||||
playtime, moon trips), the playtime sort, and rewards (Steam only). Until a user's next sync after V44, PSN-on-Steam
|
||||
lines are missing and Steam's last played is empty.
|
||||
- The game detail has `linked` and `editions` (the user's other listed games of the set); a game row has `editions`
|
||||
(size of the set in the user's library, `null` below 2) and `platforms` (`source` + `platform` per edition, for
|
||||
the icons of the card).
|
||||
(size of the set in the user's library, `null` below 2), `platforms` (`source` + `platform` per edition, for
|
||||
the icons of the card) and `totalPlaytimeMinutes`.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -577,7 +590,9 @@ flowchart LR
|
||||
Counted --> Member["user_game_entry_member"]
|
||||
Member --> Platform["user_game_entry_platform"]
|
||||
Platform -->|"platforms[]"| Card["library card, lists"]
|
||||
Member -->|"summed playtime"| Card
|
||||
Member -->|"totalPlaytimeMinutes"| Card
|
||||
Title["user_psn_title play (V44)"] -->|"psn_title_play"| Member
|
||||
Title -->|"platforms[] of the detail"| Detail["game detail"]
|
||||
Set -->|"join: align_user_game_set"| Row["user_game (status, rating, notes, priority)"]
|
||||
Row -->|"update: user_game_set_share"| Row
|
||||
Row -.->|"old values"| Backup["user_game_set_backup"]
|
||||
@@ -694,6 +709,8 @@ project ships it; all values in `app.psn.*`). `PsnAccountService` in `library` h
|
||||
spelling or platform that the user owns is only linked. Everything else goes to the review: a READY `PSN` import with
|
||||
the matched rows is created, or the rows are appended to the one that waits (`reviewImportId`). The review page is
|
||||
the import page; saving a PSN import links the checked lines and dismisses the unchecked ones for good.
|
||||
- Every `LINKED` or `STEAM_OWNED` title also keeps its own platform, minutes and last played in `user_psn_title` (V44),
|
||||
so the detail shows PlayStation play next to Steam's on a shared row. The Steam row's own playtime stays Steam's.
|
||||
- A `LINKED` title updates its row (`source = PSN`, not removed): playtime, `last_played_at`, `PLAYTIME` changes and the
|
||||
playtime history; `playtime_2weeks` is the sum of the history of the last 14 days. A game the user deleted stays
|
||||
`LINKED` and is not added again. A game also owned on Steam stays one row (the Steam row wins).
|
||||
|
||||
Reference in new issue
Block a user