// Copyright (c) StellaOps. All rights reserved. // Licensed under BUSL-1.1. See LICENSE in the project root. using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using StellaOps.Auth.ServerIntegration.Tenancy; using StellaOps.Platform.WebService.Contracts; using StellaOps.Platform.WebService.Options; using StellaOps.Platform.WebService.Services; using System.Collections.Generic; using System.Linq; namespace StellaOps.Platform.WebService.Endpoints; /// /// Serves GET /platform/envsettings.json — an anonymous endpoint that returns /// the Angular frontend's (matching AppConfig). /// The payload is assembled from three layers: env vars (Layer 1), YAML/JSON config (Layer 2), /// and database overrides (Layer 3) via . /// public static class EnvironmentSettingsEndpoints { public static IEndpointRouteBuilder MapEnvironmentSettingsEndpoints(this IEndpointRouteBuilder app) { // Primary route (used when accessed via gateway: /platform/envsettings.json) // and alias route (used when accessing the Platform service directly: /envsettings.json) app.MapGet("/envsettings.json", Handler) .WithTags("Environment Settings") .WithName("GetEnvironmentSettingsAlias") .WithSummary("Alias for /platform/envsettings.json (direct service access)") .Produces(StatusCodes.Status200OK) .AllowAnonymous() .ExcludeFromDescription(); app.MapGet("/platform/envsettings.json", Handler) .WithTags("Environment Settings") .WithName("GetEnvironmentSettings") .WithSummary("Returns frontend environment configuration (AppConfig)") .WithDescription( "Anonymous endpoint that returns the Angular frontend's AppConfig payload. " + "The response merges three configuration layers: environment variables (lowest), " + "YAML/JSON config, and database overrides (highest). Includes OIDC authority settings, " + "API base URLs, and optional telemetry/welcome/doctor configuration.") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status404NotFound) .AllowAnonymous(); return app; } private static async Task Handler( IOptions options, EnvironmentSettingsComposer composer, IEnvironmentSettingsStore envSettingsStore, SetupStateDetector setupDetector, CancellationToken ct) { var platform = options.Value; var env = await composer.ComposeAsync(ct); // Detect setup state from DB settings (cached, no extra round-trip) var dbSettings = await envSettingsStore.GetAllAsync(ct); var setupState = setupDetector.Detect(platform.Storage, dbSettings); var authority = new EnvironmentAuthoritySettings { Issuer = platform.Authority.Issuer, ClientId = env.ClientId, AuthorizeEndpoint = env.AuthorizeEndpoint ?? $"{platform.Authority.Issuer.TrimEnd('/')}/connect/authorize", TokenEndpoint = env.TokenEndpoint ?? $"{platform.Authority.Issuer.TrimEnd('/')}/connect/token", LogoutEndpoint = env.LogoutEndpoint, RedirectUri = env.RedirectUri, SilentRefreshRedirectUri = env.SilentRefreshRedirectUri, PostLogoutRedirectUri = env.PostLogoutRedirectUri, Scope = env.Scope, Audience = env.Audience ?? platform.Authority.Audiences.FirstOrDefault() ?? "stella-ops-api", DpopAlgorithms = env.DpopAlgorithms.Count > 0 ? env.DpopAlgorithms : null, RefreshLeewaySeconds = env.RefreshLeewaySeconds, }; EnvironmentTelemetrySettings? telemetry = null; if (!string.IsNullOrWhiteSpace(env.OtlpEndpoint) || env.TelemetrySampleRate > 0) { telemetry = new EnvironmentTelemetrySettings { OtlpEndpoint = env.OtlpEndpoint, SampleRate = env.TelemetrySampleRate, }; } EnvironmentWelcomeSettings? welcome = null; if (env.WelcomeTitle is not null || env.WelcomeMessage is not null || env.WelcomeDocsUrl is not null) { welcome = new EnvironmentWelcomeSettings { Title = env.WelcomeTitle, Message = env.WelcomeMessage, DocsUrl = env.WelcomeDocsUrl, }; } EnvironmentDoctorSettings? doctor = null; if (env.DoctorFixEnabled) { doctor = new EnvironmentDoctorSettings { FixEnabled = env.DoctorFixEnabled, }; } var response = new EnvironmentSettingsResponse { Authority = authority, ApiBaseUrls = new Dictionary(env.ApiBaseUrls), Telemetry = telemetry, Welcome = welcome, Doctor = doctor, Setup = setupState, }; return Results.Json(response, statusCode: StatusCodes.Status200OK); } }