line-gestao-api/Data/SeedData.cs

262 lines
10 KiB
C#

using line_gestao_api.Models;
using line_gestao_api.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace line_gestao_api.Data;
public class SeedOptions
{
public bool Enabled { get; set; } = true;
public string AdminMasterName { get; set; } = "System Admin";
public string? AdminMasterEmail { get; set; } = "sysadmin@linegestao.local";
public string? AdminMasterPassword { get; set; } = "DevSysAdmin123!";
public bool ReapplyAdminCredentialsOnStartup { get; set; } = false;
}
public static class SeedData
{
public static async Task EnsureSeedDataAsync(IServiceProvider services)
{
using var scope = services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole<Guid>>>();
var tenantProvider = scope.ServiceProvider.GetRequiredService<ITenantProvider>();
var options = scope.ServiceProvider.GetRequiredService<IOptions<SeedOptions>>().Value;
if (db.Database.IsRelational())
{
await db.Database.MigrateAsync();
}
else
{
await db.Database.EnsureCreatedAsync();
}
if (!options.Enabled)
{
return;
}
var systemTenantId = SystemTenantConstants.SystemTenantId;
var roles = AppRoles.All;
foreach (var role in roles)
{
if (!await roleManager.RoleExistsAsync(role))
{
var roleResult = await roleManager.CreateAsync(new IdentityRole<Guid>(role));
EnsureIdentitySucceeded(roleResult, $"Falha ao criar role '{role}'.");
}
}
await MigrateLegacyRolesAsync(db, roleManager);
var systemTenant = await db.Tenants.FirstOrDefaultAsync(t => t.Id == systemTenantId);
if (systemTenant == null)
{
systemTenant = new Tenant
{
Id = systemTenantId,
NomeOficial = SystemTenantConstants.SystemTenantNomeOficial,
IsSystem = true,
Ativo = true,
CreatedAt = DateTime.UtcNow
};
db.Tenants.Add(systemTenant);
}
else
{
systemTenant.NomeOficial = SystemTenantConstants.SystemTenantNomeOficial;
systemTenant.IsSystem = true;
systemTenant.Ativo = true;
}
await db.SaveChangesAsync();
var emailFromEnv = Environment.GetEnvironmentVariable("SYSADMIN_EMAIL")
?? Environment.GetEnvironmentVariable("ADMIN_MASTER_EMAIL");
var passwordFromEnv = Environment.GetEnvironmentVariable("SYSADMIN_PASSWORD")
?? Environment.GetEnvironmentVariable("ADMIN_MASTER_PASSWORD");
var adminMasterEmail = (emailFromEnv ?? options.AdminMasterEmail ?? string.Empty).Trim().ToLowerInvariant();
var adminMasterPassword = passwordFromEnv ?? options.AdminMasterPassword ?? string.Empty;
if (string.IsNullOrWhiteSpace(adminMasterEmail) || string.IsNullOrWhiteSpace(adminMasterPassword))
{
throw new InvalidOperationException(
"Credenciais do sysadmin ausentes. Defina SYSADMIN_EMAIL e SYSADMIN_PASSWORD (ou Seed:AdminMasterEmail/Seed:AdminMasterPassword).");
}
var normalizedEmail = userManager.NormalizeEmail(adminMasterEmail);
var previousTenant = tenantProvider.TenantId;
tenantProvider.SetTenantId(systemTenantId);
try
{
var existingAdminMaster = await userManager.Users
.IgnoreQueryFilters()
.FirstOrDefaultAsync(u => u.TenantId == systemTenantId && u.NormalizedEmail == normalizedEmail);
if (existingAdminMaster == null)
{
var adminMaster = new ApplicationUser
{
Name = options.AdminMasterName,
Email = adminMasterEmail,
UserName = adminMasterEmail,
TenantId = systemTenantId,
EmailConfirmed = true,
IsActive = true,
LockoutEnabled = true
};
var createResult = await userManager.CreateAsync(adminMaster, adminMasterPassword);
EnsureIdentitySucceeded(createResult, "Falha ao criar usuário sysadmin.");
var addRoleResult = await userManager.AddToRoleAsync(adminMaster, SystemTenantConstants.SystemRole);
EnsureIdentitySucceeded(addRoleResult, "Falha ao associar role sysadmin ao usuário inicial.");
}
else
{
existingAdminMaster.Name = options.AdminMasterName;
existingAdminMaster.Email = adminMasterEmail;
existingAdminMaster.UserName = adminMasterEmail;
existingAdminMaster.EmailConfirmed = true;
existingAdminMaster.IsActive = true;
existingAdminMaster.LockoutEnabled = true;
var updateResult = await userManager.UpdateAsync(existingAdminMaster);
EnsureIdentitySucceeded(updateResult, "Falha ao atualizar usuário sysadmin.");
if (options.ReapplyAdminCredentialsOnStartup)
{
await userManager.SetLockoutEndDateAsync(existingAdminMaster, null);
await userManager.ResetAccessFailedCountAsync(existingAdminMaster);
var resetToken = await userManager.GeneratePasswordResetTokenAsync(existingAdminMaster);
var resetPasswordResult = await userManager.ResetPasswordAsync(existingAdminMaster, resetToken, adminMasterPassword);
if (!resetPasswordResult.Succeeded)
{
var removePasswordResult = await userManager.RemovePasswordAsync(existingAdminMaster);
if (removePasswordResult.Succeeded)
{
var addPasswordResult = await userManager.AddPasswordAsync(existingAdminMaster, adminMasterPassword);
EnsureIdentitySucceeded(addPasswordResult, "Falha ao definir senha do sysadmin.");
}
else
{
var addPasswordResult = await userManager.AddPasswordAsync(existingAdminMaster, adminMasterPassword);
EnsureIdentitySucceeded(addPasswordResult, "Falha ao definir senha do sysadmin.");
}
}
}
if (!await userManager.IsInRoleAsync(existingAdminMaster, SystemTenantConstants.SystemRole))
{
var addRoleResult = await userManager.AddToRoleAsync(existingAdminMaster, SystemTenantConstants.SystemRole);
EnsureIdentitySucceeded(addRoleResult, "Falha ao associar role sysadmin ao usuário inicial.");
}
}
}
finally
{
tenantProvider.SetTenantId(previousTenant);
}
}
private static void EnsureIdentitySucceeded(IdentityResult result, string message)
{
if (result.Succeeded)
{
return;
}
var details = string.Join("; ", result.Errors.Select(e => e.Description));
throw new InvalidOperationException($"{message} Detalhes: {details}");
}
private static async Task MigrateLegacyRolesAsync(AppDbContext db, RoleManager<IdentityRole<Guid>> roleManager)
{
await MigrateLegacyRoleAsync(db, roleManager, "admin_master", AppRoles.SysAdmin);
await MigrateLegacyRoleAsync(db, roleManager, "admin", AppRoles.SysAdmin);
await MigrateLegacyRoleAsync(db, roleManager, "leitura", AppRoles.Cliente);
await MigrateLegacyRoleAsync(db, roleManager, "operador", AppRoles.Cliente);
}
private static async Task MigrateLegacyRoleAsync(
AppDbContext db,
RoleManager<IdentityRole<Guid>> roleManager,
string legacyRole,
string newRole)
{
var legacyRoleId = await roleManager.Roles
.Where(r => r.Name == legacyRole)
.Select(r => (Guid?)r.Id)
.FirstOrDefaultAsync();
if (!legacyRoleId.HasValue)
{
return;
}
var newRoleId = await roleManager.Roles
.Where(r => r.Name == newRole)
.Select(r => (Guid?)r.Id)
.FirstOrDefaultAsync();
if (!newRoleId.HasValue)
{
return;
}
var legacyUserIds = await db.UserRoles
.Where(ur => ur.RoleId == legacyRoleId.Value)
.Select(ur => ur.UserId)
.Distinct()
.ToListAsync();
if (legacyUserIds.Count == 0)
{
return;
}
var alreadyInNewRole = await db.UserRoles
.Where(ur => ur.RoleId == newRoleId.Value && legacyUserIds.Contains(ur.UserId))
.Select(ur => ur.UserId)
.ToListAsync();
var existingSet = alreadyInNewRole.ToHashSet();
foreach (var userId in legacyUserIds)
{
if (!existingSet.Contains(userId))
{
db.UserRoles.Add(new IdentityUserRole<Guid>
{
UserId = userId,
RoleId = newRoleId.Value
});
}
}
var legacyAssignments = await db.UserRoles
.Where(ur => ur.RoleId == legacyRoleId.Value)
.ToListAsync();
if (legacyAssignments.Count > 0)
{
db.UserRoles.RemoveRange(legacyAssignments);
}
await db.SaveChangesAsync();
var legacyRoleStillUsed = await db.UserRoles.AnyAsync(ur => ur.RoleId == legacyRoleId.Value);
if (!legacyRoleStillUsed)
{
var legacyRoleEntity = await roleManager.Roles.FirstOrDefaultAsync(r => r.Id == legacyRoleId.Value);
if (legacyRoleEntity != null)
{
await roleManager.DeleteAsync(legacyRoleEntity);
}
}
}
}