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." },
};
}