4.8 KiB
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. - 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 |
POST |
/api/library/sync |
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
- "What to play next" picker: time budget and mood on top of the current random suggestion.
- Achievement summary per game and library-wide, "closest to 100 %".
- Sale alerts for wishlist games.
- Move
steam_adapterDTOs out ofapi, 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.