up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
This commit is contained in:
@@ -896,6 +896,64 @@ var response = new GraphLinkoutsResponse(items, notFound);
|
||||
return Results.Ok(response);
|
||||
}).WithName("PostGraphLinkouts");
|
||||
|
||||
app.MapGet("/v1/graph/status", async (
|
||||
HttpContext context,
|
||||
[FromQuery(Name = "purl")] string[]? purls,
|
||||
IOptions<VexMongoStorageOptions> storageOptions,
|
||||
IOptions<GraphOptions> graphOptions,
|
||||
IVexObservationQueryService queryService,
|
||||
IMemoryCache cache,
|
||||
TimeProvider timeProvider,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveTenant(context, storageOptions.Value, requireHeader: true, out var tenant, out var tenantError))
|
||||
{
|
||||
return tenantError;
|
||||
}
|
||||
|
||||
var orderedPurls = NormalizePurls(purls);
|
||||
if (orderedPurls.Count == 0)
|
||||
{
|
||||
return Results.BadRequest("purl query parameter is required");
|
||||
}
|
||||
|
||||
if (orderedPurls.Count > graphOptions.Value.MaxPurls)
|
||||
{
|
||||
return Results.BadRequest($"purls limit exceeded (max {graphOptions.Value.MaxPurls})");
|
||||
}
|
||||
|
||||
var cacheKey = $"graph-status:{tenant}:{string.Join('|', orderedPurls)}";
|
||||
var now = timeProvider.GetUtcNow();
|
||||
|
||||
if (cache.TryGetValue<CachedGraphStatus>(cacheKey, out var cached) && cached is not null)
|
||||
{
|
||||
var ageMs = (long)Math.Max(0, (now - cached.CachedAt).TotalMilliseconds);
|
||||
return Results.Ok(new GraphStatusResponse(cached.Items, true, ageMs));
|
||||
}
|
||||
|
||||
var options = new VexObservationQueryOptions(
|
||||
tenant: tenant,
|
||||
purls: orderedPurls,
|
||||
limit: graphOptions.Value.MaxAdvisoriesPerPurl * orderedPurls.Count);
|
||||
|
||||
VexObservationQueryResult result;
|
||||
try
|
||||
{
|
||||
result = await queryService.QueryAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
return Results.BadRequest(ex.Message);
|
||||
}
|
||||
|
||||
var items = GraphStatusFactory.Build(orderedPurls, result.Observations);
|
||||
var response = new GraphStatusResponse(items, false, null);
|
||||
|
||||
cache.Set(cacheKey, new CachedGraphStatus(items, now), TimeSpan.FromSeconds(graphOptions.Value.OverlayTtlSeconds));
|
||||
|
||||
return Results.Ok(response);
|
||||
}).WithName("GetGraphStatus");
|
||||
|
||||
// Cartographer overlays
|
||||
app.MapGet("/v1/graph/overlays", async (
|
||||
HttpContext context,
|
||||
@@ -956,6 +1014,66 @@ app.MapGet("/v1/graph/overlays", async (
|
||||
return Results.Ok(response);
|
||||
}).WithName("GetGraphOverlays");
|
||||
|
||||
app.MapGet("/v1/graph/observations", async (
|
||||
HttpContext context,
|
||||
[FromQuery(Name = "purl")] string[]? purls,
|
||||
[FromQuery] bool includeJustifications,
|
||||
[FromQuery] int? limitPerPurl,
|
||||
[FromQuery] string? cursor,
|
||||
IOptions<VexMongoStorageOptions> storageOptions,
|
||||
IOptions<GraphOptions> graphOptions,
|
||||
IVexObservationQueryService queryService,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!TryResolveTenant(context, storageOptions.Value, requireHeader: true, out var tenant, out var tenantError))
|
||||
{
|
||||
return tenantError;
|
||||
}
|
||||
|
||||
var orderedPurls = NormalizePurls(purls);
|
||||
if (orderedPurls.Count == 0)
|
||||
{
|
||||
return Results.BadRequest("purl query parameter is required");
|
||||
}
|
||||
|
||||
if (orderedPurls.Count > graphOptions.Value.MaxPurls)
|
||||
{
|
||||
return Results.BadRequest($"purls limit exceeded (max {graphOptions.Value.MaxPurls})");
|
||||
}
|
||||
|
||||
var perPurlLimit = limitPerPurl.GetValueOrDefault(graphOptions.Value.MaxTooltipItemsPerPurl);
|
||||
if (perPurlLimit <= 0)
|
||||
{
|
||||
return Results.BadRequest("limitPerPurl must be greater than zero when provided.");
|
||||
}
|
||||
|
||||
var effectivePerPurlLimit = Math.Min(perPurlLimit, graphOptions.Value.MaxAdvisoriesPerPurl);
|
||||
var totalLimit = Math.Min(
|
||||
Math.Max(1, effectivePerPurlLimit * orderedPurls.Count),
|
||||
graphOptions.Value.MaxTooltipTotal);
|
||||
|
||||
var options = new VexObservationQueryOptions(
|
||||
tenant: tenant,
|
||||
purls: orderedPurls,
|
||||
limit: totalLimit,
|
||||
cursor: cursor);
|
||||
|
||||
VexObservationQueryResult result;
|
||||
try
|
||||
{
|
||||
result = await queryService.QueryAsync(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
return Results.BadRequest(ex.Message);
|
||||
}
|
||||
|
||||
var items = GraphTooltipFactory.Build(orderedPurls, result.Observations, includeJustifications, effectivePerPurlLimit);
|
||||
var response = new GraphTooltipResponse(items, result.NextCursor, result.HasMore);
|
||||
|
||||
return Results.Ok(response);
|
||||
}).WithName("GetGraphObservations");
|
||||
|
||||
app.MapPost("/ingest/vex", async (
|
||||
HttpContext context,
|
||||
VexIngestRequest request,
|
||||
|
||||
Reference in New Issue
Block a user