using Fengling.AuthService.Data; using Fengling.AuthService.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using System.Text.Json; namespace Fengling.AuthService.Controllers; [ApiController] [Route("api/[controller]")] [Authorize] public class TenantsController : ControllerBase { private readonly ApplicationDbContext _context; private readonly UserManager _userManager; private readonly ILogger _logger; public TenantsController( ApplicationDbContext context, UserManager userManager, ILogger logger) { _context = context; _userManager = userManager; _logger = logger; } [HttpGet] public async Task> GetTenants( [FromQuery] int page = 1, [FromQuery] int pageSize = 10, [FromQuery] string? name = null, [FromQuery] string? tenantId = null, [FromQuery] string? status = null) { var query = _context.Tenants.AsQueryable(); if (!string.IsNullOrEmpty(name)) { query = query.Where(t => t.Name.Contains(name)); } if (!string.IsNullOrEmpty(tenantId)) { query = query.Where(t => t.TenantId.Contains(tenantId)); } if (!string.IsNullOrEmpty(status)) { query = query.Where(t => t.Status == status); } var totalCount = await query.CountAsync(); var tenants = await query .OrderByDescending(t => t.CreatedAt) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); var result = new List(); foreach (var tenant in tenants) { var userCount = await _context.Users.CountAsync(u => u.TenantId == tenant.Id && !u.IsDeleted); result.Add(new { id = tenant.Id, tenantId = tenant.TenantId, name = tenant.Name, contactName = tenant.ContactName, contactEmail = tenant.ContactEmail, contactPhone = tenant.ContactPhone, maxUsers = tenant.MaxUsers, userCount, status = tenant.Status, expiresAt = tenant.ExpiresAt, description = tenant.Description, createdAt = tenant.CreatedAt, }); } return Ok(new { items = result, totalCount, page, pageSize }); } [HttpGet("{id}")] public async Task> GetTenant(long id) { var tenant = await _context.Tenants.FindAsync(id); if (tenant == null) { return NotFound(); } return Ok(new { id = tenant.Id, tenantId = tenant.TenantId, name = tenant.Name, contactName = tenant.ContactName, contactEmail = tenant.ContactEmail, contactPhone = tenant.ContactPhone, maxUsers = tenant.MaxUsers, status = tenant.Status, expiresAt = tenant.ExpiresAt, description = tenant.Description, createdAt = tenant.CreatedAt, updatedAt = tenant.UpdatedAt, }); } [HttpGet("{tenantId}/users")] public async Task>> GetTenantUsers(string tenantId) { var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); if (tenant == null) { return NotFound(); } var users = await _context.Users .Where(u => u.TenantId == tenant.Id && !u.IsDeleted) .ToListAsync(); var result = users.Select(async u => new { id = u.Id, userName = u.UserName, email = u.Email, realName = u.RealName, tenantId = u.TenantId, roles = await _userManager.GetRolesAsync(u), isActive = !u.LockoutEnabled || u.LockoutEnd == null || u.LockoutEnd < DateTimeOffset.UtcNow, createdAt = u.CreatedTime, }); return Ok(await Task.WhenAll(result)); } [HttpGet("{tenantId}/roles")] public async Task>> GetTenantRoles(string tenantId) { var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); if (tenant == null) { return NotFound(); } var roles = await _context.Roles .Where(r => r.TenantId == tenant.Id) .ToListAsync(); var result = roles.Select(r => new { id = r.Id, name = r.Name, displayName = r.DisplayName, }); return Ok(result); } [HttpGet("{tenantId}/settings")] public async Task> GetTenantSettings(string tenantId) { var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); if (tenant == null) { return NotFound(); } var settings = new TenantSettings { AllowRegistration = false, AllowedEmailDomains = "", DefaultRoleId = null, PasswordPolicy = new List { "requireNumber", "requireLowercase" }, MinPasswordLength = 8, SessionTimeout = 120, }; return Ok(settings); } [HttpPut("{tenantId}/settings")] public async Task UpdateTenantSettings(string tenantId, TenantSettings settings) { var tenant = await _context.Tenants.FirstOrDefaultAsync(t => t.TenantId == tenantId); if (tenant == null) { return NotFound(); } await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(settings)); return NoContent(); } [HttpPost] public async Task> CreateTenant(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, }; _context.Tenants.Add(tenant); await _context.SaveChangesAsync(); await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantId, null, JsonSerializer.Serialize(dto)); return CreatedAtAction(nameof(GetTenant), new { id = tenant.Id }, tenant); } [HttpPut("{id}")] public async Task UpdateTenant(long id, UpdateTenantDto dto) { var tenant = await _context.Tenants.FindAsync(id); if (tenant == null) { return NotFound(); } var oldValue = 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 _context.SaveChangesAsync(); await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.TenantId, oldValue, JsonSerializer.Serialize(tenant)); return NoContent(); } [HttpDelete("{id}")] public async Task DeleteTenant(long id) { var tenant = await _context.Tenants.FindAsync(id); if (tenant == null) { return NotFound(); } var oldValue = JsonSerializer.Serialize(tenant); var users = await _context.Users.Where(u => u.TenantId == tenant.Id).ToListAsync(); foreach (var user in users) { user.IsDeleted = true; user.UpdatedTime = DateTime.UtcNow; } tenant.IsDeleted = true; await _context.SaveChangesAsync(); await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.TenantId, oldValue); return NoContent(); } private async Task CreateAuditLog(string operation, string action, string targetType, long? targetId, string? targetName, string? oldValue = null, string? newValue = null) { var userName = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.Identity?.Name ?? "system"; var tenantId = 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, }; _context.AuditLogs.Add(log); await _context.SaveChangesAsync(); } } public class CreateTenantDto { public string TenantId { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string ContactName { get; set; } = string.Empty; public string ContactEmail { get; set; } = string.Empty; public string? ContactPhone { get; set; } public int? MaxUsers { get; set; } public string? Description { get; set; } public string Status { get; set; } = "active"; public DateTime? ExpiresAt { get; set; } } public class UpdateTenantDto { public string Name { get; set; } = string.Empty; public string ContactName { get; set; } = string.Empty; public string ContactEmail { get; set; } = string.Empty; public string? ContactPhone { get; set; } public int? MaxUsers { get; set; } public string? Description { get; set; } public string Status { get; set; } = "active"; public DateTime? ExpiresAt { get; set; } } public class TenantSettings { public bool AllowRegistration { get; set; } public string AllowedEmailDomains { get; set; } = string.Empty; public long? DefaultRoleId { get; set; } public List PasswordPolicy { get; set; } = new(); public int MinPasswordLength { get; set; } = 8; public int SessionTimeout { get; set; } = 120; }