186 lines
6.5 KiB
C#
186 lines
6.5 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using StellaOps.Auth.ServerIntegration.Tenancy;
|
|
using StellaOps.JobEngine.Infrastructure.Repositories;
|
|
using StellaOps.JobEngine.WebService.Contracts;
|
|
using StellaOps.JobEngine.WebService.Services;
|
|
using static StellaOps.Localization.T;
|
|
|
|
namespace StellaOps.JobEngine.WebService.Endpoints;
|
|
|
|
|
|
/// <summary>
|
|
/// REST API endpoints for runs (batch executions).
|
|
/// </summary>
|
|
public static class RunEndpoints
|
|
{
|
|
/// <summary>
|
|
/// Maps run endpoints to the route builder.
|
|
/// </summary>
|
|
public static RouteGroupBuilder MapRunEndpoints(this IEndpointRouteBuilder app)
|
|
{
|
|
var group = app.MapGroup("/api/v1/jobengine/runs")
|
|
.WithTags("Orchestrator Runs")
|
|
.RequireAuthorization(JobEnginePolicies.Read)
|
|
.RequireTenant();
|
|
|
|
group.MapGet(string.Empty, ListRuns)
|
|
.WithName("Orchestrator_ListRuns")
|
|
.WithDescription(_t("orchestrator.run.list_description"));
|
|
|
|
group.MapGet("{runId:guid}", GetRun)
|
|
.WithName("Orchestrator_GetRun")
|
|
.WithDescription(_t("orchestrator.run.get_description"));
|
|
|
|
group.MapGet("{runId:guid}/jobs", GetRunJobs)
|
|
.WithName("Orchestrator_GetRunJobs")
|
|
.WithDescription(_t("orchestrator.run.get_jobs_description"));
|
|
|
|
group.MapGet("{runId:guid}/summary", GetRunSummary)
|
|
.WithName("Orchestrator_GetRunSummary")
|
|
.WithDescription(_t("orchestrator.run.get_summary_description"));
|
|
|
|
return group;
|
|
}
|
|
|
|
private static async Task<IResult> ListRuns(
|
|
HttpContext context,
|
|
[FromServices] TenantResolver tenantResolver,
|
|
[FromServices] IRunRepository repository,
|
|
[FromQuery] Guid? sourceId = null,
|
|
[FromQuery] string? runType = null,
|
|
[FromQuery] string? status = null,
|
|
[FromQuery] string? projectId = null,
|
|
[FromQuery] string? createdAfter = null,
|
|
[FromQuery] string? createdBefore = null,
|
|
[FromQuery] int? limit = null,
|
|
[FromQuery] string? cursor = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var tenantId = tenantResolver.Resolve(context);
|
|
var effectiveLimit = EndpointHelpers.GetLimit(limit);
|
|
var offset = EndpointHelpers.ParseCursorOffset(cursor);
|
|
var parsedStatus = EndpointHelpers.TryParseRunStatus(status);
|
|
var parsedCreatedAfter = EndpointHelpers.TryParseDateTimeOffset(createdAfter);
|
|
var parsedCreatedBefore = EndpointHelpers.TryParseDateTimeOffset(createdBefore);
|
|
|
|
var runs = await repository.ListAsync(
|
|
tenantId,
|
|
sourceId,
|
|
runType,
|
|
parsedStatus,
|
|
projectId,
|
|
parsedCreatedAfter,
|
|
parsedCreatedBefore,
|
|
effectiveLimit,
|
|
offset,
|
|
cancellationToken).ConfigureAwait(false);
|
|
|
|
var responses = runs.Select(RunResponse.FromDomain).ToList();
|
|
var nextCursor = EndpointHelpers.CreateNextCursor(offset, effectiveLimit, responses.Count);
|
|
|
|
return Results.Ok(new RunListResponse(responses, nextCursor));
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
}
|
|
|
|
private static async Task<IResult> GetRun(
|
|
HttpContext context,
|
|
[FromRoute] Guid runId,
|
|
[FromServices] TenantResolver tenantResolver,
|
|
[FromServices] IRunRepository repository,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var tenantId = tenantResolver.Resolve(context);
|
|
|
|
var run = await repository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
|
if (run is null)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
return Results.Ok(RunResponse.FromDomain(run));
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
}
|
|
|
|
private static async Task<IResult> GetRunJobs(
|
|
HttpContext context,
|
|
[FromRoute] Guid runId,
|
|
[FromServices] TenantResolver tenantResolver,
|
|
[FromServices] IRunRepository runRepository,
|
|
[FromServices] IJobRepository jobRepository,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var tenantId = tenantResolver.Resolve(context);
|
|
|
|
// Verify run exists
|
|
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
|
if (run is null)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
var jobs = await jobRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
|
var responses = jobs.Select(JobResponse.FromDomain).ToList();
|
|
|
|
return Results.Ok(new JobListResponse(responses, null));
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
}
|
|
|
|
private static async Task<IResult> GetRunSummary(
|
|
HttpContext context,
|
|
[FromRoute] Guid runId,
|
|
[FromServices] TenantResolver tenantResolver,
|
|
[FromServices] IRunRepository runRepository,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var tenantId = tenantResolver.Resolve(context);
|
|
|
|
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
|
if (run is null)
|
|
{
|
|
return Results.NotFound();
|
|
}
|
|
|
|
// Return the aggregate counts from the run itself
|
|
var summary = new
|
|
{
|
|
runId = run.RunId,
|
|
status = run.Status.ToString().ToLowerInvariant(),
|
|
totalJobs = run.TotalJobs,
|
|
completedJobs = run.CompletedJobs,
|
|
succeededJobs = run.SucceededJobs,
|
|
failedJobs = run.FailedJobs,
|
|
pendingJobs = run.TotalJobs - run.CompletedJobs,
|
|
createdAt = run.CreatedAt,
|
|
startedAt = run.StartedAt,
|
|
completedAt = run.CompletedAt
|
|
};
|
|
|
|
return Results.Ok(summary);
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
return Results.BadRequest(new { error = ex.Message });
|
|
}
|
|
}
|
|
}
|