Public Access
docs(readme): add the architecture overview
This commit is contained in:
1 parent
7663e09a8c
commit
b350de1c79
1 file changed
+67
-5
@@ -71,18 +71,80 @@ accepted only by `GET /api/public/profiles/library` in the `X-Profile-Ticket` he
|
||||
## Architecture
|
||||
|
||||
A modular monolith. Each module under `core_service.modules` is a vertical slice that could move to its own
|
||||
service later:
|
||||
service later.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Browser["Frontend in the browser"]
|
||||
subgraph CoreService["ToBePlayed-Core"]
|
||||
Security["configuration.security<br/>JwtAuthenticationFilter"]
|
||||
subgraph Library["library"]
|
||||
LibApi["api"] --> LibService["service"] --> LibRepo["repo"]
|
||||
end
|
||||
Stats["stats"]
|
||||
Steam["steam_adapter"]
|
||||
Igdb["igdb_adapter"]
|
||||
LibService --> Steam
|
||||
LibService --> Igdb
|
||||
end
|
||||
Browser -->|"tbp_access cookie"| Security
|
||||
Security -->|"JWKS by kid"| Auth["ToBePlayed-Auth"]
|
||||
Steam --> SteamApi["Steam Web API"]
|
||||
Igdb --> IgdbApi["IGDB API"]
|
||||
Igdb -->|"OAuth token"| Twitch["Twitch"]
|
||||
LibRepo --> Db[("PostgreSQL tbp_core")]
|
||||
Stats --> Db
|
||||
```
|
||||
|
||||
| Module | Content |
|
||||
|---|---|
|
||||
| `library` | Sync, backlog, playtime history, wishlist, public profile data |
|
||||
| `library` | Sync, backlog, manual games, playtime history, wishlist, achievements, public profile data |
|
||||
| `stats` | Public numbers for the landing page |
|
||||
| `steam_adapter` | Steam Web API client |
|
||||
| `igdb_adapter` | IGDB client (Twitch OAuth) |
|
||||
| `igdb_adapter` | IGDB client (Twitch OAuth): time to beat, game tags for moods, game search |
|
||||
|
||||
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`.
|
||||
(repositories). `src/test/java/core_service/architecture/ArchitectureTest.java` checks these rules on every build:
|
||||
|
||||
- Modules do not depend on each other. Only `library` may use `steam_adapter` and `igdb_adapter`.
|
||||
- No cycles between `modules`, `shared` and `configuration`.
|
||||
- `api` never uses `repo`, and `repo` never uses `api`.
|
||||
- Every class in `repo` ends with `Repository`.
|
||||
- `shared` never uses `modules`, and `modules` never use `configuration`.
|
||||
- A `*Service` class in a module's `service` package has `@Service`. A `*Controller` class in `api` has
|
||||
`@RestController`, `@ControllerAdvice` or `@RestControllerAdvice`.
|
||||
|
||||
### Sync flow
|
||||
|
||||
1. `GET /api/library/games`, `/games-count` and `/picker` call `LibrarySyncService.syncIfStale`. It starts a sync
|
||||
when the last one is older than 24 hours, older than 15 minutes after a failure, or was for another Steam ID.
|
||||
`POST /api/library/sync` starts one at once, but at most every 60 seconds (else `429` with `Retry-After`).
|
||||
2. A nightly job (23:55, Europe/Berlin) syncs every account used in the last 14 days, one after the other.
|
||||
3. `LibrarySyncWorker` runs in the background. `user_steam_sync` lets only one run per user start.
|
||||
4. The worker loads the wishlist and the owned games from Steam. It updates `steam_game` and `user_game`, adds
|
||||
the playtime growth of the day to `user_game_playtime_day`, and soft-deletes Steam games the user no longer
|
||||
owns. When Steam returns no games, the library stays as it is.
|
||||
5. `TimeToBeatService` and `MoodService` ask IGDB for missing or old time-to-beat and mood data, for Steam games
|
||||
and manual games.
|
||||
6. After the commit, `LibrarySyncedEvent` starts `AchievementSyncService` in the background.
|
||||
|
||||
The frontend polls `GET /api/library/sync-status` until the run ends.
|
||||
|
||||
### Manual games next to Steam games
|
||||
|
||||
A game the user adds by hand comes from IGDB (`ManualGameService`). Steam games and manual games share
|
||||
`steam_game` and `user_game` and one key, the column `appid` (see `GameKeys`):
|
||||
|
||||
- `appid > 0`: the Steam appid. A manual game gets it when IGDB knows the game on Steam.
|
||||
- `appid < 0`: minus the IGDB id, for a game that is not on Steam. Steam appids are always positive, so the two
|
||||
never meet.
|
||||
|
||||
`user_game.source` (`GameSource`) says who owns the row. The sync never changes or removes a `MANUAL` row, with
|
||||
one exception: when the user later owns the game on Steam under the same appid, the sync takes the row over: it becomes
|
||||
`STEAM`, and status, rating and notes stay. Only `MANUAL` rows can be edited (`PUT /api/library/games/{appid}/manual`)
|
||||
or deleted (`DELETE /api/library/games/{appid}`). Time to beat uses the Steam appid for positive keys and the IGDB
|
||||
id for negative keys. Search and add check both keys of a game. A game added under minus its IGDB id can still get
|
||||
a second row when IGDB learns its Steam appid later and the user owns it on Steam.
|
||||
|
||||
## Run locally
|
||||
|
||||
|
||||
Reference in new issue
Block a user