search and ai stabilization work, localization stablized.

This commit is contained in:
master
2026-02-24 23:29:36 +02:00
parent 4f947a8b61
commit b07d27772e
766 changed files with 55299 additions and 3221 deletions

View File

@@ -3,10 +3,12 @@ using Microsoft.AspNetCore.Authorization;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using StellaOps.Auth.ServerIntegration.Tenancy;
using StellaOps.Localization;
using StellaOps.Graph.Api.Contracts;
using StellaOps.Graph.Api.Security;
using StellaOps.Graph.Api.Services;
using StellaOps.Router.AspNet;
using static StellaOps.Localization.T;
var builder = WebApplication.CreateBuilder(args);
@@ -59,6 +61,34 @@ builder.Services.AddAuthorization(options =>
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddStellaOpsLocalization(builder.Configuration, options =>
{
options.DefaultLocale = string.IsNullOrWhiteSpace(options.DefaultLocale) ? "en-US" : options.DefaultLocale;
if (options.SupportedLocales.Count == 0)
{
options.SupportedLocales.Add("en-US");
}
if (!options.SupportedLocales.Contains("de-DE", StringComparer.OrdinalIgnoreCase))
{
options.SupportedLocales.Add("de-DE");
}
if (string.IsNullOrWhiteSpace(options.RemoteBundleUrl))
{
var platformUrl = builder.Configuration["STELLAOPS_PLATFORM_URL"] ?? builder.Configuration["Platform:BaseUrl"];
if (!string.IsNullOrWhiteSpace(platformUrl))
{
options.RemoteBundleUrl = platformUrl;
}
}
options.EnableRemoteBundles =
options.EnableRemoteBundles || !string.IsNullOrWhiteSpace(options.RemoteBundleUrl);
});
builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly());
builder.Services.AddRemoteTranslationBundles();
// Stella Router integration
var routerEnabled = builder.Services.AddRouterMicroservice(
builder.Configuration,
@@ -71,12 +101,15 @@ var app = builder.Build();
app.LogStellaOpsLocalHostname("graph");
app.UseStellaOpsCors();
app.UseStellaOpsLocalization();
app.UseRouting();
app.TryUseStellaRouter(routerEnabled);
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
await app.LoadTranslationsAsync();
app.MapPost("/graph/search", async (HttpContext context, GraphSearchRequest request, IGraphSearchService service, CancellationToken ct) =>
{
var sw = System.Diagnostics.Stopwatch.StartNew();
@@ -318,7 +351,11 @@ app.MapGet("/graph/export/{jobId}", async (string jobId, HttpContext context, IG
if (job is null || !string.Equals(job.Tenant, auth.TenantId, StringComparison.Ordinal))
{
LogAudit(context, "/graph/export/download", StatusCodes.Status404NotFound, sw.ElapsedMilliseconds);
return Results.NotFound(new ErrorResponse { Error = "GRAPH_EXPORT_NOT_FOUND", Message = "Export job not found" });
return Results.NotFound(new ErrorResponse
{
Error = "GRAPH_EXPORT_NOT_FOUND",
Message = _t("graph.error.export_not_found")
});
}
context.Response.Headers.ContentLength = job.Payload.Length;
@@ -372,7 +409,11 @@ app.MapGet("/graph/edges/{edgeId}/metadata", async (string edgeId, HttpContext c
if (result is null)
{
LogAudit(context, "/graph/edges/metadata", StatusCodes.Status404NotFound, sw.ElapsedMilliseconds);
return Results.NotFound(new ErrorResponse { Error = "EDGE_NOT_FOUND", Message = $"Edge '{edgeId}' not found" });
return Results.NotFound(new ErrorResponse
{
Error = "EDGE_NOT_FOUND",
Message = _tn("graph.error.edge_not_found", ("edgeId", edgeId))
});
}
LogAudit(context, "/graph/edges/metadata", StatusCodes.Status200OK, sw.ElapsedMilliseconds);
@@ -419,7 +460,11 @@ app.MapGet("/graph/edges/by-reason/{reason}", async (string reason, int? limit,
if (!Enum.TryParse<EdgeReason>(reason, ignoreCase: true, out var edgeReason))
{
LogAudit(context, "/graph/edges/by-reason", StatusCodes.Status400BadRequest, sw.ElapsedMilliseconds);
return Results.BadRequest(new ErrorResponse { Error = "INVALID_REASON", Message = $"Unknown edge reason: {reason}" });
return Results.BadRequest(new ErrorResponse
{
Error = "INVALID_REASON",
Message = _tn("graph.error.invalid_reason", ("reason", reason))
});
}
var response = await service.QueryByReasonAsync(auth.TenantId!, edgeReason, limit ?? 100, cursor, ct);
@@ -452,7 +497,7 @@ app.MapGet("/graph/edges/by-evidence", async (string evidenceType, string eviden
app.MapGet("/healthz", () => Results.Ok(new { status = "ok" }));
app.TryRefreshStellaRouterEndpoints(routerEnabled);
app.Run();
await app.RunAsync().ConfigureAwait(false);
static async Task WriteError(HttpContext ctx, int status, string code, string message, CancellationToken ct)
{
@@ -487,7 +532,12 @@ static async Task<(bool Allowed, string? TenantId)> AuthorizeTenantRequestAsync(
var authResult = await context.AuthenticateAsync(GraphHeaderAuthenticationHandler.SchemeName);
if (!authResult.Succeeded || authResult.Principal?.Identity?.IsAuthenticated != true)
{
await WriteError(context, StatusCodes.Status401Unauthorized, "GRAPH_UNAUTHORIZED", "Missing Authorization header", ct);
await WriteError(
context,
StatusCodes.Status401Unauthorized,
"GRAPH_UNAUTHORIZED",
_t("graph.error.unauthorized_missing_auth"),
ct);
return (false, null);
}
@@ -495,7 +545,12 @@ static async Task<(bool Allowed, string? TenantId)> AuthorizeTenantRequestAsync(
if (!RateLimit(context, route))
{
await WriteError(context, StatusCodes.Status429TooManyRequests, "GRAPH_RATE_LIMITED", "Too many requests", ct);
await WriteError(
context,
StatusCodes.Status429TooManyRequests,
"GRAPH_RATE_LIMITED",
_t("graph.error.rate_limited"),
ct);
LogAudit(context, route, StatusCodes.Status429TooManyRequests, elapsedMs);
return (false, null);
}
@@ -514,8 +569,8 @@ static async Task<(bool Allowed, string? TenantId)> AuthorizeTenantRequestAsync(
static string TranslateTenantResolutionError(string? tenantError)
{
return string.Equals(tenantError, "tenant_conflict", StringComparison.Ordinal)
? "Conflicting tenant context"
: $"Missing {StellaOpsHttpHeaderNames.Tenant} header";
? _t("graph.error.tenant_conflict")
: _tn("graph.error.tenant_missing_header", ("header", StellaOpsHttpHeaderNames.Tenant));
}
static bool RateLimit(HttpContext ctx, string route)