Commit Graph
84 Commits
Author SHA1 Message Date
lobo 8eab98df74 feat(rewards): unlock items by progress and skins
Build, Test & Push Core Service / quality-check (push) Successful in 1m11s
Build, Test & Push Core Service / docker (push) Successful in 1m27s
Five progress tracks: games finished, completed, weekly finish
streaks, pile-down months and play-more months. Settle books UNLOCK
rows once, also retroactively, when a track reaches an item's target.
12 unlock-only items (incl. the Steam skin) and 3 skins to buy. The
wallet returns progress; buying an unlock item answers NOT_FOR_SALE.
2026-10-02 15:40:46 +02:00
lobo bb8341dfef feat(library): undo auditor merges for 30 days, redirect old keys
Build, Test & Push Core Service / quality-check (push) Successful in 1m14s
Build, Test & Push Core Service / docker (push) Successful in 1m36s
V29 adds game_merge_undo and game_merge_undo_user: every new merge saves
a copy of what it changed. POST /api/library/merges/{mergeId}/undo
(auditors, newest merge first) puts it back; users keep later status
and rating, nothing is paid twice. A nightly job drops copies after 30
days. Old keys follow game_alias to the kept game. Manual games can now
be hidden and shown like Steam games.
2026-10-02 13:39:02 +02:00
lobo 44af19d32e feat(library): auditor catalog search and time to beat correction
GET /api/library/catalog finds any game a user has, by name, for
auditors; GET /api/library/catalog/{appid} loads it for the correction
dialog. New correction field TIME_TO_BEAT (normal + completionist,
1..60000 min, completionist >= normal) that survives the IGDB refresh.
Every correction and reset writes GAME_CORRECT / GAME_CORRECTION_RESET
to admin_audit.
2026-10-02 11:21:15 +02:00
lobo a2ea1e45d3 feat(audit): add audit log endpoint and display names from Auth
Build, Test & Push Core Service / quality-check (push) Successful in 1m14s
Build, Test & Push Core Service / docker (push) Successful in 1m20s
GET /api/library/audit-log for auditors and admins, paged, filter by
action. The account state push stores displayName in account_name so
the log shows names. Contract test updated.
2026-10-02 09:10:56 +02:00
lobo 221b5594c5 feat(library): count linked editions once in pile and stats
All game counts read the counted_user_game view, which keeps only the
most advanced edition of a linked set (older edition wins a tie). Add
the hideDuplicates filter to the library query.
2026-10-02 09:10:56 +02:00
lobo e806670d26 feat(library): add IGDB editions, game links and auditor queue
Tables game_edition (IGDB "same game" id per library key), game_link
(global sets set by auditors), game_link_pair (queue of IGDB and
requested pairs), game_link_request and account_name. Fetch IGDB
editions, link and unlink with audit rows, serve the paged auditor queue
with request counts. WireMock recordings for the editions query.
2026-10-02 09:10:56 +02:00
lobo a12ed20705 feat(rewards): add completed, background and banner effect items
Build, Test & Push Core Service / quality-check (push) Successful in 1m4s
Build, Test & Push Core Service / docker (push) Successful in 1m31s
2026-10-01 15:09:25 +02:00
lobo 349fa40272 feat(rewards): shop, equipped looks and public looks
Build, Test & Push Core Service / quality-check (push) Successful in 1m5s
Build, Test & Push Core Service / docker (push) Successful in 1m35s
Add endpoints to buy items, equip looks by slot and unequip. The shop has
8 whitelisted looks: avatar borders, badges, accents and banner frames.
Ownership is a BUY ledger row; PLATFORM_ADMIN owns every catalog item
without buying. The public profile endpoint shares only the equipped looks
filtered by ownership.
2026-10-01 13:17:04 +02:00
lobo 837dad8a6f feat(rewards): ledger, earning rules and nightly settle
New module rewards tracks Pile Points in an append-only ledger. A settle
derives rows from the status log, playtime days and achievements, once per
user, kind and game. The balance is the sum of all rows. Settle runs on
every wallet read and nightly at 02:30. The unique key (user_id, kind, ref)
makes every run idempotent. V26 also pays a one-time starter grant for
finishes from before the status log.
2026-10-01 13:15:04 +02:00
lobo 732970697d feat(stats): add PLATFORMS stat group and landing page statistics
Add PLATFORMS stat group to user stats: games and playtime per platform.
Add platforms, genres, and finished-per-year statistics to public stats
for the landing page.
2026-10-01 11:10:59 +02:00
lobo 216c1af62f feat(import): source values for exports and bookmarklet
Build, Test & Push Core Service / quality-check (push) Successful in 1m9s
Build, Test & Push Core Service / docker (push) Successful in 1m33s
2026-09-30 15:59:05 +02:00
lobo e347b7aac8 chore(security)!: refuse tokens without aud 2026-09-30 15:00:47 +02:00
lobo 23bf5c41b4 chore(library)!: drop the full-replace PUT of a backlog entry
The Frontend sends PATCH since item 27 batch 2.
2026-09-30 15:00:47 +02:00
lobo 34d64e7215 refactor: item 29 cleanup
C14 C16 C17 C18 C20 A11 A15 X10 X12 X15: dead Steam count code, GameNames, steamIdText, one query per game for the finished estimate, no API versioning config, open-in-view off, CORS from APP_FRONTEND_URL, non-root image, prune only up to the highest known id.
2026-09-30 15:00:47 +02:00
lobo 504c935b27 feat(account): Steam ID and blocked state from Auth (V25)
A blocked user gets no sync and leaves the landing stats. A new or removed Steam ID stops the nightly sync of the old one. No token check in Core: the token lives until it expires.
2026-09-30 15:00:47 +02:00
lobo 3512159b41 test: background work inline, shared mock bases, lock-wait instead of sleep
Build, Test & Push Core Service / quality-check (push) Successful in 1m8s
Build, Test & Push Core Service / docker (push) Successful in 1m31s
2026-09-30 12:08:57 +02:00
lobo 7fe2823519 refactor(library): detail page refreshes, then assembles 2026-09-30 12:08:57 +02:00
lobo 2c97e2ae96 refactor(library): two bounded background pools
Every @Async names a pool: with the scheduler there are two TaskExecutor beans, and a bare @Async ran on an unbounded executor. Page views queue only stale games; the long runs after a sync get their own pool.
2026-09-30 12:08:57 +02:00
lobo 9a09d535fc refactor(library): listed_user_game view and one shared schema (V24) 2026-09-30 09:37:16 +02:00
lobo b499e79c41 refactor(library): one IGDB split and merge for time to beat and moods 2026-09-30 09:37:16 +02:00
lobo c1472c7d59 refactor(library): own IgdbGame record, one owned rule, layer rules
Stored import candidates keep their JSON shape, so READY imports from before still read.
2026-09-30 09:37:16 +02:00
lobo 1abc03d2c1 refactor(library): auditor fields only through applyCorrection 2026-09-30 09:37:16 +02:00
lobo 79d7bf17f2 feat(library): PATCH backlog saves only the sent fields 2026-09-30 07:36:08 +02:00
lobo 73cfcb58e2 refactor(api): one error body, token audiences, contract fixture
Build, Test & Push Core Service / quality-check (push) Successful in 1m7s
Build, Test & Push Core Service / docker (push) Successful in 1m23s
2026-09-29 14:48:28 +02:00
lobo 2360beeb81 fix(sync): short sync transactions, real Steam retries, no N+1 2026-09-29 13:27:31 +02:00
lobo 2a5fcc1c00 feat(import): import many games with background matching, review and undo 2026-09-29 10:20:54 +02:00
lobo 3bd1eda0ec docs(profile): note that the ticket claims come from the tile layout
Build, Test & Push Core Service / quality-check (push) Successful in 1m6s
Build, Test & Push Core Service / docker (push) Successful in 1m27s
2026-09-28 15:05:11 +02:00
lobo 07d287cd43 feat(profile): leave out hidden public profile sections 2026-09-28 14:20:14 +02:00
lobo 5c0c0c6a78 feat(dev): add the local profile with recorded third-party answers
Profile "local" points Steam, Twitch/IGDB and speedrun.com to a WireMock
container from compose.yaml (recordings in local/wiremock) and checks
logins against the local Auth.

