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

@@ -13,6 +13,7 @@ using StellaOps.ExportCenter.WebService.Telemetry;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using System.Text.Json;
using static StellaOps.Localization.T;
namespace StellaOps.ExportCenter.WebService.Api;
@@ -173,7 +174,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
limit = Math.Clamp(limit == 0 ? 50 : limit, 1, 100);
offset = Math.Max(0, offset);
@@ -200,7 +201,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var profile = await profileRepo.GetByIdAsync(tenantId, profileId, cancellationToken);
if (profile is null)
@@ -220,11 +221,11 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
// Validate name uniqueness
if (!await profileRepo.IsNameUniqueAsync(tenantId, request.Name, cancellationToken: cancellationToken))
return TypedResults.Conflict($"Profile name '{request.Name}' already exists");
return TypedResults.Conflict(_t("exportcenter.error.profile_name_conflict", request.Name));
var now = timeProvider.GetUtcNow();
var profile = new ExportProfile
@@ -274,21 +275,21 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var existing = await profileRepo.GetByIdAsync(tenantId, profileId, cancellationToken);
if (existing is null)
return TypedResults.NotFound();
if (existing.Status == ExportProfileStatus.Archived)
return TypedResults.BadRequest("Cannot update archived profile");
return TypedResults.BadRequest(_t("exportcenter.error.profile_archived"));
// Validate name uniqueness if changing
if (request.Name is not null &&
!request.Name.Equals(existing.Name, StringComparison.OrdinalIgnoreCase) &&
!await profileRepo.IsNameUniqueAsync(tenantId, request.Name, profileId, cancellationToken))
{
return TypedResults.Conflict($"Profile name '{request.Name}' already exists");
return TypedResults.Conflict(_t("exportcenter.error.profile_name_conflict", request.Name));
}
var updated = existing with
@@ -334,7 +335,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var archived = await profileRepo.ArchiveAsync(tenantId, profileId, cancellationToken);
if (!archived)
@@ -368,14 +369,14 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var profile = await profileRepo.GetByIdAsync(tenantId, profileId, cancellationToken);
if (profile is null)
return TypedResults.NotFound();
if (profile.Status != ExportProfileStatus.Active)
return TypedResults.BadRequest("Profile is not active");
return TypedResults.BadRequest(_t("exportcenter.error.profile_not_active"));
var options = concurrencyOptions.Value;
@@ -472,7 +473,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
limit = Math.Clamp(limit == 0 ? 50 : limit, 1, 100);
offset = Math.Max(0, offset);
@@ -501,7 +502,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -520,7 +521,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -528,7 +529,7 @@ public static class ExportApiEndpoints
var cancelled = await runRepo.CancelAsync(tenantId, runId, cancellationToken);
if (!cancelled)
return TypedResults.BadRequest("Run cannot be cancelled in its current state");
return TypedResults.BadRequest(_t("exportcenter.error.run_cancel_invalid_state"));
await auditService.LogRunOperationAsync(
ExportAuditOperation.RunCancelled,
@@ -555,7 +556,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -582,7 +583,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -607,7 +608,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -940,7 +941,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -984,7 +985,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)
@@ -1013,7 +1014,7 @@ public static class ExportApiEndpoints
{
var tenantId = GetTenantId(user);
if (tenantId == Guid.Empty)
return TypedResults.BadRequest("Tenant ID not found in claims");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_not_found_in_claims"));
var run = await runRepo.GetByIdAsync(tenantId, runId, cancellationToken);
if (run is null)

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.Abstractions;
using StellaOps.Auth.ServerIntegration;
using static StellaOps.Localization.T;
namespace StellaOps.ExportCenter.WebService.Attestation;
@@ -72,7 +73,7 @@ public static class PromotionAttestationEndpoints
var tenantId = ResolveTenantId(tenantIdHeader, httpContext);
if (string.IsNullOrWhiteSpace(tenantId))
{
return TypedResults.BadRequest("Tenant ID is required");
return TypedResults.BadRequest(_t("exportcenter.validation.tenant_required"));
}
// Ensure request has tenant ID

View File

@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration;
using StellaOps.ExportCenter.Client.Models;
using static StellaOps.Localization.T;
namespace StellaOps.ExportCenter.WebService.AuditBundle;
@@ -124,7 +125,7 @@ public static class AuditBundleEndpoints
if (status.Status != "Completed")
{
return TypedResults.Conflict($"Bundle is not ready for download. Current status: {status.Status}");
return TypedResults.Conflict(_t("exportcenter.error.bundle_not_ready", status.Status));
}
var content = await handler.GetBundleContentAsync(bundleId, cancellationToken);

View File

@@ -7,6 +7,7 @@ using StellaOps.Auth.ServerIntegration;
using StellaOps.Policy.Exceptions.Models;
using StellaOps.Policy.Exceptions.Repositories;
using System.Security.Claims;
using static StellaOps.Localization.T;
namespace StellaOps.ExportCenter.WebService.ExceptionReport;
@@ -56,7 +57,7 @@ public static class ExceptionReportEndpoints
var tenantId = GetTenantId(user, context);
if (tenantId is null)
{
return Results.BadRequest(new { error = "Tenant ID required" });
return Results.BadRequest(new { error = _t("exportcenter.validation.tenant_required") });
}
var requesterId = user.FindFirstValue(ClaimTypes.NameIdentifier) ?? "unknown";
@@ -95,7 +96,7 @@ public static class ExceptionReportEndpoints
var tenantId = GetTenantId(user, context);
if (tenantId is null)
{
return Results.BadRequest(new { error = "Tenant ID required" });
return Results.BadRequest(new { error = _t("exportcenter.validation.tenant_required") });
}
var reports = await generator.ListReportsAsync(tenantId.Value, limit ?? 50, cancellationToken);
@@ -119,7 +120,7 @@ public static class ExceptionReportEndpoints
var content = await generator.GetReportContentAsync(jobId, cancellationToken);
if (content is null)
{
return Results.NotFound(new { error = "Report not found or not ready" });
return Results.NotFound(new { error = _t("exportcenter.error.report_not_found_or_not_ready") });
}
return Results.File(

View File

@@ -18,6 +18,7 @@ using StellaOps.ExportCenter.WebService.RiskBundle;
using StellaOps.ExportCenter.WebService.SimulationExport;
using StellaOps.ExportCenter.WebService.Telemetry;
using StellaOps.ExportCenter.WebService.Timeline;
using StellaOps.Localization;
using StellaOps.Router.AspNet;
var builder = WebApplication.CreateBuilder(args);
@@ -107,6 +108,8 @@ builder.Services.AddOpenApi();
builder.Services.AddStellaOpsTenantServices();
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
builder.Services.AddStellaOpsLocalization(builder.Configuration);
builder.Services.AddTranslationBundle(System.Reflection.Assembly.GetExecutingAssembly());
// Stella Router integration
var routerEnabled = builder.Services.AddRouterMicroservice(
@@ -125,6 +128,7 @@ if (app.Environment.IsDevelopment())
}
app.UseStellaOpsCors();
app.UseStellaOpsLocalization();
app.UseAuthentication();
app.UseAuthorization();
app.UseStellaOpsTenantMiddleware();
@@ -185,6 +189,7 @@ app.MapDelete("/exports/{id}", (string id) => Results.NoContent())
// Refresh Router endpoint cache
app.TryRefreshStellaRouterEndpoints(routerEnabled);
await app.LoadTranslationsAsync();
app.Run();
// Make Program class accessible for integration testing

View File

@@ -1,6 +1,7 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using StellaOps.Auth.ServerIntegration;
using static StellaOps.Localization.T;
namespace StellaOps.ExportCenter.WebService.RiskBundle;
@@ -125,7 +126,7 @@ public static class RiskBundleEndpoints
if (status.Status is not (RiskBundleJobStatus.Pending or RiskBundleJobStatus.Running))
{
return TypedResults.Conflict($"Job cannot be cancelled in status '{status.Status}'");
return TypedResults.Conflict(_t("exportcenter.error.job_cancel_invalid_status", status.Status));
}
var actor = httpContext.User.FindFirst("sub")?.Value
@@ -134,7 +135,7 @@ public static class RiskBundleEndpoints
var cancelled = await handler.CancelJobAsync(jobId, actor, cancellationToken);
if (!cancelled)
{
return TypedResults.Conflict("Failed to cancel job");
return TypedResults.Conflict(_t("exportcenter.error.job_cancel_failed"));
}
return TypedResults.NoContent();

View File

@@ -26,6 +26,10 @@
<ProjectReference Include="..\..\..\Policy\StellaOps.Policy.Engine\StellaOps.Policy.Engine.csproj" />
<ProjectReference Include="..\..\..\Router/__Libraries/StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj" />
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Translations\*.json" />
</ItemGroup>
<PropertyGroup Label="StellaOpsReleaseVersion">
<Version>1.0.0-alpha1</Version>

View File

@@ -0,0 +1,15 @@
{
"_meta": { "locale": "en-US", "namespace": "exportcenter", "version": "1.0" },
"exportcenter.error.report_not_found_or_not_ready": "Report not found or not ready.",
"exportcenter.error.bundle_not_ready": "Bundle is not ready for download. Current status: {0}.",
"exportcenter.error.job_cancel_invalid_status": "Job cannot be cancelled in status '{0}'.",
"exportcenter.error.job_cancel_failed": "Failed to cancel job.",
"exportcenter.error.profile_name_conflict": "Profile name '{0}' already exists.",
"exportcenter.error.profile_archived": "Cannot update archived profile.",
"exportcenter.error.profile_not_active": "Profile is not active.",
"exportcenter.error.run_cancel_invalid_state": "Run cannot be cancelled in its current state.",
"exportcenter.validation.tenant_required": "Tenant ID is required.",
"exportcenter.validation.tenant_not_found_in_claims": "Tenant ID not found in claims."
}