stabilizaiton work - projects rework for maintenanceability and ui livening
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
// Copyright (c) 2025 StellaOps
|
||||
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.Platform.WebService.Services;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// Admin endpoints for managing DB-layer environment settings (Layer 3).
|
||||
/// All endpoints require <c>SetupRead</c> or <c>SetupAdmin</c> authorization.
|
||||
/// </summary>
|
||||
public static class EnvironmentSettingsAdminEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapEnvironmentSettingsAdminEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/platform/envsettings/db")
|
||||
.WithTags("Environment Settings Admin");
|
||||
|
||||
group.MapGet("/", async (IEnvironmentSettingsStore store, CancellationToken ct) =>
|
||||
{
|
||||
var all = await store.GetAllAsync(ct);
|
||||
return Results.Ok(all);
|
||||
})
|
||||
.WithName("ListDbEnvironmentSettings")
|
||||
.WithSummary("List all DB-layer environment settings")
|
||||
.Produces<IReadOnlyDictionary<string, string>>(StatusCodes.Status200OK)
|
||||
.RequireAuthorization(PlatformPolicies.SetupRead);
|
||||
|
||||
group.MapPut("/{key}", async (string key, SettingValueRequest body, IEnvironmentSettingsStore store, CancellationToken ct) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return Results.BadRequest(new { error = "Key must not be empty." });
|
||||
|
||||
if (string.IsNullOrWhiteSpace(body.Value))
|
||||
return Results.BadRequest(new { error = "Value must not be empty." });
|
||||
|
||||
await store.SetAsync(key, body.Value, body.UpdatedBy ?? "admin", ct);
|
||||
return Results.Ok(new { key, value = body.Value });
|
||||
})
|
||||
.WithName("UpsertDbEnvironmentSetting")
|
||||
.WithSummary("Create or update a DB-layer environment setting")
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization(PlatformPolicies.SetupAdmin);
|
||||
|
||||
group.MapDelete("/{key}", async (string key, IEnvironmentSettingsStore store, CancellationToken ct) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
return Results.BadRequest(new { error = "Key must not be empty." });
|
||||
|
||||
await store.DeleteAsync(key, ct);
|
||||
return Results.NoContent();
|
||||
})
|
||||
.WithName("DeleteDbEnvironmentSetting")
|
||||
.WithSummary("Delete a DB-layer environment setting")
|
||||
.Produces(StatusCodes.Status204NoContent)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.RequireAuthorization(PlatformPolicies.SetupAdmin);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
public sealed record SettingValueRequest(string Value, string? UpdatedBy = null);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// 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.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;
|
||||
|
||||
/// <summary>
|
||||
/// Serves <c>GET /platform/envsettings.json</c> — an anonymous endpoint that returns
|
||||
/// the Angular frontend's <see cref="EnvironmentSettingsResponse"/> (matching <c>AppConfig</c>).
|
||||
/// The payload is assembled from three layers: env vars (Layer 1), YAML/JSON config (Layer 2),
|
||||
/// and database overrides (Layer 3) via <see cref="EnvironmentSettingsComposer"/>.
|
||||
/// </summary>
|
||||
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<EnvironmentSettingsResponse>(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<EnvironmentSettingsResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.AllowAnonymous();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static async Task<IResult> Handler(
|
||||
IOptions<PlatformServiceOptions> 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}/connect/authorize",
|
||||
TokenEndpoint = env.TokenEndpoint
|
||||
?? $"{platform.Authority.Issuer}/connect/token",
|
||||
LogoutEndpoint = env.LogoutEndpoint,
|
||||
RedirectUri = env.RedirectUri,
|
||||
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<string, string>(env.ApiBaseUrls),
|
||||
Telemetry = telemetry,
|
||||
Welcome = welcome,
|
||||
Doctor = doctor,
|
||||
Setup = setupState,
|
||||
};
|
||||
|
||||
return Results.Json(response, statusCode: StatusCodes.Status200OK);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user