The IGDB lookups now keep only the ids they asked for: an answer with
another game made tags and release years drop the whole batch.
2026-09-28 13:07:40 +02:00
lobo 5a089b2082 feat(library): let auditors merge duplicate games
Build, Test & Push Core Service / quality-check (push) Successful in 1m3s
Build, Test & Push Core Service / docker (push) Successful in 1m26s
An auditor merges a game added by hand into its Steam game or another
game added by hand (V21). Every user's row moves; a user with both rows
keeps one, with the status, rating and notes set by hand. game_alias
sends later adds to the kept key, and admin_audit logs who merged what.
The game search shows games already in ToBePlayed first.
2026-09-28 11:25:45 +02:00
lobo 3c2f40036d feat(library): let auditors correct game data
Build, Test & Push Core Service / quality-check (push) Successful in 1m0s
Build, Test & Push Core Service / docker (push) Successful in 1m25s
An AUDITOR corrects name, header image, speedrun link, moods, endless and genres of a game for every user (V20 game_correction, PUT/DELETE /api/library/games/{appid}/corrections/{field}). The value is also written into the cached row, so reads stay unchanged; the Steam sync, the image repair, the IGDB add and the mood and speedrun upserts skip corrected fields. SteamGame gets @DynamicUpdate so a sync no longer writes back columns it did not change.
2026-09-28 09:31:39 +02:00
lobo 2596df609e feat(security): check role markers on services and read the role claim
Every service method a controller calls carries @RequiresRole, @Public or @Internal; a Spring Security method interceptor checks it while a request is served. Core now grants the active role from the token and every lower one. ArchUnit fails the build for an unmarked method, two markers, a controller calling an @Async method, or a service reaching a method that needs more. The background refreshes moved from LibraryController into the services, because an @Async method is never checked.
2026-09-28 08:50:10 +02:00
lobo 351cda411d 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.
2026-09-27 11:46:22 +02:00
lobo 88aae0101f feat(version): answer the build commit and time
Build, Test & Push Core Service / quality-check (push) Successful in 57s
Build, Test & Push Core Service / docker (push) Successful in 1m20s
GET /api/version is public. CI passes GIT_SHA and BUILD_TIME as Docker build args; a local build answers dev.
2026-09-27 10:27:01 +02:00
lobo 01552ecd89 fix(sync): keep old playtime off the day a game shows up
A game the sync saw for the first time put its whole playtime on that
day (40 h "longest gaming day" after skip_unvetted_apps=0). Growth now
counts at most Steam's last two weeks, and one game gets at most 24 h
per day. V18 drops the bad rows and caps old ones.
2026-09-27 10:00:53 +02:00
lobo 0fa2a37f47 feat(library): add stats page data and pile trend
Build, Test & Push Core Service / quality-check (push) Successful in 1m0s
Build, Test & Push Core Service / docker (push) Successful in 1m18s
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.
2026-09-27 07:16:05 +02:00
lobo b959542e23 feat(library): finish games from the ending achievement or suggest it
Build, Test & Push Core Service / quality-check (push) Successful in 58s
Build, Test & Push Core Service / docker (push) Successful in 1m23s
2026-09-26 19:00:57 +02:00
lobo c2d17d0bc7 feat(library): hide games from lists and stats
Build, Test & Push Core Service / quality-check (push) Successful in 58s
Build, Test & Push Core Service / docker (push) Successful in 1m25s
2026-09-26 12:27:58 +02:00
lobo 246e255058 feat(images): repair reported covers and achievement icons at night
Build, Test & Push Core Service / quality-check (push) Successful in 56s
Build, Test & Push Core Service / docker (push) Successful in 1m24s
2026-09-26 12:07:07 +02:00
lobo a389bdd0c7 fix(sync): stop the nightly sync when the Steam ID is gone
Build, Test & Push Core Service / quality-check (push) Successful in 49s
Build, Test & Push Core Service / docker (push) Successful in 1m20s
After an unlink in Auth, or when a verified link took the ID away, the
nightly job kept syncing the old Steam ID for up to 14 days. A
games-count request without a steamId claim now clears user_seen_at.
steam_id stays, so a later link with another ID still soft-deletes the
old games.
2026-09-26 11:20:04 +02:00
lobo 393cdddf06 feat(library): add a speedrun mood from speedrun.com
Build, Test & Push Core Service / quality-check (push) Successful in 1m4s
Build, Test & Push Core Service / docker (push) Successful in 1m23s
The picker takes mood=SPEEDRUN for backlog games with a speedrun.com
game that has at least one verified run. The game detail returns the
link as speedrunUrl.

