// SPDX-License-Identifier: BUSL-1.1 // Copyright (c) 2025 StellaOps using Microsoft.Extensions.Options; using StellaOps.Platform.WebService.Options; namespace StellaOps.Platform.WebService.Services; /// /// Composes a final from three layers: /// /// Environment variables (via ) /// YAML/JSON configuration (standard IOptions binding) /// Database (via ) /// /// Layer 3 (DB) has highest priority and is overlaid last. /// public sealed class EnvironmentSettingsComposer { private readonly IOptionsMonitor _optionsMonitor; private readonly IEnvironmentSettingsStore _store; public EnvironmentSettingsComposer( IOptionsMonitor optionsMonitor, IEnvironmentSettingsStore store) { _optionsMonitor = optionsMonitor ?? throw new ArgumentNullException(nameof(optionsMonitor)); _store = store ?? throw new ArgumentNullException(nameof(store)); } /// /// Returns the merged environment settings with all three layers applied. /// public async Task ComposeAsync(CancellationToken ct = default) { var source = _optionsMonitor.CurrentValue.EnvironmentSettings; // Clone the options so we don't mutate the originals var merged = new PlatformEnvironmentSettingsOptions { ClientId = source.ClientId, AuthorizeEndpoint = source.AuthorizeEndpoint, TokenEndpoint = source.TokenEndpoint, LogoutEndpoint = source.LogoutEndpoint, RedirectUri = source.RedirectUri, PostLogoutRedirectUri = source.PostLogoutRedirectUri, Scope = source.Scope, Audience = source.Audience, DpopAlgorithms = new List(source.DpopAlgorithms), RefreshLeewaySeconds = source.RefreshLeewaySeconds, ApiBaseUrls = new Dictionary(source.ApiBaseUrls, StringComparer.OrdinalIgnoreCase), OtlpEndpoint = source.OtlpEndpoint, TelemetrySampleRate = source.TelemetrySampleRate, WelcomeTitle = source.WelcomeTitle, WelcomeMessage = source.WelcomeMessage, WelcomeDocsUrl = source.WelcomeDocsUrl, DoctorFixEnabled = source.DoctorFixEnabled, }; // Overlay DB values (Layer 3 — highest priority) var dbSettings = await _store.GetAllAsync(ct).ConfigureAwait(false); foreach (var (key, value) in dbSettings) { if (key.StartsWith("ApiBaseUrls:", StringComparison.OrdinalIgnoreCase)) { var serviceName = key["ApiBaseUrls:".Length..]; merged.ApiBaseUrls[serviceName] = value; } else { // Map scalar settings by key name ApplyScalarSetting(merged, key, value); } } return merged; } private static void ApplyScalarSetting(PlatformEnvironmentSettingsOptions options, string key, string value) { // Case-insensitive key matching for known scalar settings if (string.Equals(key, "ClientId", StringComparison.OrdinalIgnoreCase)) options.ClientId = value; else if (string.Equals(key, "AuthorizeEndpoint", StringComparison.OrdinalIgnoreCase)) options.AuthorizeEndpoint = value; else if (string.Equals(key, "TokenEndpoint", StringComparison.OrdinalIgnoreCase)) options.TokenEndpoint = value; else if (string.Equals(key, "LogoutEndpoint", StringComparison.OrdinalIgnoreCase)) options.LogoutEndpoint = value; else if (string.Equals(key, "RedirectUri", StringComparison.OrdinalIgnoreCase)) options.RedirectUri = value; else if (string.Equals(key, "PostLogoutRedirectUri", StringComparison.OrdinalIgnoreCase)) options.PostLogoutRedirectUri = value; else if (string.Equals(key, "Scope", StringComparison.OrdinalIgnoreCase)) options.Scope = value; else if (string.Equals(key, "Audience", StringComparison.OrdinalIgnoreCase)) options.Audience = value; else if (string.Equals(key, "OtlpEndpoint", StringComparison.OrdinalIgnoreCase)) options.OtlpEndpoint = value; else if (string.Equals(key, "TelemetrySampleRate", StringComparison.OrdinalIgnoreCase) && double.TryParse(value, out var rate)) options.TelemetrySampleRate = rate; else if (string.Equals(key, "WelcomeTitle", StringComparison.OrdinalIgnoreCase)) options.WelcomeTitle = value; else if (string.Equals(key, "WelcomeMessage", StringComparison.OrdinalIgnoreCase)) options.WelcomeMessage = value; else if (string.Equals(key, "WelcomeDocsUrl", StringComparison.OrdinalIgnoreCase)) options.WelcomeDocsUrl = value; else if (string.Equals(key, "DoctorFixEnabled", StringComparison.OrdinalIgnoreCase) && bool.TryParse(value, out var fix)) options.DoctorFixEnabled = fix; } }