using Microsoft.AspNetCore.Mvc; using System.ComponentModel.DataAnnotations; using StellaOps.Auth.Abstractions; using StellaOps.Scheduler.Models; using StellaOps.Scheduler.WebService.Auth; namespace StellaOps.Scheduler.WebService.GraphJobs; public static class GraphJobEndpointExtensions { public static void MapGraphJobEndpoints(this IEndpointRouteBuilder builder) { var group = builder.MapGroup("/graphs"); group.MapPost("/build", CreateGraphBuildJob); group.MapPost("/overlays", CreateGraphOverlayJob); group.MapGet("/jobs", GetGraphJobs); group.MapPost("/hooks/completed", CompleteGraphJob); group.MapGet("/overlays/lag", GetOverlayLagMetrics); } internal static async Task CreateGraphBuildJob( [FromBody] GraphBuildJobRequest request, HttpContext httpContext, [FromServices] ITenantContextAccessor tenantAccessor, [FromServices] IScopeAuthorizer authorizer, [FromServices] IGraphJobService jobService, CancellationToken cancellationToken) { try { authorizer.EnsureScope(httpContext, StellaOpsScopes.GraphWrite); var tenant = tenantAccessor.GetTenant(httpContext); var job = await jobService.CreateBuildJobAsync(tenant.TenantId, request, cancellationToken); return Results.Created($"/graphs/jobs/{job.Id}", GraphJobResponse.From(job)); } catch (UnauthorizedAccessException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status401Unauthorized); } catch (InvalidOperationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status403Forbidden); } catch (ValidationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status400BadRequest); } catch (KeyNotFoundException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status404NotFound); } } internal static async Task CreateGraphOverlayJob( [FromBody] GraphOverlayJobRequest request, HttpContext httpContext, [FromServices] ITenantContextAccessor tenantAccessor, [FromServices] IScopeAuthorizer authorizer, [FromServices] IGraphJobService jobService, CancellationToken cancellationToken) { try { authorizer.EnsureScope(httpContext, StellaOpsScopes.GraphWrite); var tenant = tenantAccessor.GetTenant(httpContext); var job = await jobService.CreateOverlayJobAsync(tenant.TenantId, request, cancellationToken); return Results.Created($"/graphs/jobs/{job.Id}", GraphJobResponse.From(job)); } catch (UnauthorizedAccessException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status401Unauthorized); } catch (InvalidOperationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status403Forbidden); } catch (ValidationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status400BadRequest); } } internal static async Task GetGraphJobs( [AsParameters] GraphJobQuery query, HttpContext httpContext, [FromServices] ITenantContextAccessor tenantAccessor, [FromServices] IScopeAuthorizer authorizer, [FromServices] IGraphJobService jobService, CancellationToken cancellationToken) { try { authorizer.EnsureScope(httpContext, StellaOpsScopes.GraphRead); var tenant = tenantAccessor.GetTenant(httpContext); var jobs = await jobService.GetJobsAsync(tenant.TenantId, query, cancellationToken); return Results.Ok(jobs); } catch (UnauthorizedAccessException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status401Unauthorized); } catch (InvalidOperationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status403Forbidden); } } internal static async Task CompleteGraphJob( [FromBody] GraphJobCompletionRequest request, HttpContext httpContext, [FromServices] ITenantContextAccessor tenantAccessor, [FromServices] IScopeAuthorizer authorizer, [FromServices] IGraphJobService jobService, CancellationToken cancellationToken) { try { authorizer.EnsureScope(httpContext, StellaOpsScopes.GraphWrite); var tenant = tenantAccessor.GetTenant(httpContext); var response = await jobService.CompleteJobAsync(tenant.TenantId, request, cancellationToken); return Results.Ok(response); } catch (UnauthorizedAccessException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status401Unauthorized); } catch (KeyNotFoundException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status404NotFound); } catch (InvalidOperationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status403Forbidden); } catch (ValidationException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status400BadRequest); } } internal static async Task GetOverlayLagMetrics( HttpContext httpContext, [FromServices] ITenantContextAccessor tenantAccessor, [FromServices] IScopeAuthorizer authorizer, [FromServices] IGraphJobService jobService, CancellationToken cancellationToken) { try { authorizer.EnsureScope(httpContext, StellaOpsScopes.GraphRead); var tenant = tenantAccessor.GetTenant(httpContext); var metrics = await jobService.GetOverlayLagMetricsAsync(tenant.TenantId, cancellationToken); return Results.Ok(metrics); } catch (UnauthorizedAccessException ex) { return Results.Json(new { error = ex.Message }, statusCode: StatusCodes.Status401Unauthorized); } } }