Public Access
114 lines
4.8 KiB
Markdown
114 lines
4.8 KiB
Markdown
# 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.
|
||
|
||
```bash
|
||
./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
|
||
|
||
```bash
|
||
./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_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.
|