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; } }