Frontend gaps fill work. Testing fixes work. Auditing in progress.

This commit is contained in:
StellaOps Bot
2025-12-30 01:22:58 +02:00
parent 1dc4bcbf10
commit 7a5210e2aa
928 changed files with 183942 additions and 3941 deletions

View File

@@ -29,8 +29,21 @@ internal static class OfflineKitEndpoints
.MapGroup("/api/offline-kit")
.WithTags("Offline Kit");
// Sprint 026: OFFLINE-012 - Legacy v1 alias for backward compatibility
var v1Group = endpoints
.MapGroup("/api/v1/offline-kit")
.WithTags("Offline Kit");
MapEndpointsToGroup(group, isLegacy: false);
MapEndpointsToGroup(v1Group, isLegacy: true);
}
private static void MapEndpointsToGroup(RouteGroupBuilder group, bool isLegacy)
{
var suffix = isLegacy ? ".v1" : "";
group.MapPost("/import", HandleImportAsync)
.WithName("scanner.offline-kit.import")
.WithName($"scanner.offline-kit.import{suffix}")
.RequireAuthorization(ScannerPolicies.OfflineKitImport)
.Produces<OfflineKitImportResponseTransport>(StatusCodes.Status202Accepted)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest)
@@ -38,11 +51,26 @@ internal static class OfflineKitEndpoints
.Produces<ProblemDetails>(StatusCodes.Status422UnprocessableEntity);
group.MapGet("/status", HandleStatusAsync)
.WithName("scanner.offline-kit.status")
.WithName($"scanner.offline-kit.status{suffix}")
.RequireAuthorization(ScannerPolicies.OfflineKitStatusRead)
.Produces<OfflineKitStatusTransport>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status204NoContent)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
// Sprint 026: OFFLINE-011 - Manifest retrieval
group.MapGet("/manifest", HandleGetManifestAsync)
.WithName($"scanner.offline-kit.manifest{suffix}")
.RequireAuthorization(ScannerPolicies.OfflineKitManifestRead)
.Produces<OfflineKitManifestTransport>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status204NoContent)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
// Sprint 026: OFFLINE-011 - Bundle validation
group.MapPost("/validate", HandleValidateAsync)
.WithName($"scanner.offline-kit.validate{suffix}")
.RequireAuthorization(ScannerPolicies.OfflineKitValidate)
.Produces<OfflineKitValidationResult>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
}
private static async Task<IResult> HandleImportAsync(
@@ -226,5 +254,88 @@ internal static class OfflineKitEndpoints
return "anonymous";
}
// Sprint 026: OFFLINE-011 - Manifest retrieval handler
private static async Task<IResult> HandleGetManifestAsync(
HttpContext context,
IOptionsMonitor<OfflineKitOptions> offlineKitOptions,
OfflineKitManifestService manifestService,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(offlineKitOptions);
ArgumentNullException.ThrowIfNull(manifestService);
if (!offlineKitOptions.CurrentValue.Enabled)
{
return ProblemResultFactory.Create(
context,
ProblemTypes.NotFound,
"Offline kit is not enabled",
StatusCodes.Status404NotFound);
}
var tenantId = ResolveTenant(context);
var manifest = await manifestService.GetManifestAsync(tenantId, cancellationToken).ConfigureAwait(false);
return manifest is null
? Results.NoContent()
: Results.Ok(manifest);
}
// Sprint 026: OFFLINE-011 - Bundle validation handler
private static async Task<IResult> HandleValidateAsync(
HttpContext context,
HttpRequest request,
IOptionsMonitor<OfflineKitOptions> offlineKitOptions,
OfflineKitManifestService manifestService,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(context);
ArgumentNullException.ThrowIfNull(request);
ArgumentNullException.ThrowIfNull(offlineKitOptions);
ArgumentNullException.ThrowIfNull(manifestService);
if (!offlineKitOptions.CurrentValue.Enabled)
{
return ProblemResultFactory.Create(
context,
ProblemTypes.NotFound,
"Offline kit validation is not enabled",
StatusCodes.Status404NotFound);
}
OfflineKitValidationRequest? validationRequest;
try
{
validationRequest = await request.ReadFromJsonAsync<OfflineKitValidationRequest>(JsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (JsonException ex)
{
return ProblemResultFactory.Create(
context,
ProblemTypes.Validation,
"Invalid validation request",
StatusCodes.Status400BadRequest,
detail: $"Failed to parse request JSON: {ex.Message}");
}
if (validationRequest is null || string.IsNullOrWhiteSpace(validationRequest.ManifestJson))
{
return ProblemResultFactory.Create(
context,
ProblemTypes.Validation,
"Invalid validation request",
StatusCodes.Status400BadRequest,
detail: "Request body with manifestJson is required.");
}
var result = manifestService.ValidateManifest(
validationRequest.ManifestJson,
validationRequest.Signature,
validationRequest.VerifyAssets);
return Results.Ok(result);
}
}