using FozFc.Api.Configuration; using FozFc.Api.Domain; using FozFc.Api.Infrastructure.Mongo; using FozFc.Api.Infrastructure.Security; using MongoDB.Bson; namespace FozFc.Api.Application.Seeding; /// /// Seeds baseline data on startup: an admin account and the singleton /// club-profile / membership-page documents (idempotent). /// public sealed class DatabaseSeeder { private readonly IDocumentRepository _repository; private readonly IPasswordHasher _passwordHasher; private readonly AppOptions _options; private readonly ILogger _logger; public DatabaseSeeder( IDocumentRepository repository, IPasswordHasher passwordHasher, AppOptions options, ILogger logger) { _repository = repository; _passwordHasher = passwordHasher; _options = options; _logger = logger; } private static string IsoNow() => DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ"); public async Task SeedAsync(CancellationToken ct = default) { await SeedAdminAsync(ct); await SeedSingletonAsync("club_profile", "main", ClubProfile, ct); await SeedSingletonAsync("membership_page", "main", MembershipPage, ct); } private async Task SeedAdminAsync(CancellationToken ct) { var email = _options.SeedAdminEmail.ToLowerInvariant(); if (await _repository.FindOneByFieldAsync(Collections.Users, "email", email, ct) is not null) return; var uid = DocumentRepository.NewId(); var rank = FanRank.Compute(0); var admin = new BsonDocument { { "_id", uid }, { "uid", uid }, { "email", email }, { "displayName", "Administrador FC Foz" }, { "photoURL", "" }, { "role", "admin" }, { "provider", "password" }, { "passwordHash", _passwordHasher.Hash(_options.SeedAdminPassword) }, { "xp", 0 }, { "fanRank", rank.FanRank }, { "nextRank", rank.NextRank is null ? BsonNull.Value : rank.NextRank }, { "xpProgressPercent", rank.XpProgressPercent }, { "xpToNextRank", rank.XpToNextRank }, { "totalSpentEUR", 0 }, { "membershipMonths", 0 }, { "createdAt", IsoNow() }, }; await _repository.InsertAsync(Collections.Users, admin, ct); _logger.LogInformation("Seeded admin user {Email}.", email); } private async Task SeedSingletonAsync(string collection, string id, Func factory, CancellationToken ct) { if (await _repository.GetByIdAsync(collection, id, ct) is not null) return; var document = factory(); document["_id"] = id; document["createdAt"] = IsoNow(); await _repository.InsertAsync(collection, document, ct); _logger.LogInformation("Seeded {Collection}/{Id}.", collection, id); } private static BsonDocument ClubProfile() => new() { { "clubName", "Futebol Clube da Foz" }, { "foundedAt", "1934" }, { "email", "secretaria@foz.pt" }, { "phone", "+351915508561" }, { "address", "" }, { "schedule", new BsonArray() }, { "history", new BsonArray() }, { "mission", "" }, { "infrastructure", "" }, { "squads", new BsonArray() }, { "colors", new BsonArray() }, }; private static BsonDocument MembershipPage() => new() { { "benefits", new BsonArray() }, { "plans", new BsonArray() }, { "loginTitle", "Área de Sócio" }, { "loginDescription", "Inicie sessão para aceder ao seu cartão de sócio." }, }; }