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:
@@ -1,8 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Integrations.Contracts;
|
||||
using StellaOps.Integrations.Contracts.AiCodeGuard;
|
||||
using StellaOps.Integrations.Core;
|
||||
using StellaOps.Integrations.WebService.AiCodeGuard;
|
||||
using StellaOps.Integrations.WebService.Security;
|
||||
|
||||
namespace StellaOps.Integrations.WebService;
|
||||
|
||||
@@ -14,6 +16,8 @@ public static class IntegrationEndpoints
|
||||
public static void MapIntegrationEndpoints(this WebApplication app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/integrations")
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.RequireTenant()
|
||||
.WithTags("Integrations");
|
||||
|
||||
// Standalone AI Code Guard run
|
||||
@@ -25,12 +29,14 @@ public static class IntegrationEndpoints
|
||||
var response = await aiCodeGuardRunService.RunAsync(request, cancellationToken);
|
||||
return Results.Ok(response);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Operate)
|
||||
.WithName("RunAiCodeGuard")
|
||||
.WithDescription("Runs standalone AI Code Guard checks (equivalent to stella guard run).");
|
||||
.WithDescription("Executes a standalone AI Code Guard analysis pipeline against the specified target, equivalent to running `stella guard run`. Returns the scan result including detected issues, severity breakdown, and any policy violations.");
|
||||
|
||||
// List integrations
|
||||
group.MapGet("/", async (
|
||||
[FromServices] IntegrationService service,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
[FromQuery] IntegrationType? type,
|
||||
[FromQuery] IntegrationProvider? provider,
|
||||
[FromQuery] IntegrationStatus? status,
|
||||
@@ -42,11 +48,12 @@ public static class IntegrationEndpoints
|
||||
CancellationToken cancellationToken = default) =>
|
||||
{
|
||||
var query = new ListIntegrationsQuery(type, provider, status, search, null, page, pageSize, sortBy, sortDescending);
|
||||
var result = await service.ListAsync(query, null, cancellationToken);
|
||||
var result = await service.ListAsync(query, tenantAccessor.TenantId, cancellationToken);
|
||||
return Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.WithName("ListIntegrations")
|
||||
.WithDescription("Lists integrations with optional filtering and pagination.");
|
||||
.WithDescription("Returns a paginated list of integrations optionally filtered by type, provider, status, or a free-text search term. Results are sorted by the specified field and direction, defaulting to name ascending.");
|
||||
|
||||
// Get integration by ID
|
||||
group.MapGet("/{id:guid}", async (
|
||||
@@ -57,57 +64,66 @@ public static class IntegrationEndpoints
|
||||
var result = await service.GetByIdAsync(id, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.WithName("GetIntegration")
|
||||
.WithDescription("Gets an integration by ID.");
|
||||
.WithDescription("Returns the full integration record for the specified ID including provider, type, configuration metadata, and current status. Returns 404 if the ID is not found.");
|
||||
|
||||
// Create integration
|
||||
group.MapPost("/", async (
|
||||
[FromServices] IntegrationService service,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
[FromBody] CreateIntegrationRequest request,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await service.CreateAsync(request, null, null, cancellationToken);
|
||||
var result = await service.CreateAsync(request, tenantAccessor.TenantId, null, cancellationToken);
|
||||
return Results.Created($"/api/v1/integrations/{result.Id}", result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Write)
|
||||
.WithName("CreateIntegration")
|
||||
.WithDescription("Creates a new integration.");
|
||||
.WithDescription("Registers a new integration with the catalog. The provider plugin is loaded and validated during creation. Returns 201 Created with the new integration record. Returns 400 if the provider is unsupported or required configuration is missing.");
|
||||
|
||||
// Update integration
|
||||
group.MapPut("/{id:guid}", async (
|
||||
[FromServices] IntegrationService service,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
Guid id,
|
||||
[FromBody] UpdateIntegrationRequest request,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await service.UpdateAsync(id, request, null, cancellationToken);
|
||||
var result = await service.UpdateAsync(id, request, tenantAccessor.TenantId, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Write)
|
||||
.WithName("UpdateIntegration")
|
||||
.WithDescription("Updates an existing integration.");
|
||||
.WithDescription("Updates the mutable configuration of an existing integration including display name, credentials reference, and provider-specific settings. Returns the updated integration record. Returns 404 if the ID is not found.");
|
||||
|
||||
// Delete integration
|
||||
group.MapDelete("/{id:guid}", async (
|
||||
[FromServices] IntegrationService service,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
Guid id,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await service.DeleteAsync(id, null, cancellationToken);
|
||||
var result = await service.DeleteAsync(id, tenantAccessor.TenantId, cancellationToken);
|
||||
return result ? Results.NoContent() : Results.NotFound();
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Write)
|
||||
.WithName("DeleteIntegration")
|
||||
.WithDescription("Soft-deletes an integration.");
|
||||
.WithDescription("Soft-deletes an integration from the catalog, disabling it without removing audit history. Returns 204 No Content on success. Returns 404 if the ID is not found.");
|
||||
|
||||
// Test connection
|
||||
group.MapPost("/{id:guid}/test", async (
|
||||
[FromServices] IntegrationService service,
|
||||
[FromServices] IStellaOpsTenantAccessor tenantAccessor,
|
||||
Guid id,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
var result = await service.TestConnectionAsync(id, null, cancellationToken);
|
||||
var result = await service.TestConnectionAsync(id, tenantAccessor.TenantId, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Operate)
|
||||
.WithName("TestIntegrationConnection")
|
||||
.WithDescription("Tests connectivity and authentication for an integration.");
|
||||
.WithDescription("Executes a live connectivity and authentication test against the external system for the specified integration. Returns a test result object with success status, latency, and any error details. Returns 404 if the integration ID is not found.");
|
||||
|
||||
// Health check
|
||||
group.MapGet("/{id:guid}/health", async (
|
||||
@@ -118,8 +134,9 @@ public static class IntegrationEndpoints
|
||||
var result = await service.CheckHealthAsync(id, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.WithName("CheckIntegrationHealth")
|
||||
.WithDescription("Performs a health check on an integration.");
|
||||
.WithDescription("Performs a health check on the specified integration and returns the current health status, including reachability, authentication validity, and any degradation indicators. Returns 404 if the integration ID is not found.");
|
||||
|
||||
// Impact map
|
||||
group.MapGet("/{id:guid}/impact", async (
|
||||
@@ -130,8 +147,9 @@ public static class IntegrationEndpoints
|
||||
var result = await service.GetImpactAsync(id, cancellationToken);
|
||||
return result is null ? Results.NotFound() : Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.WithName("GetIntegrationImpact")
|
||||
.WithDescription("Returns affected workflows and severity impact for an integration.");
|
||||
.WithDescription("Returns an impact map for the specified integration showing which workflows, pipelines, and policy gates depend on it, grouped by severity. Use this before disabling or reconfiguring an integration to understand downstream effects. Returns 404 if the ID is not found.");
|
||||
|
||||
// Get supported providers
|
||||
group.MapGet("/providers", ([FromServices] IntegrationService service) =>
|
||||
@@ -139,7 +157,8 @@ public static class IntegrationEndpoints
|
||||
var result = service.GetSupportedProviders();
|
||||
return Results.Ok(result);
|
||||
})
|
||||
.RequireAuthorization(IntegrationPolicies.Read)
|
||||
.WithName("GetSupportedProviders")
|
||||
.WithDescription("Gets a list of supported integration providers.");
|
||||
.WithDescription("Returns the list of integration provider types currently supported by the loaded plugin set. Use this to discover valid provider values before creating a new integration.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Auth.ServerIntegration;
|
||||
using StellaOps.Integrations.Persistence;
|
||||
using StellaOps.Integrations.Plugin.GitHubApp;
|
||||
@@ -7,7 +8,9 @@ using StellaOps.Integrations.Plugin.InMemory;
|
||||
using StellaOps.Integrations.WebService;
|
||||
using StellaOps.Integrations.WebService.AiCodeGuard;
|
||||
using StellaOps.Integrations.WebService.Infrastructure;
|
||||
using StellaOps.Integrations.WebService.Security;
|
||||
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.Router.AspNet;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
@@ -70,12 +73,22 @@ builder.Services.AddScoped<IAiCodeGuardRunService, AiCodeGuardRunService>();
|
||||
|
||||
builder.Services.AddStellaOpsCors(builder.Environment, builder.Configuration);
|
||||
|
||||
// Authentication and authorization
|
||||
builder.Services.AddStellaOpsResourceServerAuthentication(builder.Configuration);
|
||||
builder.Services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddStellaOpsScopePolicy(IntegrationPolicies.Read, StellaOpsScopes.IntegrationRead);
|
||||
options.AddStellaOpsScopePolicy(IntegrationPolicies.Write, StellaOpsScopes.IntegrationWrite);
|
||||
options.AddStellaOpsScopePolicy(IntegrationPolicies.Operate, StellaOpsScopes.IntegrationOperate);
|
||||
});
|
||||
|
||||
// Stella Router integration
|
||||
var routerEnabled = builder.Services.AddRouterMicroservice(
|
||||
builder.Configuration,
|
||||
serviceName: "integrations",
|
||||
version: System.Reflection.CustomAttributeExtensions.GetCustomAttribute<System.Reflection.AssemblyInformationalVersionAttribute>(System.Reflection.Assembly.GetExecutingAssembly())?.InformationalVersion ?? "1.0.0",
|
||||
routerOptionsSection: "Router");
|
||||
builder.Services.AddStellaOpsTenantServices();
|
||||
builder.TryAddStellaOpsLocalBinding("integrations");
|
||||
var app = builder.Build();
|
||||
app.LogStellaOpsLocalHostname("integrations");
|
||||
@@ -88,6 +101,9 @@ if (app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseStellaOpsCors();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseStellaOpsTenantMiddleware();
|
||||
app.TryUseStellaRouter(routerEnabled);
|
||||
|
||||
// Map endpoints
|
||||
@@ -96,7 +112,9 @@ app.MapIntegrationEndpoints();
|
||||
// Health endpoint
|
||||
app.MapGet("/health", () => Results.Ok(new { Status = "Healthy", Timestamp = DateTimeOffset.UtcNow }))
|
||||
.WithTags("Health")
|
||||
.WithName("HealthCheck");
|
||||
.WithName("HealthCheck")
|
||||
.WithDescription("Returns the liveness status and current UTC timestamp for the Integration Catalog service. Used by the Router gateway and container orchestrator for health polling.")
|
||||
.AllowAnonymous();
|
||||
|
||||
// Ensure database is created (dev only)
|
||||
if (app.Environment.IsDevelopment())
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) StellaOps. Licensed under the BUSL-1.1.
|
||||
|
||||
namespace StellaOps.Integrations.WebService.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Named authorization policy constants for the Integration Catalog service.
|
||||
/// Policies are registered via AddStellaOpsScopePolicy in Program.cs.
|
||||
/// </summary>
|
||||
internal static class IntegrationPolicies
|
||||
{
|
||||
/// <summary>Policy for listing integrations, providers, health, and impact. Requires integration:read scope.</summary>
|
||||
public const string Read = "Integration.Read";
|
||||
|
||||
/// <summary>Policy for creating, updating, and deleting integrations. Requires integration:write scope.</summary>
|
||||
public const string Write = "Integration.Write";
|
||||
|
||||
/// <summary>Policy for executing integration operations (test connections, AI Code Guard runs). Requires integration:operate scope.</summary>
|
||||
public const string Operate = "Integration.Operate";
|
||||
}
|
||||
Reference in New Issue
Block a user