using Fengling.Console.Managers; using Fengling.Console.Models.Dtos; using Fengling.Console.Repositories; using Microsoft.AspNetCore.Identity; using System.Security.Claims; using Fengling.Console.Datas; using Fengling.Console.Models.Entities; using TenantStatus = Fengling.Console.Models.Entities.TenantStatus; namespace Fengling.Console.Services; public interface ITenantService { Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null); Task GetTenantAsync(int id); Task> GetTenantUsersAsync(int tenantId); Task> GetTenantRolesAsync(int tenantId); Task GetTenantSettingsAsync(int id); Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings); Task CreateTenantAsync(CreateTenantDto dto); Task UpdateTenantAsync(int id, UpdateTenantDto dto); Task DeleteTenantAsync(int id); } public class TenantService( ITenantManager tenantManager, IUserRepository userRepository, IRoleRepository roleRepository, UserManager userManager, ApplicationDbContext context, IHttpContextAccessor httpContextAccessor) : ITenantService { public async Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync (int page, int pageSize, string? name = null, string? tenantCode = null, TenantStatus? status = null) { var tenants = await tenantManager.GetPagedAsync(page, pageSize, name, tenantCode, status); var totalCount = await tenantManager.GetCountAsync(name, tenantCode, status); var tenantDtos = new List(); foreach (var tenant in tenants) { var userCount = await tenantManager.GetUserCountAsync(tenant.Id); tenantDtos.Add(new TenantDto { Id = tenant.Id, TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, ContactPhone = tenant.ContactPhone, MaxUsers = tenant.MaxUsers, UserCount = userCount, Status = tenant.Status, ExpiresAt = tenant.ExpiresAt, Description = tenant.Description, CreatedAt = tenant.CreatedAt }); } return (tenantDtos, totalCount); } public async Task GetTenantAsync(int id) { var tenant = await tenantManager.FindByIdAsync(id); if (tenant == null) return null; return new TenantDto { Id = tenant.Id, TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, ContactPhone = tenant.ContactPhone, MaxUsers = tenant.MaxUsers, UserCount = await tenantManager.GetUserCountAsync(tenant.Id), Status = tenant.Status, ExpiresAt = tenant.ExpiresAt, Description = tenant.Description, CreatedAt = tenant.CreatedAt }; } public async Task> GetTenantUsersAsync(int tenantId) { var tenant = await tenantManager.FindByIdAsync(tenantId); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {tenantId} not found"); } var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, tenantId.ToString()); var userDtos = new List(); foreach (var user in users) { var roles = await userManager.GetRolesAsync(user); userDtos.Add(new UserDto { Id = user.Id, UserName = user.UserName, Email = user.Email, RealName = user.RealName, TenantCode = user.TenantId.ToString(), TenantName = tenant?.Name ?? "", Roles = roles.ToList(), EmailConfirmed = user.EmailConfirmed, IsActive = !user.LockoutEnabled || user.LockoutEnd == null || user.LockoutEnd < DateTimeOffset.UtcNow, CreatedAt = user.CreatedTime }); } return userDtos; } public async Task> GetTenantRolesAsync(int tenantId) { var tenant = await tenantManager.FindByIdAsync(tenantId); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {tenantId} not found"); } var roles = await roleRepository.GetPagedAsync(1, int.MaxValue, null, tenantId.ToString()); return roles.Select(r => new { id = r.Id, name = r.Name, displayName = r.DisplayName }); } public async Task GetTenantSettingsAsync(int id) { var tenant = await tenantManager.FindByIdAsync(id); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {id} not found"); } return new TenantSettingsDto { AllowRegistration = false, AllowedEmailDomains = "", DefaultRoleId = null, PasswordPolicy = new List { "requireNumber", "requireLowercase" }, MinPasswordLength = 8, SessionTimeout = 120 }; } public async Task UpdateTenantSettingsAsync(int id, TenantSettingsDto settings) { var tenant = await tenantManager.FindByIdAsync(id); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {id} not found"); } await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.Name, null, System.Text.Json.JsonSerializer.Serialize(settings)); } public async Task CreateTenantAsync(CreateTenantDto dto) { var tenant = new Tenant { TenantCode = dto.TenantCode, Name = dto.Name, ContactName = dto.ContactName, ContactEmail = dto.ContactEmail, ContactPhone = dto.ContactPhone, MaxUsers = dto.MaxUsers, Description = dto.Description, ExpiresAt = dto.ExpiresAt, Status = TenantStatus.Active, CreatedAt = DateTime.UtcNow }; var result = await tenantManager.CreateAsync(tenant); if (!result.Succeeded) { var errors = string.Join(", ", result.Errors.Select(e => e.Description)); throw new InvalidOperationException($"Failed to create tenant: {errors}"); } await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.Name, null, System.Text.Json.JsonSerializer.Serialize(dto)); return new TenantDto { Id = tenant.Id, TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, ContactPhone = tenant.ContactPhone, MaxUsers = tenant.MaxUsers, UserCount = 0, Status = tenant.Status, ExpiresAt = tenant.ExpiresAt, Description = tenant.Description, CreatedAt = tenant.CreatedAt }; } public async Task UpdateTenantAsync(int id, UpdateTenantDto dto) { var tenant = await tenantManager.FindByIdAsync(id); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {id} not found"); } var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant); tenant.Name = dto.Name; tenant.ContactName = dto.ContactName; tenant.ContactEmail = dto.ContactEmail; tenant.ContactPhone = dto.ContactPhone; tenant.UpdatedAt = DateTime.UtcNow; var result = await tenantManager.UpdateAsync(tenant); if (!result.Succeeded) { var errors = string.Join(", ", result.Errors.Select(e => e.Description)); throw new InvalidOperationException($"Failed to update tenant: {errors}"); } await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue, System.Text.Json.JsonSerializer.Serialize(tenant)); return new TenantDto { Id = tenant.Id, TenantCode = tenant.TenantCode, Name = tenant.Name, ContactName = tenant.ContactName, ContactEmail = tenant.ContactEmail, ContactPhone = tenant.ContactPhone, MaxUsers = tenant.MaxUsers, UserCount = await tenantManager.GetUserCountAsync(tenant.Id), Status = tenant.Status, ExpiresAt = tenant.ExpiresAt, Description = tenant.Description, CreatedAt = tenant.CreatedAt }; } public async Task DeleteTenantAsync(int id) { var tenant = await tenantManager.FindByIdAsync(id); if (tenant == null) { throw new KeyNotFoundException($"Tenant with ID {id} not found"); } var oldValue = System.Text.Json.JsonSerializer.Serialize(tenant); var users = await userRepository.GetPagedAsync(1, int.MaxValue, null, null, id.ToString()); foreach (var user in users) { user.IsDeleted = true; user.UpdatedTime = DateTime.UtcNow; await context.SaveChangesAsync(); } tenant.IsDeleted = true; var result = await tenantManager.UpdateAsync(tenant); if (!result.Succeeded) { var errors = string.Join(", ", result.Errors.Select(e => e.Description)); throw new InvalidOperationException($"Failed to delete tenant: {errors}"); } await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, oldValue); } private async Task CreateAuditLog(string operation, string action, string targetType, int? targetId, string? targetName, string? oldValue = null, string? newValue = null) { var httpContext = httpContextAccessor.HttpContext; var userName = httpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier) ?? httpContext?.User?.Identity?.Name ?? "system"; var tenantIdClaim = httpContext?.User?.FindFirstValue("TenantId"); var log = new AuditLog { Operator = userName, TenantId = tenantIdClaim, Operation = operation, Action = action, TargetType = targetType, TargetId = targetId.HasValue ? (long)targetId.Value : null, TargetName = targetName, IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown", Status = "success", OldValue = oldValue, NewValue = newValue, CreatedAt = DateTime.UtcNow }; context.AuditLogs.Add(log); await context.SaveChangesAsync(); } }