using System.Text.Json; using GB5Shared.DTO.Framework.Login; using Microsoft.Extensions.Logging; namespace FrameworkBLL.VersionSync; public class SqlWorkbenchVersionClient : ISqlWorkbenchVersionClient { // SqlWorkbench's own control-plane schema (SW.*) lives in the "Gb5System" database — same // convention as EntitlementBLL.Onboarding.SqlWorkbenchClient. private const string SqlWorkbenchDatabaseName = "Gb5System"; private readonly IHttpClientFactory _HttpClientFactory; private readonly ILogger _Logger; public SqlWorkbenchVersionClient(IHttpClientFactory httpClientFactory, ILogger logger) { _HttpClientFactory = httpClientFactory; _Logger = logger; } public async Task GetCurrentVersionAsync(int clientDbId, LoginDTO swLogin, CancellationToken ct) { var client = CreateClient(swLogin); var response = await client.GetAsync($"/Provisioning/GetCurrentVersion?ClientDbId={clientDbId}", ct).ConfigureAwait(false); var raw = await ReadAndEnsureSuccessAsync(response, "GetCurrentVersion", ct).ConfigureAwait(false); return ParseBody(raw); } public async Task GetClientDatabaseAsync(int clientDbId, LoginDTO swLogin, CancellationToken ct) { var client = CreateClient(swLogin); var response = await client.GetAsync($"/ClientDatabase/GetClientDatabaseById?ClientDbId={clientDbId}", ct).ConfigureAwait(false); var raw = await ReadAndEnsureSuccessAsync(response, "GetClientDatabaseById", ct).ConfigureAwait(false); return ParseBody(raw); } // ── Helpers ────────────────────────────────────────────────────────────── private HttpClient CreateClient(LoginDTO eventLogin) { var client = _HttpClientFactory.CreateClient("SqlWorkbench"); // Forward the exact Login that already succeeded moments earlier inside SqlWorkbench's // own ChangeRequestBLL.Execute() for this ClientDatabaseId (embedded, unencoded, in the // sqlworkbench.changerequest.applied event by EventLogPublish) — this subscriber has no // HTTP session/request of its own to build a Login from, and reusing the event's own // Login guarantees the TenantId scoping ClientDatabaseDAL.GetById filters on will match. // Only DatabaseName is forced, same reasoning as SqlWorkbenchClient.CreateClient. var swLogin = new LoginDTO { ClientId = eventLogin.ClientId, UserId = eventLogin.UserId, DatabaseName = SqlWorkbenchDatabaseName, }; client.DefaultRequestHeaders.Remove("Login"); client.DefaultRequestHeaders.Add("Login", JsonSerializer.Serialize(swLogin)); return client; } private async Task ReadAndEnsureSuccessAsync(HttpResponseMessage response, string operation, CancellationToken ct) { var raw = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { _Logger.LogError("SqlWorkbench {Operation} returned {StatusCode}: {Body}", operation, response.StatusCode, raw); throw new InvalidOperationException($"SqlWorkbench {operation} failed ({(int)response.StatusCode}): {ExtractErrorBody(raw) ?? raw}"); } var errorBody = ExtractErrorBody(raw); if (!string.IsNullOrEmpty(errorBody)) { _Logger.LogError("SqlWorkbench {Operation} returned 200 with an ErrorBody: {ErrorBody}", operation, errorBody); throw new InvalidOperationException($"SqlWorkbench {operation} failed: {errorBody}"); } return raw; } private static string? ExtractErrorBody(string rawJson) { try { using var doc = JsonDocument.Parse(rawJson); if (doc.RootElement.TryGetProperty("ErrorBody", out var err) && err.ValueKind == JsonValueKind.String) return err.GetString(); } catch { /* not JSON, or no ErrorBody property — treated as success by the caller */ } return null; } private static T? ParseBody(string rawJson) where T : class { using var doc = JsonDocument.Parse(rawJson); if (!doc.RootElement.TryGetProperty("Body", out var body) || body.ValueKind != JsonValueKind.Object) return null; return body.Deserialize(new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); } }