diff --git a/readme.md b/readme.md
index b511af2..51b9687 100644
--- a/readme.md
+++ b/readme.md
@@ -28,6 +28,11 @@ To Be Played is split into three repositories:
Every game carries a `reason` (`source`, plus the anchor game for `SIMILAR` or `firstSeenAt` for `LONGEST`).
Games already shown can be excluded for a reroll. Moods `STORY`, `COOP`,
`CHILL`, `ACTION`, `SCARY` come from IGDB genres, themes and game modes; `SPEEDRUN` from speedrun.com.
+- **Coming games**: a game with a release date after today (Berlin date) or Steam "coming soon", and no playtime or
+ achievement of any user, is not picked and not on the pile (live count, new snapshots, pile stats, time to beat
+ sum). It keeps its status and shows `coming` / `comingOn`. Date: IGDB `first_release_date`, else Steam's store
+ (`GetItems` with `include_release`), else unknown (= released). View `coming_game`, table `game_release`; a
+ year-only IGDB date stays a placeholder until the real date comes (coming rows refresh daily).
```mermaid
flowchart LR
@@ -239,7 +244,7 @@ flowchart LR
Igdb -->|"OAuth token"| Twitch["Twitch"]
Speedrun --> SpeedrunApi["speedrun.com API"]
LibRepo --> Db[("PostgreSQL tbp_core")]
- Stats -->|"listed_user_game, counted_user_game, game_mood, user_game_status_change, deleted_user, blocked_user"| Db
+ Stats -->|"listed_user_game, counted_user_game, game_mood, coming_game, user_game_status_change, deleted_user, blocked_user"| Db
Auth -->|"service token
/internal/accounts"| Account
Account -->|"per-user rows, deleted_user, blocked_user, account_name, user_seen_at"| Db
```
@@ -255,7 +260,7 @@ flowchart LR
| `speedrun_adapter` | speedrun.com client: game search by name, verified runs |
**One shared schema.** All modules use one database `tbp_core`, and some read or write another module's tables in
-SQL, which ArchUnit cannot see: `stats` reads `listed_user_game`, `counted_user_game`, `steam_game`, `game_mood`, `user_game_status_change`, `deleted_user` and `blocked_user`; `account`
+SQL, which ArchUnit cannot see: `stats` reads `listed_user_game`, `counted_user_game`, `steam_game`, `game_mood`, `coming_game`, `user_game_status_change`, `deleted_user` and `blocked_user`; `account`
deletes the per-user rows of the library tables (`AccountCleanupRepository`; a new library table with `user_id` goes
into its list) and writes `user_seen_at` and `last_synced_at` of `user_steam_sync` (`AccountStateRepository`); `library`
reads `deleted_user` and `blocked_user`. A module that moves to its own service takes these reads along as calls.
diff --git a/src/main/java/core_service/modules/igdb_adapter/service/IgdbApiService.java b/src/main/java/core_service/modules/igdb_adapter/service/IgdbApiService.java
index 144769c..ab7868f 100644
--- a/src/main/java/core_service/modules/igdb_adapter/service/IgdbApiService.java
+++ b/src/main/java/core_service/modules/igdb_adapter/service/IgdbApiService.java
@@ -14,6 +14,7 @@ import org.springframework.web.client.RestClient;
import java.time.Duration;
import java.time.Instant;
+import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -251,8 +252,8 @@ public class IgdbApiService {
Map> result = new HashMap<>();
for (int from = 0; from < appids.size(); from += BATCH) {
Map> steamByIgdb = steamAppidsByIgdbGame(appids.subList(from, Math.min(from + BATCH, appids.size())));
- yearsOf(steamByIgdb.keySet()).forEach((igdbId, year) -> steamByIgdb.get(igdbId)
- .forEach(appid -> result.computeIfAbsent(appid, key -> new TreeSet<>()).add(year)));
+ datesOf(steamByIgdb.keySet()).forEach((igdbId, date) -> steamByIgdb.get(igdbId)
+ .forEach(appid -> result.computeIfAbsent(appid, key -> new TreeSet<>()).add(date.getYear())));
}
return Optional.of(result);
} catch (Exception e) {
@@ -272,8 +273,8 @@ public class IgdbApiService {
List ids = List.copyOf(igdbIds);
Map> result = new HashMap<>();
for (int from = 0; from < ids.size(); from += BATCH) {
- yearsOf(ids.subList(from, Math.min(from + BATCH, ids.size())))
- .forEach((igdbId, year) -> result.put(igdbId, Set.of(year)));
+ datesOf(ids.subList(from, Math.min(from + BATCH, ids.size())))
+ .forEach((igdbId, date) -> result.put(igdbId, Set.of(date.getYear())));
}
return Optional.of(result);
} catch (Exception e) {
@@ -282,6 +283,49 @@ public class IgdbApiService {
}
}
+ /**
+ * The first release date per Steam appid (UTC); the earliest when an appid belongs to several IGDB games. An app
+ * IGDB has no date for is missing. Empty when the adapter is off or IGDB failed.
+ */
+ public Optional