using Admin.NET.Plugin.AiDOP.Infrastructure.S8; using Admin.NET.Plugin.AiDOP.Const.S8; using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Infrastructure; using Admin.NET.Plugin.AiDOP.Service.S8; namespace Admin.NET.Plugin.AiDOP.Controllers.S8; [ApiController] [Route("api/aidop/s8/config/data-sources")] [NonUnify] public class AdoS8ConfigDataSourcesController : ControllerBase { private readonly S8DataSourceService _svc; private readonly S8TrustedScopeResolver _scope; public AdoS8ConfigDataSourcesController(S8DataSourceService svc, S8TrustedScopeResolver scope) { _svc = svc; _scope = scope; } [HttpGet] [S8Permission(S8PermissionCatalog.ConfigRead)] public async Task ListAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) { // Compatibility-only. Security scope is resolved server-side. _ = tenantId; _ = factoryId; var scope = await _scope.ResolveAsync(); return Ok(await _svc.ListAsync(scope.TenantId, scope.FactoryId)); } // ================================================================================ // S8-LEGACY-RUNTIME-RETIREMENT-1:POST / PUT / DELETE / POST test 已退役 → 410 Gone。 // // 410 而非 400/403/404 是刻意选择:语义为「该能力曾经存在、现已永久退役」。 // 400 会暗示「改对入参还能存」,404 会暗示「换个 Id 还能存」,都与事实不符。 // // ⚠️ catch 顺序:S8WriteRetiredException 必须排在 S8NotFoundException / S8BizException 之前, // 否则会被基类分支先接住、降级成 404/400。 // 返回固定文案常量(不是 ex.ToString()/堆栈/类型名),避免泄漏内部实现。 // GET 保持不变,存量 Legacy 规则绑定的数据源行仍可读(endpoint 已脱敏)。 // // 作用域改用 RetiredWriteScope 占位而非 await _scope.ResolveAsync(): // 能力既已退役,就不该为一个注定 410 的请求再去解析租户上下文。 // Service 写方法必须在读取该占位值或访问任何仓储前抛 S8WriteRetiredException。 // ================================================================================ private static readonly S8TrustedScope RetiredWriteScope = new(0, 0); private IActionResult Retired() => StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status410Gone, new { message = S8DataSourceService.RetiredMessage }); [HttpPost] [S8Permission(S8PermissionCatalog.ConfigDataSource)] public async Task CreateAsync([FromBody] AdoS8DataSource body) { try { return Ok(await _svc.CreateAsync(body, RetiredWriteScope)); } catch (S8WriteRetiredException) { return Retired(); } catch (S8NotFoundException) { return NotFound(); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPut("{id:long}")] [S8Permission(S8PermissionCatalog.ConfigDataSource)] public async Task UpdateAsync(long id, [FromBody] AdoS8DataSource body) { try { return Ok(await _svc.UpdateAsync(id, body, RetiredWriteScope)); } catch (S8WriteRetiredException) { return Retired(); } catch (S8NotFoundException) { return NotFound(); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpDelete("{id:long}")] [S8Permission(S8PermissionCatalog.ConfigDataSource)] public async Task DeleteAsync(long id) { try { await _svc.DeleteAsync(id, RetiredWriteScope); } catch (S8WriteRetiredException) { return Retired(); } catch (S8NotFoundException) { return NotFound(); } return Ok(); } [HttpPost("{id:long}/test")] [S8Permission(S8PermissionCatalog.ConfigDataSource)] public async Task TestAsync(long id) { try { return Ok(await _svc.TestAsync(id, RetiredWriteScope)); } catch (S8WriteRetiredException) { return Retired(); } catch (S8NotFoundException) { return NotFound(); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } }