tests fixes and sprints work
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using StellaOps.Platform.WebService.Constants;
|
||||
using StellaOps.Platform.WebService.Contracts;
|
||||
using StellaOps.Platform.WebService.Services;
|
||||
|
||||
namespace StellaOps.Platform.WebService.Endpoints;
|
||||
|
||||
public static class AnalyticsEndpoints
|
||||
{
|
||||
public static IEndpointRouteBuilder MapAnalyticsEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var analytics = app.MapGroup("/api/analytics")
|
||||
.WithTags("Analytics");
|
||||
|
||||
analytics.MapGet("/suppliers", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] SuppliersQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetSuppliersAsync(
|
||||
requestContext!,
|
||||
query.Limit,
|
||||
query.Environment,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsSupplierConcentration>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsSuppliers")
|
||||
.WithSummary("Get supplier concentration analytics")
|
||||
.WithDescription("Returns the top suppliers by component and artifact exposure.")
|
||||
.Produces<PlatformListResponse<AnalyticsSupplierConcentration>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/licenses", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] EnvironmentQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetLicensesAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsLicenseDistribution>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsLicenses")
|
||||
.WithSummary("Get license distribution analytics")
|
||||
.WithDescription("Returns component and artifact counts grouped by license.")
|
||||
.Produces<PlatformListResponse<AnalyticsLicenseDistribution>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/vulnerabilities", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] VulnerabilityQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetVulnerabilitiesAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
query.MinSeverity,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsVulnerabilityExposure>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsVulnerabilities")
|
||||
.WithSummary("Get vulnerability exposure analytics")
|
||||
.WithDescription("Returns vulnerability exposure by severity, filtered by environment and minimum severity.")
|
||||
.Produces<PlatformListResponse<AnalyticsVulnerabilityExposure>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/backlog", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] EnvironmentQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetFixableBacklogAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsFixableBacklogItem>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsBacklog")
|
||||
.WithSummary("Get fixable vulnerability backlog")
|
||||
.WithDescription("Returns vulnerabilities with available fixes, filtered by environment.")
|
||||
.Produces<PlatformListResponse<AnalyticsFixableBacklogItem>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/attestation-coverage", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] EnvironmentQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetAttestationCoverageAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsAttestationCoverage>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsAttestationCoverage")
|
||||
.WithSummary("Get attestation coverage analytics")
|
||||
.WithDescription("Returns attestation coverage gaps by environment.")
|
||||
.Produces<PlatformListResponse<AnalyticsAttestationCoverage>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/trends/vulnerabilities", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] TrendQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetVulnerabilityTrendsAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
query.Days,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsVulnerabilityTrendPoint>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsVulnerabilityTrends")
|
||||
.WithSummary("Get vulnerability trend analytics")
|
||||
.WithDescription("Returns daily vulnerability trend points for a time window.")
|
||||
.Produces<PlatformListResponse<AnalyticsVulnerabilityTrendPoint>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/trends/components", async Task<IResult> (
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
PlatformAnalyticsService service,
|
||||
[AsParameters] TrendQuery query,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
|
||||
{
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (!service.IsConfigured)
|
||||
{
|
||||
return AnalyticsUnavailable();
|
||||
}
|
||||
|
||||
var result = await service.GetComponentTrendsAsync(
|
||||
requestContext!,
|
||||
query.Environment,
|
||||
query.Days,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return Results.Ok(new PlatformListResponse<AnalyticsComponentTrendPoint>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
result.DataAsOf,
|
||||
result.Cached,
|
||||
result.CacheTtlSeconds,
|
||||
result.Value,
|
||||
result.Value.Count));
|
||||
}).WithName("GetAnalyticsComponentTrends")
|
||||
.WithSummary("Get component trend analytics")
|
||||
.WithDescription("Returns daily component trend points for a time window.")
|
||||
.Produces<PlatformListResponse<AnalyticsComponentTrendPoint>>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest)
|
||||
.Produces(StatusCodes.Status503ServiceUnavailable)
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
private static bool TryResolveContext(
|
||||
HttpContext context,
|
||||
PlatformRequestContextResolver resolver,
|
||||
out PlatformRequestContext? requestContext,
|
||||
out IResult? failure)
|
||||
{
|
||||
if (resolver.TryResolve(context, out requestContext, out var error))
|
||||
{
|
||||
failure = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
failure = Results.BadRequest(new { error = error ?? "tenant_missing" });
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IResult AnalyticsUnavailable()
|
||||
{
|
||||
return Results.Problem(
|
||||
title: "analytics_not_configured",
|
||||
detail: "Analytics storage is not configured for this service.",
|
||||
statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
|
||||
private sealed record SuppliersQuery(int? Limit, string? Environment);
|
||||
private sealed record EnvironmentQuery(string? Environment);
|
||||
|
||||
private sealed record VulnerabilityQuery(
|
||||
string? Environment,
|
||||
[property: FromQuery(Name = "minSeverity")] string? MinSeverity);
|
||||
|
||||
private sealed record TrendQuery(
|
||||
string? Environment,
|
||||
int? Days);
|
||||
}
|
||||
Reference in New Issue
Block a user