- Extract smremote to docker-compose.crypto-provider.smremote.yml - Rename cryptopro/crypto-sim compose files for consistent naming - Add crypto provider health probe endpoint (CP-001) - Add tenant crypto provider preferences API + migration (CP-002) - Update docs and compliance env examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
152 lines
5.9 KiB
C#
152 lines
5.9 KiB
C#
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
|
using StellaOps.Platform.WebService.Constants;
|
|
using StellaOps.Platform.WebService.Contracts;
|
|
using StellaOps.Platform.WebService.Services;
|
|
using System;
|
|
|
|
namespace StellaOps.Platform.WebService.Endpoints;
|
|
|
|
/// <summary>
|
|
/// Admin endpoints for crypto provider health probing and tenant preference management.
|
|
/// CP-001: GET /api/v1/admin/crypto-providers/health
|
|
/// CP-002: GET/PUT/DELETE /api/v1/admin/crypto-providers/preferences
|
|
/// </summary>
|
|
public static class CryptoProviderAdminEndpoints
|
|
{
|
|
public static IEndpointRouteBuilder MapCryptoProviderAdminEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var group = app.MapGroup("/api/v1/admin/crypto-providers")
|
|
.WithTags("CryptoProviders")
|
|
.RequireAuthorization(PlatformPolicies.HealthAdmin)
|
|
.RequireTenant();
|
|
|
|
// ---------------------------------------------------------------
|
|
// CP-001: Health probe
|
|
// ---------------------------------------------------------------
|
|
|
|
group.MapGet("/health", async Task<IResult>(
|
|
CryptoProviderHealthService healthService,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
var result = await healthService.ProbeAllAsync(cancellationToken).ConfigureAwait(false);
|
|
return Results.Ok(result);
|
|
})
|
|
.WithName("GetCryptoProviderHealth")
|
|
.WithSummary("Probe crypto provider health")
|
|
.WithDescription(
|
|
"Probes each known crypto provider health endpoint and returns aggregated status. " +
|
|
"Unreachable providers return 'unreachable' status, not an error.");
|
|
|
|
// ---------------------------------------------------------------
|
|
// CP-002: Preferences CRUD
|
|
// ---------------------------------------------------------------
|
|
|
|
group.MapGet("/preferences", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
ICryptoProviderPreferenceStore store,
|
|
TimeProvider timeProvider,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var rc, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
if (!Guid.TryParse(rc!.TenantId, out var tenantGuid))
|
|
{
|
|
return Results.BadRequest(new { error = "invalid_tenant_id" });
|
|
}
|
|
|
|
var preferences = await store.GetByTenantAsync(tenantGuid, cancellationToken).ConfigureAwait(false);
|
|
|
|
return Results.Ok(new CryptoProviderPreferencesResponse(
|
|
TenantId: rc.TenantId,
|
|
Preferences: preferences,
|
|
DataAsOf: timeProvider.GetUtcNow()));
|
|
})
|
|
.WithName("GetCryptoProviderPreferences")
|
|
.WithSummary("List tenant crypto provider preferences")
|
|
.WithDescription("Returns all crypto provider preferences for the current tenant, ordered by priority.");
|
|
|
|
group.MapPut("/preferences", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
ICryptoProviderPreferenceStore store,
|
|
[FromBody] CryptoProviderPreferenceRequest request,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var rc, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(request.ProviderId))
|
|
{
|
|
return Results.BadRequest(new { error = "provider_id_required" });
|
|
}
|
|
|
|
if (!Guid.TryParse(rc!.TenantId, out var tenantGuid))
|
|
{
|
|
return Results.BadRequest(new { error = "invalid_tenant_id" });
|
|
}
|
|
|
|
var result = await store.UpsertAsync(tenantGuid, request, cancellationToken).ConfigureAwait(false);
|
|
return Results.Ok(result);
|
|
})
|
|
.WithName("UpsertCryptoProviderPreference")
|
|
.WithSummary("Create or update a crypto provider preference")
|
|
.WithDescription(
|
|
"Upserts a tenant crypto provider preference. The unique key is (tenantId, providerId, algorithmScope).");
|
|
|
|
group.MapDelete("/preferences/{id:guid}", async Task<IResult>(
|
|
HttpContext context,
|
|
PlatformRequestContextResolver resolver,
|
|
ICryptoProviderPreferenceStore store,
|
|
Guid id,
|
|
CancellationToken cancellationToken) =>
|
|
{
|
|
if (!TryResolveContext(context, resolver, out var rc, out var failure))
|
|
{
|
|
return failure!;
|
|
}
|
|
|
|
if (!Guid.TryParse(rc!.TenantId, out var tenantGuid))
|
|
{
|
|
return Results.BadRequest(new { error = "invalid_tenant_id" });
|
|
}
|
|
|
|
var deleted = await store.DeleteAsync(tenantGuid, id, cancellationToken).ConfigureAwait(false);
|
|
|
|
return deleted
|
|
? Results.NoContent()
|
|
: Results.NotFound(new { error = "preference_not_found", id });
|
|
})
|
|
.WithName("DeleteCryptoProviderPreference")
|
|
.WithSummary("Delete a crypto provider preference")
|
|
.WithDescription("Removes a single crypto provider preference by ID. Tenant-isolated.");
|
|
|
|
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;
|
|
}
|
|
}
|