using AMREZ.EOP.Abstractions.Applications.Tenancy; using AMREZ.EOP.Abstractions.Applications.UseCases.Tenancy; using AMREZ.EOP.Contracts.DTOs.Tenancy.AddBaseDomain; using AMREZ.EOP.Contracts.DTOs.Tenancy.CreateTenant; using AMREZ.EOP.Contracts.DTOs.Tenancy.ListDomains; using AMREZ.EOP.Contracts.DTOs.Tenancy.ListTenants; using AMREZ.EOP.Contracts.DTOs.Tenancy.MapDomain; using AMREZ.EOP.Contracts.DTOs.Tenancy.RemoveBaseDomain; using AMREZ.EOP.Contracts.DTOs.Tenancy.UnmapDomain; using AMREZ.EOP.Contracts.DTOs.Tenancy.UpdateTenant; using Microsoft.AspNetCore.Mvc; namespace AMREZ.EOP.API.Controllers; [ApiController] [Route("api/[controller]")] public sealed class TenancyController : ControllerBase { private readonly ICreateTenantUseCase _createTenant; private readonly IUpdateTenantUseCase _updateTenant; private readonly IDeleteTenantUseCase _deleteTenant; private readonly IMapDomainUseCase _mapDomain; private readonly IUnmapDomainUseCase _unmapDomain; private readonly IAddBaseDomainUseCase _addBaseDomain; private readonly IRemoveBaseDomainUseCase _removeBaseDomain; private readonly IListTenantsUseCase _listTenants; private readonly IListDomainsUseCase _listDomains; private readonly ITenantResolver _resolver; public TenancyController( ICreateTenantUseCase createTenant, IUpdateTenantUseCase updateTenant, IDeleteTenantUseCase deleteTenant, IMapDomainUseCase mapDomain, IUnmapDomainUseCase unmapDomain, IAddBaseDomainUseCase addBaseDomain, IRemoveBaseDomainUseCase removeBaseDomain, IListTenantsUseCase listTenants, IListDomainsUseCase listDomains, ITenantResolver resolver) { _createTenant = createTenant; _updateTenant = updateTenant; _deleteTenant = deleteTenant; _mapDomain = mapDomain; _unmapDomain = unmapDomain; _addBaseDomain = addBaseDomain; _removeBaseDomain = removeBaseDomain; _listTenants = listTenants; _listDomains = listDomains; _resolver = resolver; } private static object ErrTenantResolve => new { code = "TENANT_CONTEXT_NOT_RESOLVED", message = "Check X-Tenant (e.g., public) or permission scope." }; // Tenants // Create: อย่า feed body เข้า resolver (tenant ยังไม่เกิด) ให้ใช้ context ปัจจุบันเท่านั้น [HttpPost("tenants")] public async Task CreateTenant([FromBody] CreateTenantRequest body, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _createTenant.ExecuteAsync(body, ct); if (res is null) return Conflict(new { code = "TENANT_EXISTS", message = "Use PUT to update/reactivate." }); return Created($"/api/Tenancy/tenants/{res.TenantKey}", res); } [HttpPut("tenants/{tenantKey}")] public async Task UpdateTenant([FromRoute] string tenantKey, [FromBody] UpdateTenantRequest body, CancellationToken ct) { if (!string.Equals(tenantKey?.Trim(), body.TenantKey?.Trim(), StringComparison.OrdinalIgnoreCase)) return BadRequest(new { code = "TENANT_KEY_MISMATCH", message = "Route tenantKey must match body.TenantKey" }); // การแก้ไข tenant ที่มีอยู่: ให้ resolver ใช้ tenantKey เป็น hint เพื่อบังคับ context ให้ถูกต้อง var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _updateTenant.ExecuteAsync(body, ct); if (res is null) return NotFound(new { code = "TENANT_UPDATE_FAILED", message = "Update failed (not found, precondition, or tenant not resolved)" }); return Ok(res); } [HttpDelete("tenants/{tenantKey}")] public async Task DeleteTenant([FromRoute] string tenantKey, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var ok = await _deleteTenant.ExecuteAsync(tenantKey, ct); if (!ok) return NotFound(new { code = "TENANT_DELETE_FAILED", message = "Delete failed (not found or tenant not resolved)" }); return NoContent(); } [HttpGet("tenants")] public async Task ListTenants(CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _listTenants.ExecuteAsync(new ListTenantsRequest(), ct); if (res is null) return NotFound(new { code = "TENANT_LIST_FAILED", message = "Tenant resolve failed" }); return Ok(res); } // Domains [HttpGet("domains")] public async Task ListDomains([FromQuery] string? tenantKey, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _listDomains.ExecuteAsync(new ListDomainsRequest { TenantKey = tenantKey }, ct); if (res is null) return NotFound(new { code = "DOMAIN_LIST_FAILED", message = "Tenant resolve failed" }); return Ok(res); } [HttpPost("domains/map")] public async Task MapDomain([FromBody] MapDomainRequest body, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: body?.TenantKey); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _mapDomain.ExecuteAsync(body, ct); if (res is null) return Conflict(new { code = "DOMAIN_MAP_FAILED", message = "Map failed (tenant/domain invalid or tenant not resolved)" }); return Ok(res); } [HttpDelete("domains/{domain}")] public async Task UnmapDomain([FromRoute] string domain, [FromQuery] string? tenantKey, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _unmapDomain.ExecuteAsync(new UnmapDomainRequest { Domain = domain, TenantKey = tenantKey }, ct); if (res is null) return NotFound(new { code = "DOMAIN_UNMAP_FAILED", message = "Unmap failed (not found or tenant not resolved)" }); return NoContent(); } [HttpPost("domains/base")] public async Task AddBaseDomain([FromBody] AddBaseDomainRequest body, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _addBaseDomain.ExecuteAsync(body, ct); if (res is null) return Conflict(new { code = "BASE_DOMAIN_ADD_FAILED", message = "Add base domain failed (tenant not resolved or duplicate)" }); return Ok(res); } [HttpDelete("domains/base/{baseDomain}")] public async Task RemoveBaseDomain([FromRoute] string baseDomain, [FromQuery] string? tenantKey, CancellationToken ct) { var resolved = _resolver.Resolve(HttpContext, hint: null); if (resolved is null) return NotFound(ErrTenantResolve); var res = await _removeBaseDomain.ExecuteAsync(new RemoveBaseDomainRequest { BaseDomain = baseDomain, TenantKey = tenantKey }, ct); if (res is null) return NotFound(new { code = "BASE_DOMAIN_REMOVE_FAILED", message = "Remove base domain failed (not found or tenant not resolved)" }); return NoContent(); } }