using LANCommander.Server.Data; using LANCommander.Server.Data.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace LANCommander.Server.Services { /// /// Idempotent startup routine that seeds the GameVersion table for games that predate /// first-class versioning. Each existing archive becomes its own version (ordered oldest /// to newest), and the game's current version-scoped config (Scripts, Actions, SavePaths) /// is attached to the newest version. Games with no archives receive a single empty /// version so their config still has a home. /// public static class GameVersionBackfill { public static async Task RunAsync(IDbContextFactory contextFactory, ILogger logger) { await using var context = await contextFactory.CreateDbContextAsync(); // Only consider games that don't already have versions, so this is safe to run on every startup. var gameIds = await context.Games .Where(g => !g.Versions.Any()) .Select(g => g.Id) .ToListAsync(); if (gameIds.Count == 0) { logger.LogDebug("GameVersion backfill: no games require backfilling"); return; } logger.LogInformation("GameVersion backfill: seeding versions for {GameCount} game(s)", gameIds.Count); foreach (var gameId in gameIds) { var game = await context.Games.FirstOrDefaultAsync(g => g.Id == gameId); if (game == null) continue; // Load version-scoped config from the dependent side (filtered by GameId) rather than // through Game's collection navigations. This is equivalent in relational providers and // avoids the EF InMemory provider's collection-navigation loading gap relied on by tests. var orderedArchives = await context.Set() .Where(a => a.GameId == gameId) .OrderBy(a => a.CreatedOn) .ToListAsync(); var scripts = await context.Set