using FrameworkBLL.DBLevelSetting; using FrameworkBLL.ReportOrchestration; using GB5Shared.DTO.Framework.Login; using GB5Shared.Enums.ReportOrchestration; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; namespace FrameworkBLL.VersionSync; public class VersionSyncBLL : IVersionSyncBLL { private readonly ISqlWorkbenchVersionClient _SwClient; private readonly IServerConfigCache _ServerConfigCache; private readonly IDBLevelSettingBLL _DBLevelSettingBLL; private readonly ILogger _Logger; public VersionSyncBLL( ISqlWorkbenchVersionClient swClient, IServerConfigCache serverConfigCache, IDBLevelSettingBLL dbLevelSettingBLL, ILogger logger) { _SwClient = swClient; _ServerConfigCache = serverConfigCache; _DBLevelSettingBLL = dbLevelSettingBLL; _Logger = logger; } public async Task SyncVersionAsync(int clientDatabaseId, LoginDTO eventLogin, CancellationToken ct) { using var section = GB5Trace.BeginSection("version-sync"); GB5Trace.Tag("gb5.clientdatabase.id", clientDatabaseId); try { var rollup = await _SwClient.GetCurrentVersionAsync(clientDatabaseId, eventLogin, ct).ConfigureAwait(false); if (rollup is null || !rollup.IsVersioned || string.IsNullOrEmpty(rollup.CurrentReleaseVersion)) { // Legitimate state, not an error — no release-boundary package has been // applied to this tenant yet (e.g. mid-development, pre-first-release). _Logger.LogInformation( "VersionSync: ClientDatabaseId={ClientDatabaseId} is unversioned — nothing to write", clientDatabaseId); return; } var clientDb = await _SwClient.GetClientDatabaseAsync(clientDatabaseId, eventLogin, ct).ConfigureAwait(false); if (clientDb?.ServerConfigId is not int serverConfigId) { // Legitimate state — this ClientDatabaseId has never been registered into // MSERVERCONFIG (IServerConfigBLL.RegisterDatabaseAsync), so there is no GB5 // connection to write to yet. _Logger.LogInformation( "VersionSync: ClientDatabaseId={ClientDatabaseId} has no ServerConfigId registered — skipping", clientDatabaseId); return; } GB5Trace.Step("resolve-tenant-connection", new { serverConfigId }); var config = await _ServerConfigCache.GetAsync(serverConfigId, ct).ConfigureAwait(false); string connStr = BuildConnectionString(config); int rows = await _DBLevelSettingBLL.UpdateBuildVersionOnConnection( connStr, (byte)config.ProviderType, rollup.CurrentReleaseVersion, ct).ConfigureAwait(false); _Logger.LogInformation( "VersionSync: ClientDatabaseId={ClientDatabaseId} ServerConfigId={ServerConfigId} BUILDVERSION set to {Version} ({Rows} row(s))", clientDatabaseId, serverConfigId, rollup.CurrentReleaseVersion, rows); } catch (Exception ex) { GB5Trace.MarkFailed("version-sync-failed", ex); _Logger.LogError(ex, "VersionSync failed for ClientDatabaseId={ClientDatabaseId}", clientDatabaseId); throw; } } // Same connection-string shape as ReportConnectionResolver.BuildDto — duplicated rather // than shared, since that method is private to a report-critical class this change has no // reason to touch. private static string BuildConnectionString(CachedServerConfig cfg) => cfg.ProviderType switch { DatabaseProviderType.SqlServer => $"Data Source={cfg.ServerIp};" + $"Initial Catalog={cfg.DatabaseName};" + $"User Id={cfg.DatabaseUserName};" + $"Password={cfg.DatabasePassword};" + "Pooling=true;Connection Timeout=120;Max Pool Size=250;Connection Lifetime=20;" + "Encrypt=False;TrustServerCertificate=True", DatabaseProviderType.PostgreSql => $"Host={cfg.ServerIp};Port=5432;" + $"Database={cfg.DatabaseName};" + $"Username={cfg.DatabaseUserName.ToLower()};" + $"Password={cfg.DatabasePassword};" + "Pooling=true;Timeout=120;Maximum Pool Size=250", _ => throw new NotSupportedException( $"Provider {cfg.ProviderType} is not supported for MDBLevelSetting writes."), }; }