using Microsoft.AspNetCore.Mvc;
namespace DeepDrftPublic.Controllers;
///
/// Proxies public track API calls to DeepDrftAPI so the browser never makes
/// cross-origin requests. The WASM client points both named HttpClients at
/// this host; this controller forwards unauthenticated public routes upstream.
/// SSR prerender calls DeepDrftAPI directly (server-to-server) via the same
/// named clients — no proxy hop needed on the server side.
///
[ApiController]
[Route("api/track")]
public class TrackProxyController : ControllerBase
{
private readonly HttpClient _upstream;
private readonly ILogger _logger;
public TrackProxyController(IHttpClientFactory httpClientFactory, ILogger logger)
{
_upstream = httpClientFactory.CreateClient("DeepDrft.API");
_logger = logger;
}
/// Proxies paged track metadata from DeepDrftAPI.
[HttpGet("page")]
public async Task GetPage(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? sortColumn = null,
[FromQuery] bool sortDescending = false,
CancellationToken ct = default)
{
var query = $"api/track/page?page={page}&pageSize={pageSize}&sortDescending={sortDescending}";
if (!string.IsNullOrWhiteSpace(sortColumn))
query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(query, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/page failed");
return StatusCode(502, "Upstream unavailable");
}
using (upstream)
{
if (!upstream.IsSuccessStatusCode)
{
_logger.LogWarning("DeepDrftAPI track/page returned {Status}", (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
var json = await upstream.Content.ReadAsStringAsync(ct);
return Content(json, "application/json");
}
}
///
/// Proxies the random-track metadata lookup from DeepDrftAPI. Unauthenticated, same posture as
/// the paged listing. Small JSON, buffered and relayed; a 404 from upstream (empty library)
/// passes through so the client renders it as a valid empty state. Declared before the
/// parameterized "{trackId}" route so the literal segment is never treated as a trackId.
///
[HttpGet("random")]
public async Task GetRandom(CancellationToken ct = default)
{
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync("api/track/random", HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/random failed");
return StatusCode(502, "Upstream unavailable");
}
using (upstream)
{
if (!upstream.IsSuccessStatusCode)
{
_logger.LogWarning("DeepDrftAPI track/random returned {Status}", (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
var json = await upstream.Content.ReadAsStringAsync(ct);
return Content(json, "application/json");
}
}
///
/// Proxies single-track metadata lookup by vault entry key from DeepDrftAPI. Unauthenticated,
/// same posture as the paged listing. Small JSON, so it is buffered and relayed; a 404 from
/// upstream (no track with that entry key) passes through. Declared before the parameterized
/// "{trackId}" route, though the 3-segment template makes a collision impossible regardless.
///
[HttpGet("meta/by-key/{entryKey}")]
public async Task GetMetaByKey(string entryKey, CancellationToken ct = default)
{
var path = $"api/track/meta/by-key/{Uri.EscapeDataString(entryKey)}";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/meta/by-key/{EntryKey} failed", entryKey);
return StatusCode(502, "Upstream unavailable");
}
using (upstream)
{
if (!upstream.IsSuccessStatusCode)
{
_logger.LogWarning("DeepDrftAPI track/meta/by-key/{EntryKey} returned {Status}", entryKey, (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
var json = await upstream.Content.ReadAsStringAsync(ct);
return Content(json, "application/json");
}
}
///
/// Proxies audio streaming from DeepDrftAPI. Passes the optional byte offset
/// so seek-beyond-buffer works through the proxy without buffering.
///
[HttpGet("{trackId}")]
public async Task GetTrack(
string trackId,
[FromQuery] long offset = 0,
CancellationToken ct = default)
{
_logger.LogInformation("Proxying track {TrackId} offset {Offset}", trackId, offset);
var path = offset == 0
? $"api/track/{Uri.EscapeDataString(trackId)}"
: $"api/track/{Uri.EscapeDataString(trackId)}?offset={offset}";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId} failed", trackId);
return StatusCode(502, "Upstream unavailable");
}
if (!upstream.IsSuccessStatusCode)
{
upstream.Dispose();
_logger.LogWarning("DeepDrftAPI track/{TrackId} returned {Status}", trackId, (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
// Do NOT dispose upstream here — File() takes ownership of the response stream
// and disposes it after the body is sent.
var contentType = upstream.Content.Headers.ContentType?.ToString() ?? "audio/wav";
var contentLength = upstream.Content.Headers.ContentLength;
// Forward Content-Length so the WASM player has duration info from the WAV header length.
if (contentLength.HasValue)
Response.ContentLength = contentLength.Value;
var stream = await upstream.Content.ReadAsStreamAsync(ct);
HttpContext.Response.RegisterForDispose(upstream);
return File(stream, contentType, enableRangeProcessing: false);
}
///
/// Proxies a track's stored waveform profile (JSON) from DeepDrftAPI. Unauthenticated,
/// same posture as the audio stream forward. The profile is small JSON, so it is buffered
/// and relayed rather than streamed; a 404 from upstream (no profile stored) passes through.
///
[HttpGet("{trackId}/waveform")]
public async Task GetWaveform(string trackId, CancellationToken ct = default)
{
var path = $"api/track/{Uri.EscapeDataString(trackId)}/waveform";
HttpResponseMessage upstream;
try
{
upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId}/waveform failed", trackId);
return StatusCode(502, "Upstream unavailable");
}
using (upstream)
{
if (!upstream.IsSuccessStatusCode)
{
_logger.LogWarning("DeepDrftAPI track/{TrackId}/waveform returned {Status}", trackId, (int)upstream.StatusCode);
return StatusCode((int)upstream.StatusCode);
}
var json = await upstream.Content.ReadAsStringAsync(ct);
return Content(json, "application/json");
}
}
}