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(); } }