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; /// /// 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 /// 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( 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( 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( 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( 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; } }