using System.Text.Json; using LudosData.Api.Domain; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; namespace LudosData.Api.Data; public class SeedOptions { public const string SectionName = "Seed"; /// When false, migrations still run but no user or games are created. public bool Enabled { get; set; } = true; public string UserName { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; } /// /// Applies migrations and, on a genuinely empty database, creates the initial user /// and imports the 105 games recovered from the 2018 MySQL dump. /// /// The dump predates the multi-user work, so it has no users table and no per-game /// owner: every imported game is assigned to the seed user. /// public static class DbSeeder { public static async Task MigrateAndSeedAsync(IServiceProvider services, CancellationToken ct = default) { using var scope = services.CreateScope(); var sp = scope.ServiceProvider; var logger = sp.GetRequiredService().CreateLogger("DbSeeder"); var db = sp.GetRequiredService(); await db.Database.MigrateAsync(ct); var options = sp.GetRequiredService>().Value; if (!options.Enabled) { logger.LogInformation("Seeding disabled; skipping."); return; } if (await db.Users.AnyAsync(ct)) { logger.LogInformation("Database already has users; skipping seed."); return; } if (string.IsNullOrWhiteSpace(options.UserName) || string.IsNullOrWhiteSpace(options.Password)) { logger.LogWarning( "Seeding is enabled but Seed:UserName / Seed:Password are not set, so no initial user was " + "created and the {Count} games from the 2018 dump were not imported. Set SEED__USERNAME, " + "SEED__EMAIL and SEED__PASSWORD and restart, or register a user and import manually.", await CountSeedGamesAsync(ct)); return; } var userManager = sp.GetRequiredService>(); var user = new AppUser { UserName = options.UserName, Email = string.IsNullOrWhiteSpace(options.Email) ? $"{options.UserName}@localhost" : options.Email, EmailConfirmed = true, }; var created = await userManager.CreateAsync(user, options.Password); if (!created.Succeeded) { var errors = string.Join("; ", created.Errors.Select(e => e.Description)); logger.LogError("Could not create the seed user: {Errors}", errors); return; } var games = await LoadSeedGamesAsync(ct); foreach (var game in games) { game.OwnerId = user.Id; db.Games.Add(game); } await db.SaveChangesAsync(ct); logger.LogInformation( "Seeded user {UserName} with {Count} games from the 2018 dump.", user.UserName, games.Count); } private static async Task> LoadSeedGamesAsync(CancellationToken ct) { var path = Path.Combine(AppContext.BaseDirectory, "Data", "Seed", "games.json"); if (!File.Exists(path)) { return []; } await using var stream = File.OpenRead(path); var records = await JsonSerializer.DeserializeAsync>( stream, new JsonSerializerOptions(JsonSerializerDefaults.Web), ct) ?? []; return records.Select(r => new Game { Title = r.Title, System = r.System, Genre = r.Genre, Year = r.Year, Developer = r.Developer, Publisher = r.Publisher, Art = r.Art, Description = r.Description, Own = r.Own, Dumped = r.Dumped, Played = r.Played, Finished = r.Finished, }).ToList(); } private static async Task CountSeedGamesAsync(CancellationToken ct) => (await LoadSeedGamesAsync(ct)).Count; private sealed record SeedGame( string Title, string? System, string? Genre, string? Year, string? Developer, string? Publisher, string? Art, string? Description, bool Own, bool Dumped, bool Played, bool Finished); }