feat(picker): add FRIENDS row from followed users

Backlog games a followed user finished or played >= 5 h. One game per
friend; strongest signal first (finished, then playtime). Requires an
active follow of a public profile that shows SUMMARY or STATUS. Skips
DROPPED games, blocked and deleted users, hidden and removed games.
This commit is contained in:
lobo committed 2026-10-07 09:42:10 +02:00
1 parent 117ff61730
commit e87f40bba0
8 files changed
+481 -18

No files matched your search

+8 -4
View File
@@ -21,19 +21,23 @@ To Be Played is split into three repositories:
can be taken over; its own finish then changes nothing.
- **Backlog**: status (`BACKLOG`, `PLAYING`, `FINISHED`, `COMPLETED` = 100 %, `DROPPED`), rating 1–5, notes, priority,
filters and sorting. "Pile of Shame" summary with a random suggestion of what to play.
- **Picker**: six rows of up to 3 backlog games each, for a time budget (IGDB normal time to beat), in this order:
- **Picker**: seven rows of up to 3 backlog games each, for a time budget (IGDB normal time to beat), in this order:
`NEXT_UP` (the first games of the user's own backlog order), `SERIES` (next in series: for each of the 6 most played
IGDB series the user played, one backlog part, the parts released after the latest played part first), `SIMILAR`
("because you played": for each of the 3 most played games the first backlog game sharing its moods, no game twice),
`STUDIO` (for each of the 6 developers of the user's most played games, one backlog game with a credit of that
company, developer credits first; publishers are never anchors, so a big publisher adds at most one card),
`FRIENDS` (for each followed user with a public profile, one backlog game of the user that the friend finished or
played 5 h or more, strongest signal first; a finish and playtime need the `SUMMARY` or `STATUS` profile section (`FINISHED` alone lists only 12
games); `DROPPED`, hidden and removed games never count; the friend's current
display name, never an id),
`LONGEST` (oldest `firstSeenAt`) and `RANDOM` (the mood row: random, HIGH priority first, then MEDIUM, then the
rest). The mood filters only the `RANDOM` row; budget and exclude filters apply to every row. A row with no game is
left out. A game can show in more than one row. IGDB series (collections) and companies are stored per game
(`game_credit`, `game_credit_check`) and refreshed with the other IGDB data; a scheduled backfill
(`app.library.credit-backfill-*`) fetches them for existing games, 250 games per 30 s, so the two rows fill up over
time after a deploy. Every game carries a `reason` (`source`, plus the anchor game for `SIMILAR`, `seriesName` for
`SERIES`, `studioName` for `STUDIO` or `firstSeenAt` for `LONGEST`).
`SERIES`, `studioName` for `STUDIO`, `friendName`, `friendFinished` and `friendPlaytimeMinutes` for `FRIENDS` or `firstSeenAt` for `LONGEST`).
Games already shown can be excluded for a reroll. Moods `STORY`, `COOP`,
`CHILL`, `ACTION`, `SCARY` come from IGDB genres, themes and game modes; `SPEEDRUN` from speedrun.com.
- **Follow users (#27)**: a logged-in user follows people with a public profile by display name (`/api/follows`, at most
@@ -274,7 +278,7 @@ flowchart LR
Igdb --> IgdbApi["IGDB API"]
Igdb -->|"OAuth token"| Twitch["Twitch"]
Speedrun --> SpeedrunApi["speedrun.com API"]
LibRepo --> Db[("PostgreSQL tbp_core")]
LibRepo -->|"own tables; picker also follow, public_user, account_name, blocked_user, deleted_user"| Db[("PostgreSQL tbp_core")]
Stats -->|"listed_user_game, counted_user_game, game_mood, coming_game, user_game_status_change, deleted_user, blocked_user, listed_user, account_name, game_alias"| Db
Follow -->|"follow, follow_digest, inbox_note, public_user, sync_change, user_game_status_change, account_name"| Db
Inbox --> Db
@@ -301,7 +305,7 @@ flowchart LR
SQL, which ArchUnit cannot see: `stats` reads `listed_user`, `account_name`, `game_alias`, `listed_user_game`, `counted_user_game`, `steam_game`, `game_mood`, `coming_game`, `user_game_status_change`, `deleted_user` and `blocked_user`; `account`
deletes the per-user rows of the library tables (`AccountCleanupRepository`; a new library table with `user_id` goes
into its list) and writes `user_seen_at` and `last_synced_at` of `user_steam_sync` (`AccountStateRepository`); `library`
reads `deleted_user` and `blocked_user`. A module that moves to its own service takes these reads along as calls.
reads `deleted_user` and `blocked_user`, and for the picker `follow`, `public_user` and `account_name`. A module that moves to its own service takes these reads along as calls.
The listed rule ("still owned, not hidden") is `UserGameRepository.LISTED` for JPQL and the view
`listed_user_game` for native SQL; V24 says what a migration of `user_game` must do with the view. The numbers that
count games once per linked set read `counted_user_game` (V28), built on `listed_user_game`: a migration that changes