speedrun.com has no Steam or IGDB link, so games are matched by name
and, when IGDB knows it, by release year (one year off at most). One
queue serves all users at 1 request per second. Results are kept 30
days in game_speedrun.
2026-09-26 10:24:22 +02:00
lobo 3224257325 fix(steam): sync owned apps Steam calls unvetted
Build, Test & Push Core Service / quality-check (push) Successful in 1m3s
Build, Test & Push Core Service / docker (push) Successful in 1m32s
GetOwnedGames leaves out "unvetted" apps unless skip_unvetted_apps=0
is set. So a released game owned through a developer license was
missing from the library.

Such an app can come without a name. The sync now keeps the stored
name, or uses "App <appid>" for a new game, so the NOT NULL name
column does not fail the whole sync.
2026-09-26 10:04:23 +02:00
lobo b350de1c79 docs(readme): add the architecture overview
Build, Test & Push Core Service / quality-check (push) Successful in 53s
Build, Test & Push Core Service / docker (push) Successful in 1m20s
2026-09-26 07:37:44 +02:00
lobo 7663e09a8c feat(library): filter the picker by mood
Moods come from IGDB genres, themes and game modes and are stored per game in game_mood (V9), refreshed like the time to beat. Picking a mood also refreshes the moods of the backlog in the background, so users without a Steam sync get them too.
2026-09-26 07:36:10 +02:00
lobo 5e7455db05 feat(library): weight the backlog suggestion by priority
Build, Test & Push Core Service / quality-check (push) Successful in 51s
Build, Test & Push Core Service / docker (push) Successful in 1m19s
A HIGH game is 4 times as likely as a LOW or unset one, a
MEDIUM game twice. Not HIGH first like the picker: the
dashboard shuffles by excluding only the current game, so a
strict order would swap between two games. The picker now
starts a stale Steam sync like the library does.
2026-09-26 07:05:57 +02:00
lobo 1cac7de836 feat(igdb): rank game search by popularity and help with typos
Build, Test & Push Core Service / quality-check (push) Successful in 49s
Build, Test & Push Core Service / docker (push) Successful in 1m36s
IGDB lists every e-Reader card as a game, and its own order
puts obscure entries first. Core now drops e-Reader-only
games, asks for 50 hits and sorts exact name first, then by
rating count. When a search finds fewer than 3 games, Core
searches again without doubled letters and with single
words, and adds those hits by Levenshtein distance after
IGDB's own hits.

