Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.

This commit is contained in:
StellaOps Bot
2025-12-26 21:54:17 +02:00
parent 335ff7da16
commit c2b9cd8d1f
3717 changed files with 264714 additions and 48202 deletions

View File

@@ -0,0 +1,122 @@
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.Mvc;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace StellaOps.Router.Gateway.OpenApi;
/// <summary>
/// Endpoints for serving OpenAPI documentation.
/// </summary>
public static class OpenApiEndpoints
{
private static readonly ISerializer YamlSerializer = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();
/// <summary>
/// Maps OpenAPI endpoints to the application.
/// </summary>
public static IEndpointRouteBuilder MapRouterOpenApiEndpoints(this IEndpointRouteBuilder endpoints)
{
endpoints.MapGet("/.well-known/openapi", GetOpenApiDiscovery)
.ExcludeFromDescription();
endpoints.MapGet("/openapi.json", GetOpenApiJson)
.ExcludeFromDescription();
endpoints.MapGet("/openapi.yaml", GetOpenApiYaml)
.ExcludeFromDescription();
return endpoints;
}
private static IResult GetOpenApiDiscovery(
[FromServices] IRouterOpenApiDocumentCache cache,
HttpContext context)
{
var (_, etag, generatedAt) = cache.GetDocument();
var discovery = new
{
openapi_json = "/openapi.json",
openapi_yaml = "/openapi.yaml",
etag,
generated_at = generatedAt.ToString("O")
};
context.Response.Headers.CacheControl = "public, max-age=60";
return Results.Ok(discovery);
}
private static IResult GetOpenApiJson(
[FromServices] IRouterOpenApiDocumentCache cache,
HttpContext context)
{
var (documentJson, etag, _) = cache.GetDocument();
// Check If-None-Match header
if (context.Request.Headers.TryGetValue("If-None-Match", out var ifNoneMatch))
{
if (ifNoneMatch == etag)
{
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=60";
return Results.StatusCode(304);
}
}
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=60";
return Results.Content(documentJson, "application/json; charset=utf-8");
}
private static IResult GetOpenApiYaml(
[FromServices] IRouterOpenApiDocumentCache cache,
HttpContext context)
{
var (documentJson, etag, _) = cache.GetDocument();
// Check If-None-Match header
if (context.Request.Headers.TryGetValue("If-None-Match", out var ifNoneMatch))
{
if (ifNoneMatch == etag)
{
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=60";
return Results.StatusCode(304);
}
}
// Convert JSON to YAML
var jsonNode = JsonNode.Parse(documentJson);
var yamlContent = ConvertToYaml(jsonNode);
context.Response.Headers.ETag = etag;
context.Response.Headers.CacheControl = "public, max-age=60";
return Results.Content(yamlContent, "application/yaml; charset=utf-8");
}
private static string ConvertToYaml(JsonNode? node)
{
if (node is null)
return string.Empty;
var obj = ConvertJsonNodeToObject(node);
return YamlSerializer.Serialize(obj);
}
private static object? ConvertJsonNodeToObject(JsonNode? node)
{
return node switch
{
null => null,
JsonObject obj => obj.ToDictionary(
kvp => kvp.Key,
kvp => ConvertJsonNodeToObject(kvp.Value)),
JsonArray arr => arr.Select(ConvertJsonNodeToObject).ToList(),
JsonValue val => val.GetValue<object>(),
_ => null
};
}
}