w4c-workflows-api/Controllers/NodesController.cs

117 lines
3.8 KiB
C#
Raw Permalink Normal View History

2026-09-11 16:04:50 +00:00
using Microsoft.AspNetCore.Mvc;
using w4c_workflows.Filters;
using w4c_workflows.Models.Nodes;
using w4c_workflows.Services.Nodes;
2026-09-11 22:02:46 +00:00
using w4c_workflows.Services.Security;
2026-09-11 16:04:50 +00:00
namespace w4c_workflows.Controllers;
/// <summary>
/// Node catalog surface. The authoring UI uses this to render its palette and
/// its generic parameter forms; it authenticates with the tenant operator key
/// like the rest of the control plane.
/// </summary>
[ApiController]
[Route("api/nodes")]
public class NodesController : ControllerBase
{
private readonly NodeBlueprintCatalog _catalog;
private readonly NodeExecutorRegistry _executors;
2026-09-11 22:02:46 +00:00
private readonly NodePermissionPolicy? _permissions;
2026-09-11 16:04:50 +00:00
2026-09-11 22:02:46 +00:00
public NodesController(
NodeBlueprintCatalog catalog,
NodeExecutorRegistry executors,
NodePermissionPolicy? permissions = null)
2026-09-11 16:04:50 +00:00
{
_catalog = catalog;
_executors = executors;
2026-09-11 22:02:46 +00:00
_permissions = permissions;
2026-09-11 16:04:50 +00:00
}
/// <summary>Lists node types for the palette, with optional search/filter.</summary>
[HttpGet]
[RequireScope("read")]
public IActionResult List(
[FromQuery] string? search,
[FromQuery] string? kind,
[FromQuery] string? category,
[FromQuery] bool includeHidden = false)
{
var results = _catalog.Search(search, kind, category);
if (!includeHidden)
results = results.Where(b => !b.Hidden).ToList();
2026-09-11 22:02:46 +00:00
// Hide barred node types from the palette so the author is never offered
// a step that the compiler and run kernel would refuse anyway.
if (_permissions != null)
{
var tenantId = HttpContext?.Items["TenantId"] as string;
results = results.Where(b => _permissions.IsPermitted(b, tenantId)).ToList();
}
2026-09-11 16:04:50 +00:00
return Ok(results.Select(Summarize));
}
/// <summary>All category labels, for the palette's category rail.</summary>
[HttpGet("categories")]
[RequireScope("read")]
public IActionResult Categories() => Ok(_catalog.Categories);
/// <summary>Full blueprint for one node type (optionally a pinned version).</summary>
[HttpGet("{type}")]
[RequireScope("read")]
public IActionResult Get(string type, [FromQuery] double? version)
{
var blueprint = _catalog.Resolve(type, version);
if (blueprint is null)
return NotFound(new { error = $"unknown node type '{type}'" });
return Ok(new NodeDetail(blueprint, _executors.CanRun(blueprint.Type)));
}
private NodeSummary Summarize(NodeBlueprint blueprint) => new(
blueprint.Type,
blueprint.Version,
blueprint.DisplayName,
blueprint.Description,
blueprint.Kind,
blueprint.Categories,
blueprint.Icon,
blueprint.IconColor,
blueprint.RunMode,
blueprint.Hidden,
blueprint.Origin,
blueprint.Inputs.Count,
blueprint.Outputs.Count,
blueprint.Inputs,
blueprint.Outputs,
blueprint.ProducesErrorBranch,
blueprint.Credentials.Any(c => c.Required),
_executors.CanRun(blueprint.Type));
}
/// <summary>Palette entry: blueprint identity plus port shape, without the full form schema.</summary>
public sealed record NodeSummary(
string Type,
double Version,
string DisplayName,
string? Description,
string Kind,
IReadOnlyList<string> Categories,
string? Icon,
string? IconColor,
string RunMode,
bool Hidden,
string Origin,
int Inputs,
int Outputs,
IReadOnlyList<NodePort> InputPorts,
IReadOnlyList<NodePort> OutputPorts,
bool HasErrorBranch,
bool RequiresCredentials,
bool Runnable);
/// <summary>Full blueprint detail plus whether an executor is installed for it.</summary>
public sealed record NodeDetail(NodeBlueprint Blueprint, bool Runnable);