Also fixes a flaky test that read the time to beat before
the background refresh wrote it.
2026-09-26 06:29:57 +02:00
lobo e1d837bc78 fix(igdb): search only games a user can own and play
Build, Test & Push Core Service / quality-check (push) Failing after 49s
Build, Test & Push Core Service / docker (push) Skipped
The search listed DLC and e-Reader levels, and the limit of
10 pushed the main games out. Keep main games, standalone
expansions, seasons, remakes, remasters, expanded games and
ports, and show 20 hits.
2026-09-25 15:24:05 +02:00
lobo 4d50863cb8 feat(library): add games by name from IGDB
Build, Test & Push Core Service / quality-check (push) Successful in 51s
Build, Test & Push Core Service / docker (push) Successful in 1m24s
Games the user owns outside Steam get the key minus their
IGDB id, or the Steam appid when IGDB knows the game on
Steam. The sync never removes a MANUAL row and takes it
over once the user owns the game on Steam. Users without
a Steam ID can use the library.
2026-09-25 14:37:21 +02:00
lobo 33e0a59664 docs(roadmap): add games by name from IGDB for other platforms 2026-09-25 13:45:13 +02:00
lobo 5d608b72e6 feat(security): accept the auth cookie and guard against CSRF
Core reads the login token from the tbp_access cookie or a Bearer
header, whichever verifies, and parses it once per request. Unsafe
requests need an X-TBP-Client or Authorization header, CORS allows
only the exact frontend origin, and a missing or expired token
answers 401 instead of 403.
2026-09-25 13:45:08 +02:00