feat(console): add TenantService with audit logging
This commit is contained in:
parent
993152aa56
commit
cd2ec42ea3
305
Services/TenantService.cs
Normal file
305
Services/TenantService.cs
Normal file
@ -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<TenantDto> Items, int TotalCount)> GetTenantsAsync(int page, int pageSize, string? name = null, string? tenantId = null, string? status = null);
|
||||
Task<TenantDto?> GetTenantAsync(long id);
|
||||
Task<IEnumerable<UserDto>> GetTenantUsersAsync(long tenantId);
|
||||
Task<IEnumerable<object>> GetTenantRolesAsync(long tenantId);
|
||||
Task<TenantSettingsDto> GetTenantSettingsAsync(string tenantId);
|
||||
Task UpdateTenantSettingsAsync(string tenantId, TenantSettingsDto settings);
|
||||
Task<TenantDto> CreateTenantAsync(CreateTenantDto dto);
|
||||
Task<TenantDto> 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<ApplicationUser> _userManager;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public TenantService(
|
||||
ITenantRepository repository,
|
||||
IUserRepository userRepository,
|
||||
IRoleRepository roleRepository,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
ApplicationDbContext context,
|
||||
IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_repository = repository;
|
||||
_userRepository = userRepository;
|
||||
_roleRepository = roleRepository;
|
||||
_userManager = userManager;
|
||||
_context = context;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public async Task<(IEnumerable<TenantDto> 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<TenantDto>();
|
||||
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<TenantDto?> 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<IEnumerable<UserDto>> 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<UserDto>();
|
||||
|
||||
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<IEnumerable<object>> 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<TenantSettingsDto> 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<string> { "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<TenantDto> 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<TenantDto> 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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user