64 lines
2.2 KiB
C#
64 lines
2.2 KiB
C#
|
|
using Microsoft.AspNetCore.Mvc;
|
||
|
|
using w4c_workflows.Filters;
|
||
|
|
using w4c_workflows.Models.Nodes;
|
||
|
|
using w4c_workflows.Services.Nodes.Binary;
|
||
|
|
|
||
|
|
namespace w4c_workflows.Controllers;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Streams binary payloads referenced by run items. Items carry only an opaque,
|
||
|
|
/// content-addressed <c>assetId</c>; the bytes live in <see cref="IBinaryStore"/>.
|
||
|
|
/// The node catalog's HTTP/binary nodes write assets here, and the run panel's
|
||
|
|
/// item preview reads them back (inline preview with <c>?download=false</c>, or
|
||
|
|
/// a named download with <c>?download=true</c>).
|
||
|
|
/// </summary>
|
||
|
|
[ApiController]
|
||
|
|
[Route("api/binary")]
|
||
|
|
public class BinaryController : ControllerBase
|
||
|
|
{
|
||
|
|
private readonly IBinaryStore _store;
|
||
|
|
|
||
|
|
public BinaryController(IBinaryStore store)
|
||
|
|
{
|
||
|
|
_store = store;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Streams one stored asset. 404 when the id is unknown/malformed.</summary>
|
||
|
|
[HttpGet("{assetId}")]
|
||
|
|
[RequireScope("read")]
|
||
|
|
public async Task<IActionResult> 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);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Asset metadata only — used to label binary chips without downloading.</summary>
|
||
|
|
[HttpGet("{assetId}/meta")]
|
||
|
|
[RequireScope("read")]
|
||
|
|
public async Task<IActionResult> 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;
|
||
|
|
}
|
||
|
|
}
|