feat(crypto): extract crypto providers to overlay compose files + health probe API
- 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>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user