diff --git a/Services/TenantService.cs b/Services/TenantService.cs new file mode 100644 index 0000000..aa92927 --- /dev/null +++ b/Services/TenantService.cs @@ -0,0 +1,305 @@ +using Fengling.AuthService.Data; +using Fengling.AuthService.Models; +using Fengling.Console.Models.Dtos; +using Fengling.Console.Repositories; +using Microsoft.AspNetCore.Identity; +using System.Security.Claims; + +namespace Fengling.Console.Services; + +public interface ITenantService +{ + Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null); + Task GetTenantAsync(long id); + Task> GetTenantUsersAsync(long tenantId); + Task> GetTenantRolesAsync(long tenantId); + Task GetTenantSettingsAsync(string tenantId); + Task UpdateTenantSettingsAsync(string tenantId, TenantSettingsDto settings); + Task CreateTenantAsync(CreateTenantDto dto); + Task UpdateTenantAsync(long id, UpdateTenantDto dto); + Task DeleteTenantAsync(long id); +} + +public class TenantService : ITenantService +{ + private readonly ITenantRepository _repository; + private readonly IUserRepository _userRepository; + private readonly IRoleRepository _roleRepository; + private readonly UserManager _userManager; + private readonly ApplicationDbContext _context; + private readonly IHttpContextAccessor _httpContextAccessor; + + public TenantService( + ITenantRepository repository, + IUserRepository userRepository, + IRoleRepository roleRepository, + UserManager userManager, + ApplicationDbContext context, + IHttpContextAccessor httpContextAccessor) + { + _repository = repository; + _userRepository = userRepository; + _roleRepository = roleRepository; + _userManager = userManager; + _context = context; + _httpContextAccessor = httpContextAccessor; + } + + public async Task<(IEnumerable Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null) + { + var tenants = await _repository.GetPagedAsync(page, pageSize, name, tenantId, status); + var totalCount = await _repository.CountAsync(name, tenantId, status); + + var tenantDtos = new List(); + foreach (var tenant in tenants) + { + var userCount = await _repository.GetUserCountAsync(tenant.Id); + tenantDtos.Add(new TenantDto + { + Id = tenant.Id, + TenantId = tenant.TenantId, + 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(long id) + { + var tenant = await _repository.GetByIdAsync(id); + if (tenant == null) return null; + + return new TenantDto + { + Id = tenant.Id, + TenantId = tenant.TenantId, + Name = tenant.Name, + ContactName = tenant.ContactName, + ContactEmail = tenant.ContactEmail, + ContactPhone = tenant.ContactPhone, + MaxUsers = tenant.MaxUsers, + UserCount = await _repository.GetUserCountAsync(tenant.Id), + Status = tenant.Status, + ExpiresAt = tenant.ExpiresAt, + Description = tenant.Description, + CreatedAt = tenant.CreatedAt + }; + } + + public async Task> GetTenantUsersAsync(long tenantId) + { + var tenant = await _repository.GetByIdAsync(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, + TenantId = user.TenantInfo.Id, + TenantName = user.TenantInfo.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(long tenantId) + { + var tenant = await _repository.GetByIdAsync(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(string tenantId) + { + var tenant = await _repository.GetByTenantIdAsync(tenantId); + if (tenant == null) + { + throw new KeyNotFoundException($"Tenant with tenantId '{tenantId}' not found"); + } + + return new TenantSettingsDto + { + AllowRegistration = false, + AllowedEmailDomains = "", + DefaultRoleId = null, + PasswordPolicy = new List { "requireNumber", "requireLowercase" }, + MinPasswordLength = 8, + SessionTimeout = 120 + }; + } + + public async Task UpdateTenantSettingsAsync(string tenantId, TenantSettingsDto settings) + { + var tenant = await _repository.GetByTenantIdAsync(tenantId); + if (tenant == null) + { + throw new KeyNotFoundException($"Tenant with tenantId '{tenantId}' not found"); + } + + await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(settings)); + } + + public async Task CreateTenantAsync(CreateTenantDto dto) + { + var tenant = new Tenant + { + TenantId = dto.TenantId, + Name = dto.Name, + ContactName = dto.ContactName, + ContactEmail = dto.ContactEmail, + ContactPhone = dto.ContactPhone, + MaxUsers = dto.MaxUsers, + Description = dto.Description, + Status = dto.Status, + ExpiresAt = dto.ExpiresAt, + CreatedAt = DateTime.UtcNow + }; + + await _repository.AddAsync(tenant); + + await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, System.Text.Json.JsonSerializer.Serialize(dto)); + + return new TenantDto + { + Id = tenant.Id, + TenantId = tenant.TenantId, + 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(long id, UpdateTenantDto dto) + { + var tenant = await _repository.GetByIdAsync(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.MaxUsers = dto.MaxUsers; + tenant.Description = dto.Description; + tenant.Status = dto.Status; + tenant.ExpiresAt = dto.ExpiresAt; + tenant.UpdatedAt = DateTime.UtcNow; + + await _repository.UpdateAsync(tenant); + + await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, System.Text.Json.JsonSerializer.Serialize(tenant)); + + return new TenantDto + { + Id = tenant.Id, + TenantId = tenant.TenantId, + Name = tenant.Name, + ContactName = tenant.ContactName, + ContactEmail = tenant.ContactEmail, + ContactPhone = tenant.ContactPhone, + MaxUsers = tenant.MaxUsers, + UserCount = await _repository.GetUserCountAsync(tenant.Id), + Status = tenant.Status, + ExpiresAt = tenant.ExpiresAt, + Description = tenant.Description, + CreatedAt = tenant.CreatedAt + }; + } + + public async Task DeleteTenantAsync(long id) + { + var tenant = await _repository.GetByIdAsync(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; + await _repository.UpdateAsync(tenant); + + await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, oldValue); + } + + private async Task CreateAuditLog(string operation, string action, string targetType, long? 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 tenantId = httpContext?.User?.FindFirstValue("TenantId"); + + var log = new AuditLog + { + Operator = userName, + TenantId = tenantId, + Operation = operation, + Action = action, + TargetType = targetType, + TargetId = targetId, + TargetName = targetName, + IpAddress = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "unknown", + Status = "success", + OldValue = oldValue, + NewValue = newValue, + CreatedAt = DateTime.UtcNow + }; + + _context.AuditLogs.Add(log); + await _context.SaveChangesAsync(); + } +}