- Add Fengling.Platform domain and infrastructure projects - Move Tenant aggregate from AuthService/Console to Platform.Domain - Add TenantRepository and SeedData to Platform - Remove duplicate Tenant/TenantInfo models from AuthService and Console - Update controllers and services to use Platform.Domain.Tenant - Add new migrations for PlatformDbContext BREAKING CHANGE: Tenant entity now uses strongly-typed ID (TenantId)
325 lines
9.8 KiB
C#
325 lines
9.8 KiB
C#
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;
|
|
using Fengling.Platform.Domain.AggregatesModel.TenantAggregate;
|
|
using Fengling.Platform.Infrastructure;
|
|
|
|
namespace Fengling.AuthService.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class TenantsController(
|
|
ApplicationDbContext context,
|
|
UserManager<ApplicationUser> userManager,
|
|
ILogger<TenantsController> logger,
|
|
PlatformDbContext platformDbContext)
|
|
: ControllerBase
|
|
{
|
|
[HttpGet]
|
|
public async Task<ActionResult<object>> GetTenants(
|
|
[FromQuery] int page = 1,
|
|
[FromQuery] int pageSize = 10,
|
|
[FromQuery] string? name = null,
|
|
[FromQuery] string? tenantCode = null,
|
|
[FromQuery] TenantStatus? status = null)
|
|
{
|
|
var query = platformDbContext.Tenants.AsQueryable();
|
|
|
|
if (!string.IsNullOrEmpty(name))
|
|
{
|
|
query = query.Where(t => t.Name.Contains(name));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(tenantCode))
|
|
{
|
|
query = query.Where(t => t.TenantCode.Contains(tenantCode));
|
|
}
|
|
|
|
if (status.HasValue)
|
|
{
|
|
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<object>();
|
|
|
|
foreach (var tenant in tenants)
|
|
{
|
|
var userCount = await context.Users.CountAsync(u => u.TenantInfo.TenantId == tenant.Id && !u.IsDeleted);
|
|
result.Add(new
|
|
{
|
|
id = tenant.Id,
|
|
tenantId = tenant.Id,
|
|
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<ActionResult<Tenant>> GetTenant(TenantId id)
|
|
{
|
|
var tenant = await platformDbContext.Tenants.FindAsync(id);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
return Ok(new
|
|
{
|
|
id = tenant.Id,
|
|
tenantId = tenant.Id,
|
|
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<ActionResult<List<object>>> GetTenantUsers(TenantId tenantId)
|
|
{
|
|
var tenant = await platformDbContext.Tenants
|
|
.FirstOrDefaultAsync(t => t.Id == tenantId);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var users = await context.Users
|
|
.Where(u =>
|
|
u.TenantInfo.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.TenantInfo.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<ActionResult<List<object>>> GetTenantRoles(TenantId tenantId)
|
|
{
|
|
var tenant = await platformDbContext.Tenants
|
|
.FirstOrDefaultAsync(t => t.Id == 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<ActionResult<TenantSettings>> GetTenantSettings(TenantId tenantId)
|
|
{
|
|
var tenant = await platformDbContext.Tenants
|
|
.FirstOrDefaultAsync(t => t.Id == tenantId);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var settings = new TenantSettings
|
|
{
|
|
AllowRegistration = false,
|
|
AllowedEmailDomains = "",
|
|
DefaultRoleId = null,
|
|
PasswordPolicy = new List<string> { "requireNumber", "requireLowercase" },
|
|
MinPasswordLength = 8,
|
|
SessionTimeout = 120,
|
|
};
|
|
|
|
return Ok(settings);
|
|
}
|
|
|
|
[HttpPut("{tenantId}/settings")]
|
|
public async Task<IActionResult> UpdateTenantSettings(TenantId tenantId, TenantSettings settings)
|
|
{
|
|
var tenant = await platformDbContext.Tenants.FirstOrDefaultAsync(t => t.Id == tenantId);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
await CreateAuditLog("tenant", "update", "TenantSettings", tenant.Id, tenant.Name, null,
|
|
JsonSerializer.Serialize(settings));
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult<Tenant>> CreateTenant(CreateTenantDto dto)
|
|
{
|
|
var tenant = new Tenant(dto.TenantCode, dto.TenantName, dto.ContactName, dto.ContactEmail, dto.ContactPhone,
|
|
dto.MaxUsers, dto.Description, dto.ExpiresAt);
|
|
|
|
platformDbContext.Tenants.Add(tenant);
|
|
await context.SaveChangesAsync();
|
|
|
|
await CreateAuditLog("tenant", "create", "Tenant", tenant.Id, tenant.TenantCode,
|
|
null, JsonSerializer.Serialize(dto));
|
|
|
|
return CreatedAtAction(nameof(GetTenant), new { id = tenant.Id }, tenant);
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
public async Task<IActionResult> UpdateTenant(long id, UpdateTenantDto dto)
|
|
{
|
|
var tenant = await platformDbContext.Tenants.FindAsync(id);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var oldValue = JsonSerializer.Serialize(tenant);
|
|
tenant.UpdateInfo(dto.Name, dto.ContactName, dto.ContactEmail, dto.ContactPhone);
|
|
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
await CreateAuditLog("tenant", "update", "Tenant", tenant.Id, tenant.Name, oldValue,
|
|
JsonSerializer.Serialize(tenant));
|
|
|
|
return NoContent();
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
public async Task<IActionResult> DeleteTenant(long id)
|
|
{
|
|
var tenant = await platformDbContext.Tenants.FindAsync(id);
|
|
if (tenant == null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var oldValue = JsonSerializer.Serialize(tenant);
|
|
|
|
var users = await context.Users.Where(u => u.TenantInfo.TenantId == tenant.Id).ToListAsync();
|
|
foreach (var user in users)
|
|
{
|
|
user.IsDeleted = true;
|
|
user.UpdatedTime = DateTime.UtcNow;
|
|
}
|
|
|
|
tenant.Delete();
|
|
await context.SaveChangesAsync();
|
|
|
|
await CreateAuditLog("tenant", "delete", "Tenant", tenant.Id, tenant.Name, 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 record CreateTenantDto(
|
|
string TenantCode,
|
|
string TenantName,
|
|
string ContactName,
|
|
string ContactEmail,
|
|
string ContactPhone,
|
|
int? MaxUsers,
|
|
string? Description,
|
|
string Status,
|
|
DateTime? ExpiresAt);
|
|
|
|
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<string> PasswordPolicy { get; set; } = new();
|
|
public int MinPasswordLength { get; set; } = 8;
|
|
public int SessionTimeout { get; set; } = 120;
|
|
} |