wip: doctor/cli/docs/api to vector db consolidation; api hardening for descriptions, tenant, and scopes; migrations and conversions of all DALs to EF v10
This commit is contained in:
@@ -19,7 +19,8 @@ public static class AdministrationTrustSigningMutationEndpoints
|
||||
public static IEndpointRouteBuilder MapAdministrationTrustSigningMutationEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/administration/trust-signing")
|
||||
.WithTags("Administration");
|
||||
.WithTags("Administration")
|
||||
.RequireAuthorization(PlatformPolicies.TrustRead);
|
||||
|
||||
group.MapGet("/keys", async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -17,7 +17,8 @@ public static class AnalyticsEndpoints
|
||||
public static IEndpointRouteBuilder MapAnalyticsEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var analytics = app.MapGroup("/api/analytics")
|
||||
.WithTags("Analytics");
|
||||
.WithTags("Analytics")
|
||||
.RequireAuthorization(PlatformPolicies.AnalyticsRead);
|
||||
|
||||
analytics.MapGet("/suppliers", async Task<IResult> (
|
||||
HttpContext context,
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class ContextEndpoints
|
||||
public static IEndpointRouteBuilder MapContextEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var context = app.MapGroup("/api/v2/context")
|
||||
.WithTags("Platform Context");
|
||||
.WithTags("Platform Context")
|
||||
.RequireAuthorization(PlatformPolicies.ContextRead);
|
||||
|
||||
context.MapGet("/regions", async Task<IResult>(
|
||||
HttpContext httpContext,
|
||||
|
||||
@@ -18,7 +18,8 @@ public static class EnvironmentSettingsAdminEndpoints
|
||||
public static IEndpointRouteBuilder MapEnvironmentSettingsAdminEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/platform/envsettings/db")
|
||||
.WithTags("Environment Settings Admin");
|
||||
.WithTags("Environment Settings Admin")
|
||||
.RequireAuthorization(PlatformPolicies.SetupRead);
|
||||
|
||||
group.MapGet("/", async (IEnvironmentSettingsStore store, CancellationToken ct) =>
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ 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.Services;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Export;
|
||||
using StellaOps.ReleaseOrchestrator.EvidenceThread.Models;
|
||||
@@ -31,13 +32,14 @@ public static class EvidenceThreadEndpoints
|
||||
public static IEndpointRouteBuilder MapEvidenceThreadEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var evidence = app.MapGroup("/api/v1/evidence")
|
||||
.WithTags("Evidence Thread");
|
||||
.WithTags("Evidence Thread")
|
||||
.RequireAuthorization(PlatformPolicies.ContextRead);
|
||||
|
||||
// GET /api/v1/evidence/{artifactDigest} - Get evidence thread for artifact
|
||||
evidence.MapGet("/{artifactDigest}", GetEvidenceThread)
|
||||
.WithName("GetEvidenceThread")
|
||||
.WithSummary("Get evidence thread for an artifact")
|
||||
.WithDescription("Retrieves the full evidence thread graph for an artifact by its digest.")
|
||||
.WithDescription("Retrieves the full evidence thread graph for an artifact by its digest, including node count, link count, verdict, and risk score.")
|
||||
.Produces<EvidenceThreadResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
@@ -46,7 +48,8 @@ public static class EvidenceThreadEndpoints
|
||||
evidence.MapPost("/{artifactDigest}/export", ExportEvidenceThread)
|
||||
.WithName("ExportEvidenceThread")
|
||||
.WithSummary("Export evidence thread as DSSE bundle")
|
||||
.WithDescription("Exports the evidence thread as a signed DSSE envelope for offline verification.")
|
||||
.WithDescription("Exports the evidence thread as a signed DSSE envelope for offline verification. Supports DSSE, JSON, Markdown, and PDF formats. The envelope is optionally signed with the specified key.")
|
||||
.RequireAuthorization(PlatformPolicies.ContextWrite)
|
||||
.Produces<EvidenceExportResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
@@ -55,7 +58,8 @@ public static class EvidenceThreadEndpoints
|
||||
evidence.MapPost("/{artifactDigest}/transcript", GenerateTranscript)
|
||||
.WithName("GenerateEvidenceTranscript")
|
||||
.WithSummary("Generate natural language transcript")
|
||||
.WithDescription("Generates a natural language transcript explaining the evidence thread.")
|
||||
.WithDescription("Generates a natural language transcript explaining the evidence thread in summary, detailed, or audit format. May invoke an LLM for rationale generation when enabled.")
|
||||
.RequireAuthorization(PlatformPolicies.ContextWrite)
|
||||
.Produces<EvidenceTranscriptResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
@@ -64,7 +68,7 @@ public static class EvidenceThreadEndpoints
|
||||
evidence.MapGet("/{artifactDigest}/nodes", GetEvidenceNodes)
|
||||
.WithName("GetEvidenceNodes")
|
||||
.WithSummary("Get evidence nodes for an artifact")
|
||||
.WithDescription("Retrieves all evidence nodes in the thread.")
|
||||
.WithDescription("Retrieves all evidence nodes in the thread, optionally filtered by node kind (e.g., sbom, scan, attestation). Returns node summaries, confidence scores, and anchor counts.")
|
||||
.Produces<EvidenceNodeListResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
@@ -73,7 +77,7 @@ public static class EvidenceThreadEndpoints
|
||||
evidence.MapGet("/{artifactDigest}/links", GetEvidenceLinks)
|
||||
.WithName("GetEvidenceLinks")
|
||||
.WithSummary("Get evidence links for an artifact")
|
||||
.WithDescription("Retrieves all evidence links in the thread.")
|
||||
.WithDescription("Retrieves all directed evidence links in the thread, describing provenance and dependency relationships between evidence nodes.")
|
||||
.Produces<EvidenceLinkListResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status404NotFound)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
@@ -82,7 +86,8 @@ public static class EvidenceThreadEndpoints
|
||||
evidence.MapPost("/{artifactDigest}/collect", CollectEvidence)
|
||||
.WithName("CollectEvidence")
|
||||
.WithSummary("Collect evidence for an artifact")
|
||||
.WithDescription("Triggers collection of all available evidence for an artifact.")
|
||||
.WithDescription("Triggers collection of all available evidence for an artifact: SBOM diff, reachability graph, VEX advisories, and attestations. Returns the count of nodes and links created, plus any collection errors.")
|
||||
.RequireAuthorization(PlatformPolicies.ContextWrite)
|
||||
.Produces<EvidenceCollectionResponse>(StatusCodes.Status200OK)
|
||||
.Produces(StatusCodes.Status400BadRequest);
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ public static class FederationTelemetryEndpoints
|
||||
public static IEndpointRouteBuilder MapFederationTelemetryEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/telemetry/federation")
|
||||
.WithTags("Federated Telemetry");
|
||||
.WithTags("Federated Telemetry")
|
||||
.RequireAuthorization(PlatformPolicies.FederationRead);
|
||||
|
||||
// GET /consent — get consent state
|
||||
group.MapGet("/consent", async Task<IResult>(
|
||||
|
||||
@@ -24,7 +24,8 @@ public static class FunctionMapEndpoints
|
||||
public static IEndpointRouteBuilder MapFunctionMapEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var maps = app.MapGroup("/api/v1/function-maps")
|
||||
.WithTags("Function Maps");
|
||||
.WithTags("Function Maps")
|
||||
.RequireAuthorization(PlatformPolicies.FunctionMapRead);
|
||||
|
||||
MapCrudEndpoints(maps);
|
||||
MapVerifyEndpoints(maps);
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class IntegrationReadModelEndpoints
|
||||
public static IEndpointRouteBuilder MapIntegrationReadModelEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var integrations = app.MapGroup("/api/v2/integrations")
|
||||
.WithTags("Integrations V2");
|
||||
.WithTags("Integrations V2")
|
||||
.RequireAuthorization(PlatformPolicies.IntegrationsRead);
|
||||
|
||||
integrations.MapGet("/feeds", async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -16,7 +16,8 @@ public static class LegacyAliasEndpoints
|
||||
public static IEndpointRouteBuilder MapLegacyAliasEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var legacy = app.MapGroup("/api/v1")
|
||||
.WithTags("Pack22 Legacy Aliases");
|
||||
.WithTags("Pack22 Legacy Aliases")
|
||||
.RequireAuthorization(PlatformPolicies.ContextRead);
|
||||
|
||||
legacy.MapGet("/context/regions", async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -43,7 +43,8 @@ public static class PackAdapterEndpoints
|
||||
.RequireAuthorization(PlatformPolicies.HealthRead);
|
||||
|
||||
var platform = app.MapGroup("/api/v1/platform")
|
||||
.WithTags("Platform Ops");
|
||||
.WithTags("Platform Ops")
|
||||
.RequireAuthorization(PlatformPolicies.HealthRead);
|
||||
|
||||
platform.MapGet("/data-integrity/summary", (
|
||||
HttpContext context,
|
||||
|
||||
@@ -18,7 +18,8 @@ public static class PlatformEndpoints
|
||||
public static IEndpointRouteBuilder MapPlatformEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var platform = app.MapGroup("/api/v1/platform")
|
||||
.WithTags("Platform");
|
||||
.WithTags("Platform")
|
||||
.RequireAuthorization(PlatformPolicies.HealthRead);
|
||||
|
||||
MapHealthEndpoints(platform);
|
||||
MapQuotaEndpoints(platform);
|
||||
@@ -161,12 +162,12 @@ public static class PlatformEndpoints
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant_missing" });
|
||||
return tenantFailure!;
|
||||
}
|
||||
|
||||
var result = await service.GetTenantAsync(tenantId.Trim().ToLowerInvariant(), cancellationToken).ConfigureAwait(false);
|
||||
var result = await service.GetTenantAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(new PlatformListResponse<PlatformQuotaUsage>(
|
||||
requestContext!.TenantId,
|
||||
requestContext.ActorId,
|
||||
@@ -293,12 +294,12 @@ public static class PlatformEndpoints
|
||||
return failure!;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
|
||||
{
|
||||
return Results.BadRequest(new { error = "tenant_missing" });
|
||||
return tenantFailure!;
|
||||
}
|
||||
|
||||
var status = await service.GetTenantSetupStatusAsync(tenantId.Trim().ToLowerInvariant(), cancellationToken).ConfigureAwait(false);
|
||||
var status = await service.GetTenantSetupStatusAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(status);
|
||||
}).RequireAuthorization(PlatformPolicies.OnboardingRead);
|
||||
}
|
||||
@@ -476,7 +477,8 @@ public static class PlatformEndpoints
|
||||
private static void MapLegacyQuotaCompatibilityEndpoints(IEndpointRouteBuilder app)
|
||||
{
|
||||
var quotas = app.MapGroup("/api/v1/authority/quotas")
|
||||
.WithTags("Platform Quotas Compatibility");
|
||||
.WithTags("Platform Quotas Compatibility")
|
||||
.RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet(string.Empty, async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -491,7 +493,7 @@ public static class PlatformEndpoints
|
||||
|
||||
var summary = await service.GetSummaryAsync(requestContext!, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(BuildLegacyEntitlement(summary.Value, requestContext!));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/consumption", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -506,7 +508,7 @@ public static class PlatformEndpoints
|
||||
|
||||
var summary = await service.GetSummaryAsync(requestContext!, cancellationToken).ConfigureAwait(false);
|
||||
return Results.Ok(BuildLegacyConsumption(summary.Value));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/dashboard", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -528,7 +530,7 @@ public static class PlatformEndpoints
|
||||
activeAlerts = 0,
|
||||
recentViolations = 0
|
||||
});
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/history", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -570,7 +572,7 @@ public static class PlatformEndpoints
|
||||
points,
|
||||
aggregation = string.IsNullOrWhiteSpace(aggregation) ? "daily" : aggregation
|
||||
});
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/tenants", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -612,7 +614,7 @@ public static class PlatformEndpoints
|
||||
.ToArray();
|
||||
|
||||
return Results.Ok(new { items, total = 1 });
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/tenants/{tenantId}", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -626,7 +628,12 @@ public static class PlatformEndpoints
|
||||
return failure!;
|
||||
}
|
||||
|
||||
var result = await service.GetTenantAsync(tenantId, cancellationToken).ConfigureAwait(false);
|
||||
if (!TryResolveRequestedTenant(requestContext!, tenantId, out var normalizedTenantId, out var tenantFailure))
|
||||
{
|
||||
return tenantFailure!;
|
||||
}
|
||||
|
||||
var result = await service.GetTenantAsync(normalizedTenantId, cancellationToken).ConfigureAwait(false);
|
||||
var consumption = BuildLegacyConsumption(result.Value);
|
||||
|
||||
return Results.Ok(new
|
||||
@@ -655,7 +662,7 @@ public static class PlatformEndpoints
|
||||
},
|
||||
forecast = BuildLegacyForecast("api")
|
||||
});
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/forecast", async Task<IResult> (
|
||||
HttpContext context,
|
||||
@@ -673,7 +680,7 @@ public static class PlatformEndpoints
|
||||
|
||||
var forecasts = categories.Select(BuildLegacyForecast).ToArray();
|
||||
return Results.Ok(forecasts);
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapGet("/alerts", (HttpContext context, PlatformRequestContextResolver resolver) =>
|
||||
{
|
||||
@@ -694,7 +701,7 @@ public static class PlatformEndpoints
|
||||
channels = Array.Empty<object>(),
|
||||
escalationMinutes = 30
|
||||
}));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
quotas.MapPost("/alerts", (HttpContext context, PlatformRequestContextResolver resolver, [FromBody] object config) =>
|
||||
{
|
||||
@@ -704,10 +711,11 @@ public static class PlatformEndpoints
|
||||
}
|
||||
|
||||
return Task.FromResult<IResult>(Results.Ok(config));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaAdmin);
|
||||
|
||||
var rateLimits = app.MapGroup("/api/v1/gateway/rate-limits")
|
||||
.WithTags("Platform Gateway Compatibility");
|
||||
.WithTags("Platform Gateway Compatibility")
|
||||
.RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
rateLimits.MapGet(string.Empty, (HttpContext context, PlatformRequestContextResolver resolver) =>
|
||||
{
|
||||
@@ -729,7 +737,7 @@ public static class PlatformEndpoints
|
||||
burstRemaining = 119
|
||||
}
|
||||
}));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
|
||||
rateLimits.MapGet("/violations", (HttpContext context, PlatformRequestContextResolver resolver) =>
|
||||
{
|
||||
@@ -749,7 +757,7 @@ public static class PlatformEndpoints
|
||||
end = now.ToString("o")
|
||||
}
|
||||
}));
|
||||
}).RequireAuthorization();
|
||||
}).RequireAuthorization(PlatformPolicies.QuotaRead);
|
||||
}
|
||||
|
||||
private static LegacyQuotaItem[] BuildLegacyConsumption(IReadOnlyList<PlatformQuotaUsage> usage)
|
||||
@@ -885,6 +893,37 @@ public static class PlatformEndpoints
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryResolveRequestedTenant(
|
||||
PlatformRequestContext requestContext,
|
||||
string? requestedTenantId,
|
||||
out string normalizedTenantId,
|
||||
out IResult? failure)
|
||||
{
|
||||
normalizedTenantId = string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requestedTenantId))
|
||||
{
|
||||
failure = Results.BadRequest(new { error = "tenant_missing" });
|
||||
return false;
|
||||
}
|
||||
|
||||
normalizedTenantId = requestedTenantId.Trim().ToLowerInvariant();
|
||||
if (!string.Equals(normalizedTenantId, requestContext.TenantId, StringComparison.Ordinal))
|
||||
{
|
||||
failure = Results.Json(
|
||||
new
|
||||
{
|
||||
error = "tenant_forbidden",
|
||||
requestedTenantId = normalizedTenantId
|
||||
},
|
||||
statusCode: StatusCodes.Status403Forbidden);
|
||||
return false;
|
||||
}
|
||||
|
||||
failure = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private sealed record LegacyQuotaItem(
|
||||
string Category,
|
||||
decimal Current,
|
||||
|
||||
@@ -25,7 +25,8 @@ public static class PolicyInteropEndpoints
|
||||
public static IEndpointRouteBuilder MapPolicyInteropEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var interop = app.MapGroup("/api/v1/policy/interop")
|
||||
.WithTags("PolicyInterop");
|
||||
.WithTags("PolicyInterop")
|
||||
.RequireAuthorization(PlatformPolicies.PolicyRead);
|
||||
|
||||
MapExportEndpoint(interop);
|
||||
MapImportEndpoint(interop);
|
||||
|
||||
@@ -19,7 +19,8 @@ public static class ReleaseControlEndpoints
|
||||
public static IEndpointRouteBuilder MapReleaseControlEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var bundles = app.MapGroup("/api/v1/release-control/bundles")
|
||||
.WithTags("Release Control");
|
||||
.WithTags("Release Control")
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
|
||||
|
||||
bundles.MapGet(string.Empty, async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -16,7 +16,8 @@ public static class ReleaseReadModelEndpoints
|
||||
public static IEndpointRouteBuilder MapReleaseReadModelEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var releases = app.MapGroup("/api/v2/releases")
|
||||
.WithTags("Releases V2");
|
||||
.WithTags("Releases V2")
|
||||
.RequireAuthorization(PlatformPolicies.ReleaseControlRead);
|
||||
|
||||
releases.MapGet(string.Empty, async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -25,7 +25,8 @@ public static class ScoreEndpoints
|
||||
public static IEndpointRouteBuilder MapScoreEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var score = app.MapGroup("/api/v1/score")
|
||||
.WithTags("Score");
|
||||
.WithTags("Score")
|
||||
.RequireAuthorization(PlatformPolicies.ScoreRead);
|
||||
|
||||
MapEvaluateEndpoints(score);
|
||||
MapHistoryEndpoints(score);
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class SecurityReadModelEndpoints
|
||||
public static IEndpointRouteBuilder MapSecurityReadModelEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var security = app.MapGroup("/api/v2/security")
|
||||
.WithTags("Security V2");
|
||||
.WithTags("Security V2")
|
||||
.RequireAuthorization(PlatformPolicies.SecurityRead);
|
||||
|
||||
security.MapGet("/findings", async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
@@ -15,7 +15,8 @@ public static class TopologyReadModelEndpoints
|
||||
public static IEndpointRouteBuilder MapTopologyReadModelEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var topology = app.MapGroup("/api/v2/topology")
|
||||
.WithTags("Topology V2");
|
||||
.WithTags("Topology V2")
|
||||
.RequireAuthorization(PlatformPolicies.TopologyRead);
|
||||
|
||||
topology.MapGet("/regions", async Task<IResult>(
|
||||
HttpContext context,
|
||||
|
||||
Reference in New Issue
Block a user