old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions

This commit is contained in:
master
2026-01-15 18:37:59 +02:00
parent c631bacee2
commit 88a85cdd92
208 changed files with 32271 additions and 2287 deletions

View File

@@ -0,0 +1,288 @@
// Copyright (c) StellaOps. All rights reserved.
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
// Sprint: SPRINT_20260112_004_PLATFORM_setup_wizard_backend (PLATFORM-SETUP-003)
// Task: Add /api/v1/setup/* endpoints with auth policies, request validation, and Problem+JSON errors
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging;
using StellaOps.Platform.WebService.Constants;
using StellaOps.Platform.WebService.Contracts;
using StellaOps.Platform.WebService.Services;
namespace StellaOps.Platform.WebService.Endpoints;
/// <summary>
/// Setup wizard API endpoints aligned to docs/setup/setup-wizard-ux.md.
/// </summary>
public static class SetupEndpoints
{
public static IEndpointRouteBuilder MapSetupEndpoints(this IEndpointRouteBuilder app)
{
var setup = app.MapGroup("/api/v1/setup")
.WithTags("Setup Wizard");
MapSessionEndpoints(setup);
MapStepEndpoints(setup);
MapDefinitionEndpoints(setup);
return app;
}
private static void MapSessionEndpoints(IEndpointRouteBuilder setup)
{
var sessions = setup.MapGroup("/sessions").WithTags("Setup Sessions");
// GET /api/v1/setup/sessions - Get current session
sessions.MapGet("/", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var result = await service.GetSessionAsync(requestContext!, ct).ConfigureAwait(false);
if (result is null)
{
return Results.NotFound(CreateProblem(
"Session Not Found",
"No active setup session for this tenant.",
StatusCodes.Status404NotFound));
}
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupRead)
.WithName("GetSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status404NotFound);
// POST /api/v1/setup/sessions - Create new session
sessions.MapPost("/", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
[FromBody] CreateSetupSessionRequest? request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var result = await service.CreateSessionAsync(
requestContext!,
request ?? new CreateSetupSessionRequest(),
ct).ConfigureAwait(false);
return Results.Created($"/api/v1/setup/sessions", result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
.WithName("CreateSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status201Created)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
// POST /api/v1/setup/sessions/resume - Resume or create session
sessions.MapPost("/resume", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var result = await service.ResumeOrCreateSessionAsync(requestContext!, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
.WithName("ResumeSetupSession")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
// POST /api/v1/setup/sessions/finalize - Finalize session
sessions.MapPost("/finalize", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
[FromBody] FinalizeSetupSessionRequest? request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
try
{
var result = await service.FinalizeSessionAsync(
requestContext!,
request ?? new FinalizeSetupSessionRequest(),
ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
.WithName("FinalizeSetupSession")
.Produces<FinalizeSetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
}
private static void MapStepEndpoints(IEndpointRouteBuilder setup)
{
var steps = setup.MapGroup("/steps").WithTags("Setup Steps");
// POST /api/v1/setup/steps/execute - Execute a step
steps.MapPost("/execute", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
[FromBody] ExecuteSetupStepRequest request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (request is null)
{
return Results.BadRequest(CreateProblem(
"Invalid Request",
"Request body is required with stepId.",
StatusCodes.Status400BadRequest));
}
try
{
var result = await service.ExecuteStepAsync(requestContext!, request, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
.WithName("ExecuteSetupStep")
.Produces<ExecuteSetupStepResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
// POST /api/v1/setup/steps/skip - Skip a step
steps.MapPost("/skip", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
[FromBody] SkipSetupStepRequest request,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
if (request is null)
{
return Results.BadRequest(CreateProblem(
"Invalid Request",
"Request body is required with stepId.",
StatusCodes.Status400BadRequest));
}
try
{
var result = await service.SkipStepAsync(requestContext!, request, ct).ConfigureAwait(false);
return Results.Ok(result);
}
catch (InvalidOperationException ex)
{
return Results.BadRequest(CreateProblem("Invalid Operation", ex.Message, StatusCodes.Status400BadRequest));
}
}).RequireAuthorization(PlatformPolicies.SetupWrite)
.WithName("SkipSetupStep")
.Produces<SetupSessionResponse>(StatusCodes.Status200OK)
.Produces<ProblemDetails>(StatusCodes.Status400BadRequest);
}
private static void MapDefinitionEndpoints(IEndpointRouteBuilder setup)
{
var definitions = setup.MapGroup("/definitions").WithTags("Setup Definitions");
// GET /api/v1/setup/definitions/steps - Get all step definitions
definitions.MapGet("/steps", async Task<IResult> (
HttpContext context,
PlatformRequestContextResolver resolver,
PlatformSetupService service,
CancellationToken ct) =>
{
if (!TryResolveContext(context, resolver, out var requestContext, out var failure))
{
return failure!;
}
var result = await service.GetStepDefinitionsAsync(ct).ConfigureAwait(false);
return Results.Ok(result);
}).RequireAuthorization(PlatformPolicies.SetupRead)
.WithName("GetSetupStepDefinitions")
.Produces<SetupStepDefinitionsResponse>(StatusCodes.Status200OK);
}
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(CreateProblem(
"Context Resolution Failed",
error ?? "Unable to resolve tenant context.",
StatusCodes.Status400BadRequest));
return false;
}
private static ProblemDetails CreateProblem(string title, string detail, int statusCode)
{
return new ProblemDetails
{
Title = title,
Detail = detail,
Status = statusCode,
Type = $"https://stella.ops/problems/{title.ToLowerInvariant().Replace(' ', '-')}"
};
}
}