diff --git a/Marco.Pms.Services/Controllers/TaskController.cs b/Marco.Pms.Services/Controllers/TaskController.cs index b764f00..6a1921b 100644 --- a/Marco.Pms.Services/Controllers/TaskController.cs +++ b/Marco.Pms.Services/Controllers/TaskController.cs @@ -230,8 +230,8 @@ namespace MarcoBMS.Services.Controllers : image.Base64Data; var fileType = _s3Service.GetContentTypeFromBase64(base64); - var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_report"); - var objectKey = $"tenant-{tenantId}/project-{projectId}/Actitvity/{fileName}"; + var fileName = _s3Service.GenerateFileName(fileType, taskAllocation.Id, "task_report"); + var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); @@ -336,7 +336,7 @@ namespace MarcoBMS.Services.Controllers : image.Base64Data; var fileType = _s3Service.GetContentTypeFromBase64(base64); - var fileName = _s3Service.GenerateFileName(fileType, tenantId, "task_comment"); + var fileName = _s3Service.GenerateFileName(fileType, comment.Id, "task_comment"); var objectKey = $"tenant-{tenantId}/project-{projectId}/Activity/{fileName}"; await _s3Service.UploadFileAsync(base64, fileType, objectKey); diff --git a/Marco.Pms.Services/Service/StartupDataSeeder.cs b/Marco.Pms.Services/Service/StartupDataSeeder.cs index 10d6f71..d2496fc 100644 --- a/Marco.Pms.Services/Service/StartupDataSeeder.cs +++ b/Marco.Pms.Services/Service/StartupDataSeeder.cs @@ -4,134 +4,211 @@ using Marco.Pms.Model.Entitlements; using Marco.Pms.Model.Roles; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; // For configuration +using System.Linq.Expressions; namespace Marco.Pms.Services.Service { + // A configuration class to hold settings from appsettings.json + // This avoids hardcoding sensitive information. + public class SuperAdminSettings + { + public const string CONFIG_SECTION_NAME = "SuperAdminAccount"; + public string Email { get; set; } = "admin@marcoaiot.com"; + public string Password { get; set; } = "User@123"; + public string TenantId { get; set; } = "b3466e83-7e11-464c-b93a-daf047838b26"; + } + public class StartupUserSeeder : IHostedService { private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; - public StartupUserSeeder(IServiceProvider serviceProvider) + // Constants to avoid "magic strings" + private const string AdminJobRoleName = "Admin"; + private const string SuperUserRoleName = "Super User"; + + public StartupUserSeeder(IServiceProvider serviceProvider, ILogger logger) { _serviceProvider = serviceProvider; + _logger = logger; } public async Task StartAsync(CancellationToken cancellationToken) { + _logger.LogInformation("Starting database seeding process..."); + using var scope = _serviceProvider.CreateScope(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - var dbContext = scope.ServiceProvider.GetRequiredService(); + var serviceProvider = scope.ServiceProvider; - var userEmail = "admin@marcoaiot.com"; + // Get services from the scoped provider + var userManager = serviceProvider.GetRequiredService>(); + var dbContext = serviceProvider.GetRequiredService(); + var adminSettings = serviceProvider.GetRequiredService>().Value; + var tenantId = Guid.Parse(adminSettings.TenantId); - var user = await userManager.FindByEmailAsync(userEmail); - var newUser = new ApplicationUser + // Use a database transaction to ensure all operations succeed or none do. + await using var transaction = await dbContext.Database.BeginTransactionAsync(cancellationToken); + + try { - UserName = userEmail, - Email = userEmail, - EmailConfirmed = true, - IsRootUser = true, - TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") - }; - var result = new IdentityResult(); + // 1. Seed the Application User (Super Admin) + var user = await SeedSuperAdminUserAsync(userManager, adminSettings, tenantId); + // 2. Seed the Job Role + var jobRole = await GetOrCreateAsync( + dbContext.JobRoles, + j => j.Name == AdminJobRoleName && j.TenantId == tenantId, + () => new JobRole + { + Name = AdminJobRoleName, + Description = "Administrator with full system access.", + TenantId = tenantId + }); + + // 3. Seed the Application Role + var appRole = await GetOrCreateAsync( + dbContext.ApplicationRoles, + a => a.Role == SuperUserRoleName && a.TenantId == tenantId, + () => new ApplicationRole + { + Role = SuperUserRoleName, + Description = "System role with all permissions.", + IsSystem = true, + TenantId = tenantId + }); + + // 4. Seed the Employee record linked to the user and job role + var employee = await GetOrCreateAsync( + dbContext.Employees, + e => e.Email == adminSettings.Email && e.TenantId == tenantId, + () => new Employee + { + ApplicationUserId = user.Id, + FirstName = "Admin", + LastName = "User", + Email = adminSettings.Email, + TenantId = tenantId, + PhoneNumber = "9876543210", + JobRoleId = jobRole.Id, + IsSystem = true, + JoiningDate = DateTime.UtcNow, + BirthDate = new DateTime(1970, 1, 1) + // Set other non-nullable fields to sensible defaults + }); + + // 5. Seed the Employee-Role Mapping + await GetOrCreateAsync( + dbContext.EmployeeRoleMappings, + erm => erm.EmployeeId == employee.Id && erm.RoleId == appRole.Id, + () => new EmployeeRoleMapping + { + EmployeeId = employee.Id, + RoleId = appRole.Id, + TenantId = tenantId, + IsEnabled = true + }); + + // 6. Seed Role Permissions (Efficiently) + await SeedRolePermissionsAsync(dbContext, appRole.Id); + + // All entities are now tracked by the DbContext. + // A single SaveChanges call is more efficient. + await dbContext.SaveChangesAsync(cancellationToken); + + // If all operations were successful, commit the transaction. + await transaction.CommitAsync(cancellationToken); + _logger.LogInformation("Database seeding process completed successfully."); + } + catch (Exception ex) + { + _logger.LogError(ex, "An error occurred during database seeding. Rolling back changes."); + await transaction.RollbackAsync(cancellationToken); + // Optionally re-throw or handle the exception as needed + throw; + } + } + + private async Task SeedSuperAdminUserAsync(UserManager userManager, SuperAdminSettings settings, Guid tenantId) + { + _logger.LogInformation("Seeding Super Admin user: {Email}", settings.Email); + var user = await userManager.FindByEmailAsync(settings.Email); if (user == null) { - result = await userManager.CreateAsync(newUser, "User@123"); - } - else - { - newUser = user; - } - - var jobRole = new JobRole - { - Id = Guid.Empty, - Name = "Admin", - Description = "Admin", - TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"), - }; - - if (!await dbContext.JobRoles.Where(j => j.Name == "Admin").AnyAsync()) - { - await dbContext.JobRoles.AddAsync(jobRole); - } - else - { - jobRole = await dbContext.JobRoles.Where(j => j.Name == "Admin").FirstOrDefaultAsync(); - } - var role = new ApplicationRole - { - Role = "Super User", - Description = "Super User", - IsSystem = true, - TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26") - }; - if (!await dbContext.ApplicationRoles.Where(a => a.Role == "Super User").AnyAsync()) - { - await dbContext.ApplicationRoles.AddAsync(role); - } - else - { - role = await dbContext.ApplicationRoles.Where(a => a.Role == "Super User").FirstOrDefaultAsync(); - } - await dbContext.SaveChangesAsync(); - var employee = new Employee - { - ApplicationUserId = newUser.Id, - FirstName = "Admin", - LastName = "", - Email = userEmail, - TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"), - CurrentAddress = "", - BirthDate = Convert.ToDateTime("1965-04-20 10:11:17.588000"), - EmergencyPhoneNumber = "", - EmergencyContactPerson = "", - AadharNumber = "", - Gender = "", - MiddleName = "", - PanNumber = "", - PermanentAddress = "", - PhoneNumber = "9876543210", - Photo = null, // GetFileDetails(model.Photo).Result.FileData, - JobRoleId = jobRole != null ? jobRole.Id : Guid.Empty, - IsSystem = true, - JoiningDate = Convert.ToDateTime("2000-04-20 10:11:17.588000"), - }; - if ((!await dbContext.Employees.Where(e => e.Email == "admin@marcoaiot.com").AnyAsync()) && jobRole?.Id != Guid.Empty) - { - await dbContext.Employees.AddAsync(employee); - } - else - { - employee = await dbContext.Employees.Where(e => e.Email == "admin@marcoaiot.com").FirstOrDefaultAsync(); - } - await dbContext.SaveChangesAsync(); - if (!await dbContext.EmployeeRoleMappings.AnyAsync()) - { - await dbContext.EmployeeRoleMappings.AddAsync(new EmployeeRoleMapping + user = new ApplicationUser { - EmployeeId = employee?.Id ?? Guid.Empty, - IsEnabled = true, - TenantId = Guid.Parse("b3466e83-7e11-464c-b93a-daf047838b26"), - RoleId = role?.Id ?? Guid.Empty - }); - } - if (!await dbContext.RolePermissionMappings.AnyAsync()) - { - List permissions = await dbContext.FeaturePermissions.ToListAsync(); - List rolesMapping = new List(); - foreach (var permission in permissions) + UserName = settings.Email, + Email = settings.Email, + EmailConfirmed = true, + IsRootUser = true, + TenantId = tenantId + }; + var result = await userManager.CreateAsync(user, settings.Password); + + if (!result.Succeeded) { - rolesMapping.Add(new RolePermissionMappings - { - ApplicationRoleId = role != null ? role.Id : Guid.Empty, - FeaturePermissionId = permission.Id - }); + // If user creation fails, it's a critical error. + var errors = string.Join(", ", result.Errors.Select(e => e.Description)); + _logger.LogError("Failed to create super admin user. Errors: {Errors}", errors); + throw new InvalidOperationException($"Failed to create super admin user: {errors}"); } - await dbContext.RolePermissionMappings.AddRangeAsync(rolesMapping); + _logger.LogInformation("Super Admin user created successfully."); } - await dbContext.SaveChangesAsync(); + else + { + _logger.LogInformation("Super Admin user already exists."); + } + return user; + } + + private async Task SeedRolePermissionsAsync(ApplicationDbContext dbContext, Guid superUserRoleId) + { + _logger.LogInformation("Seeding permissions for Super User role (ID: {RoleId})", superUserRoleId); + + var allPermissionIds = await dbContext.FeaturePermissions + .Select(p => p.Id) + .ToListAsync(); + + var permissionIdsFromDb = await dbContext.RolePermissionMappings + .Where(pm => pm.ApplicationRoleId == superUserRoleId) + .Select(pm => pm.FeaturePermissionId) + .ToListAsync(); // 1. Fetch data from DB into a List + + var existingPermissionIds = new HashSet(permissionIdsFromDb); // 2. Convert the List to a HashSet in memory + + var missingPermissionIds = allPermissionIds.Except(existingPermissionIds).ToList(); + + if (missingPermissionIds.Any()) + { + var newMappings = missingPermissionIds.Select(permissionId => new RolePermissionMappings + { + ApplicationRoleId = superUserRoleId, + FeaturePermissionId = permissionId + }); + + await dbContext.RolePermissionMappings.AddRangeAsync(newMappings); + _logger.LogInformation("Added {Count} new permission mappings to the Super User role.", missingPermissionIds.Count); + } + else + { + _logger.LogInformation("Super User role already has all available permissions."); + } + } + + /// + /// A generic helper to find an entity by a predicate or create, add, and return it if not found. + /// This promotes code reuse and makes the main logic cleaner. + /// + private async Task GetOrCreateAsync(DbSet dbSet, Expression> predicate, Func factory) where T : class + { + var entity = await dbSet.FirstOrDefaultAsync(predicate); + if (entity == null) + { + entity = factory(); + await dbSet.AddAsync(entity); + _logger.LogInformation("Creating new entity of type {EntityType}.", typeof(T).Name); + } + return entity; } public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;