using AmpDAL.DTO.RegisterApi; using AmpDAL.Query.RegisterApi; using GB5Shared.DTO.Framework.Login; using GB5Shared.QueryExecutor; using Microsoft.Extensions.Logging; namespace ApiRegistrationTool; /// /// Direct SQL UPDATE service for refreshing existing MAPI rows. /// Used when an endpoint is already registered but its DTO or description changed. /// /// Bypasses the BLL (which only supports INSERT) and writes UPDATE SQL directly /// via IQueryExecutor — the same infrastructure used by all GB5 DAL classes. /// public sealed class ApiUpdateService { private readonly IQueryExecutor _qe; private readonly ILogger _log; public ApiUpdateService(IQueryExecutor qe, ILogger log) { _qe = qe; _log = log; } // ── UPDATE MAPI master description + governance fields ──────────────── private const string SQL_UPDATE_MAPI = @" UPDATE MAPI SET DESCRIPTION = @Description, ABOUTAPI = @AboutApi, KEYFEATURES = @KeyFeatures, USECASES = @UseCases, QUICKSTART = @QuickStart, OWNERTEAM = @OwnerTeam, BUSINESSOWNER = @BusinessOwner, REPOURL = @RepoUrl, PIPELINEREF = @PipelineRef, GEOGRAPHY = @Geography, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── UPDATE MAPIVERSION lifecycle dates ───────────────────────────────── private const string SQL_UPDATE_VERSION = @" UPDATE MAPIVERSION SET TODATE = @VersionToDate, SUNSETDATE = @SunsetDate, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── UPDATE MAPIINTERFACE path, description and parse metadata ───────── private const string SQL_UPDATE_INTERFACE = @" UPDATE MAPIINTERFACE SET INTERFACEDESCRIPTION = @InterfaceDescription, APIPATH = @ApiPath, METHOD = @InterfaceMethod, CONTENTHASH = @ContentHash, LASTPARSEDAT = @LastParsedAt, PARSEDSUCCESSFULLY = @ParsedSuccessfully, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── UPDATE MAPISCHEMA RequestJson + ResponseJson (core of every re-run) ─ private const string SQL_UPDATE_SCHEMA = @" UPDATE MAPISCHEMA SET REQUESTJSON = @RequestJson, RESPONSEJSON = @ResponseJson, SCHEMAJSON = @SchemaJson, SCHEMADESCRIPTION= @SchemaDescription, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── UPDATE MAPIOPERATION summary, path and remarks ───────────────────── private const string SQL_UPDATE_OPERATION = @" UPDATE MAPIOPERATION SET RELATIVEPATH = @OperationPath, SUMMARY = @OperationSummary, REMARKS = @OperationRemarks, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── UPDATE MAPIPARAMETER remarks ──────────────────────────────────────── private const string SQL_UPDATE_PARAMETER = @" UPDATE MAPIPARAMETER SET REMARKS = @ParameterRemarks, DATATYPE = @ParameterDataType, INLOCATION = @ParameterInLocation, MODIFIEDBYID = @UserId, MODIFIEDON = GETDATE() WHERE APIID = @ApiId AND TENANTID = @TenantId"; // ── Batched: MAPI + APIDOMAINID + MAPIVERSION + MAPIINTERFACE + MAPISCHEMA // in a single round-trip (was 5 separate ExecuteAsync calls) ───────── private const string SQL_UPDATE_CORE_BATCH = SQL_UPDATE_MAPI + ";" + RegisterApiQB.UPDATE_API_DOMAIN_ID + ";" + SQL_UPDATE_VERSION + ";" + SQL_UPDATE_INTERFACE + ";" + SQL_UPDATE_SCHEMA; // ── Batched: MAPIOPERATION + MAPIPARAMETER (optional, may not exist) ── private const string SQL_UPDATE_OPTIONAL_BATCH = SQL_UPDATE_OPERATION + ";" + SQL_UPDATE_PARAMETER; // ── SELECT to check if this ApiPath already has a record ───────────── private const string SQL_GET_APIID_BY_PATH = @" SELECT TOP 1 A.APIID FROM MAPI A JOIN MAPIINTERFACE I ON I.APIID = A.APIID AND I.TENANTID = A.TENANTID WHERE I.APIPATH = @ApiPath AND A.TENANTID = @TenantId AND A.STATUS = 1"; // ═════════════════════════════════════════════════════════════════════ // PUBLIC METHODS // ═════════════════════════════════════════════════════════════════════ /// /// Looks up an existing APIID by ApiPath. Returns -1 if not found. /// public async Task GetApiIdByPathAsync(string apiPath, int tenantId, LoginDTO login, CancellationToken ct) { try { var result = await _qe.QuerySingleAsync( login, SQL_GET_APIID_BY_PATH, new { ApiPath = apiPath, TenantId = tenantId }, cancellationToken: ct).ConfigureAwait(false); return result ?? -1; } catch { return -1; } } /// /// Updates all description + schema + interface + version + parameter /// fields for an existing API, and repoints MAPI.APIDOMAINID at the /// (possibly shared) domain id resolved by the caller. /// public async Task UpdateAsync( RegisterApiSaveDTO dto, int apiId, int tenantId, int apiDomainId, LoginDTO login, CancellationToken ct) { int userId = login.UserId; // Core fields (MAPI, APIDOMAINID, MAPIVERSION, MAPIINTERFACE, MAPISCHEMA) // in one round-trip — all statements share compatible parameter names. await _qe.ExecuteAsync(login, SQL_UPDATE_CORE_BATCH, new { ApiId = apiId, TenantId = tenantId, dto.Description, dto.AboutApi, dto.KeyFeatures, dto.UseCases, dto.QuickStart, dto.OwnerTeam, dto.BusinessOwner, dto.RepoUrl, dto.PipelineRef, dto.Geography, ApiDomainId = apiDomainId, dto.VersionToDate, dto.SunsetDate, dto.InterfaceDescription, dto.ApiPath, dto.InterfaceMethod, dto.ContentHash, dto.LastParsedAt, dto.ParsedSuccessfully, dto.RequestJson, dto.ResponseJson, dto.SchemaJson, SchemaDescription = dto.SchemaDescription, UserId = userId }).ConfigureAwait(false); // Operation/Parameter rows are optional (only present when the endpoint // has query params or a request body) — log and continue if missing. try { await _qe.ExecuteAsync(login, SQL_UPDATE_OPTIONAL_BATCH, new { ApiId = apiId, TenantId = tenantId, dto.OperationPath, dto.OperationSummary, dto.OperationRemarks, dto.ParameterRemarks, dto.ParameterDataType, dto.ParameterInLocation, UserId = userId }).ConfigureAwait(false); } catch (Exception ex) { _log.LogWarning(ex, "Operation/Parameter update failed for ApiId {ApiId}", apiId); } } }