using Microsoft.AspNetCore.Mvc;
using w4c_workflows.Filters;
using w4c_workflows.Models.Nodes;
using w4c_workflows.Services.Nodes.Binary;
namespace w4c_workflows.Controllers;
///
/// Streams binary payloads referenced by run items. Items carry only an opaque,
/// content-addressed assetId; the bytes live in .
/// The node catalog's HTTP/binary nodes write assets here, and the run panel's
/// item preview reads them back (inline preview with ?download=false, or
/// a named download with ?download=true).
///
[ApiController]
[Route("api/binary")]
public class BinaryController : ControllerBase
{
private readonly IBinaryStore _store;
public BinaryController(IBinaryStore store)
{
_store = store;
}
/// Streams one stored asset. 404 when the id is unknown/malformed.
[HttpGet("{assetId}")]
[RequireScope("read")]
public async Task Get(string assetId, [FromQuery] bool download = false, CancellationToken ct = default)
{
var meta = await _store.DescribeAsync(assetId, ct);
var stream = await _store.OpenAsync(assetId, ct);
if (stream == null)
return NotFound(new { error = "Asset not found." });
var contentType = meta?.MimeType ?? "application/octet-stream";
var fileName = SafeFileName(meta?.FileName) ?? "asset";
return download
? File(stream, contentType, fileName)
: File(stream, contentType);
}
/// Asset metadata only — used to label binary chips without downloading.
[HttpGet("{assetId}/meta")]
[RequireScope("read")]
public async Task Meta(string assetId, CancellationToken ct)
{
var meta = await _store.DescribeAsync(assetId, ct);
if (meta == null)
return NotFound(new { error = "Asset not found." });
return Ok(meta);
}
private static string? SafeFileName(string? name)
{
if (string.IsNullOrWhiteSpace(name))
return null;
var trimmed = name.Replace('\\', '/');
var slash = trimmed.LastIndexOf('/');
return slash >= 0 ? trimmed[(slash + 1)..] : trimmed;
}
}