Files
ToBePlayed-Core/readme.md
T
lobo 86db6f7167 feat(library): picker for what to play next and a status-only update
GET /api/library/picker returns random backlog games for a time budget, HIGH priority first. PUT /games/{appid}/status changes only the status, so the picker does not need the game detail (and its Steam calls) to keep rating, notes and priority.
2026-09-25 09:29:15 +02:00

5.1 KiB
Raw Blame History

To Be Played – Core Service

Business service of To Be Played, a Steam backlog tracker. It syncs the user's Steam library, stores the backlog (status, rating, notes, priority), tracks playtime per day, syncs the wishlist and adds time-to-beat data from IGDB.

To Be Played is split into three repositories:

Repository Role
ToBePlayed-Auth Users, login, settings, signs JWTs
ToBePlayed-Core (this one) Steam library, backlog, wishlist, stats; verifies JWTs
ToBePlayed-Frontend React single-page app

Features

  • Steam sync: owned games, playtime and achievements through the Steam Web API. Manual sync plus a nightly sync. Resilience4j retries and rate-limits every Steam call; Caffeine caches game counts and player summaries.
  • 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: up to 10 random backlog games for a time budget (IGDB normal time to beat), HIGH priority first, then MEDIUM, then the rest. Games already shown can be excluded for a reroll.
  • Playtime history: one row per game and day, for the activity view and the "most played in the last two weeks" dashboard banner.
  • Wishlist: synced from Steam with store price and discount.
  • Time to beat: story, extras and completionist hours from the IGDB API. Optional: without credentials the feature is off and the rest works.
  • Public profile data: totals, status counts, games in progress and finished games with rating. Readable only with a profile ticket issued by Auth. Never returns notes, priority or the Steam ID.

Authentication

Core never issues tokens. It verifies Auth's EdDSA (Ed25519) JWTs with the public key only (JWT_PUBLIC_KEY) and reads userId and steamId from the claims. 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.

API

Method Path Auth
GET /api/library/games, /api/library/games/{appid} JWT
PUT /api/library/games/{appid}/backlog JWT
PUT /api/library/games/{appid}/status (status only, keeps rating, notes, priority) JWT
POST /api/library/sync JWT
GET /api/library/picker?maxMinutes=&count=&exclude= JWT
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

Tech stack

  • Java 25, Spring Boot 4.1 (Web MVC, Security, Data JPA, Validation, Cache with Caffeine), virtual threads
  • PostgreSQL 17, Flyway migrations
  • Resilience4j: retry and rate limiter for Steam, rate limiter for IGDB
  • JJWT with Ed25519 public key
  • Spring Boot Actuator + Micrometer (Prometheus)
  • Tests: JUnit 5, Testcontainers (PostgreSQL), ArchUnit
  • CI: Forgejo Actions, multi-stage Docker image

Architecture

A modular monolith. Each module under core_service.modules is a vertical slice that could move to its own service later:

Module Content
library Sync, backlog, playtime history, wishlist, public profile data
stats Public numbers for the landing page
steam_adapter Steam Web API client
igdb_adapter IGDB client (Twitch OAuth)

Inside a module: api (controllers, DTOs), service (business logic), domain (JPA entities) and repo (Spring Data repositories). ArchUnit enforces cycle freedom, controller-to-repository direction, naming and module isolation in src/test/java/core_service/architecture/ArchitectureTest.java.

Run locally

Needs Java 25 and Docker.

./mvnw spring-boot:run -Dspring-boot.run.profiles=dev

Spring Boot's Docker Compose support starts PostgreSQL from compose.yaml, Flyway applies the migrations, and the service listens on http://localhost:8090. Health: /actuator/health, metrics: /actuator/prometheus.

In the dev profile a request without an Authorization header gets a dummy user, so the API is usable without a running Auth service.

Configuration comes from environment variables:

Variable Purpose
JWT_PUBLIC_KEY Auth's Ed25519 public key, Base64 of the X.509 DER bytes
STEAM_API_KEY Steam Web API key
IGDB_CLIENT_ID, IGDB_CLIENT_SECRET Twitch app for IGDB (optional)
DB_PASSWORD Local PostgreSQL password

Tests

./mvnw clean verify

Runs unit, WebMvc, persistence and integration tests plus the ArchUnit rules. Needs Docker for Testcontainers.

Roadmap

  • Mood filter for the picker (needs genres or themes from IGDB).
  • Achievement summary per game and library-wide, "closest to 100 %".
  • Sale alerts for wishlist games.
  • Move steam_adapter DTOs out of api, then turn on strict module independence in ArchUnit.
  • Fetch the public key from a JWKS endpoint in Auth instead of an environment variable.

License

All rights reserved. The source is public for reading only.