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, forwarding optional filter params. [HttpGet("page")] public async Task GetPage( [FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? sortColumn = null, [FromQuery] bool sortDescending = false, [FromQuery] string? q = null, [FromQuery] string? album = null, [FromQuery] string? genre = null, [FromQuery] long? releaseId = null, CancellationToken ct = default) { var query = $"api/track/page?page={page}&pageSize={pageSize}&sortDescending={sortDescending}"; if (!string.IsNullOrWhiteSpace(sortColumn)) query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}"; if (!string.IsNullOrWhiteSpace(q)) query += $"&q={Uri.EscapeDataString(q)}"; if (!string.IsNullOrWhiteSpace(album)) query += $"&album={Uri.EscapeDataString(album)}"; if (!string.IsNullOrWhiteSpace(genre)) query += $"&genre={Uri.EscapeDataString(genre)}"; if (releaseId is { } rid) query += $"&releaseId={rid}"; 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 the distinct-albums browse list from DeepDrftAPI. Unauthenticated, same posture as /// the paged listing. Small JSON, buffered and relayed. Literal segment, declared before the /// parameterized "{trackId}" route so it is never treated as a trackId. /// [HttpGet("albums")] public async Task GetAlbums(CancellationToken ct = default) { HttpResponseMessage upstream; try { upstream = await _upstream.GetAsync("api/track/albums", HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Upstream call to DeepDrftAPI track/albums failed"); return StatusCode(502, "Upstream unavailable"); } using (upstream) { if (!upstream.IsSuccessStatusCode) { _logger.LogWarning("DeepDrftAPI track/albums returned {Status}", (int)upstream.StatusCode); return StatusCode((int)upstream.StatusCode); } var json = await upstream.Content.ReadAsStringAsync(ct); return Content(json, "application/json"); } } /// /// Proxies the distinct-genres browse list from DeepDrftAPI. Unauthenticated, same posture as /// the paged listing. Small JSON, buffered and relayed. Literal segment, declared before the /// parameterized "{trackId}" route so it is never treated as a trackId. /// [HttpGet("genres")] public async Task GetGenres(CancellationToken ct = default) { HttpResponseMessage upstream; try { upstream = await _upstream.GetAsync("api/track/genres", HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Upstream call to DeepDrftAPI track/genres failed"); return StatusCode(502, "Upstream unavailable"); } using (upstream) { if (!upstream.IsSuccessStatusCode) { _logger.LogWarning("DeepDrftAPI track/genres 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 as a transparent HTTP Range relay. /// Forwards the incoming Range header upstream and relays the upstream status /// (200 full, 206 partial, 416 unsatisfiable) and range-related response headers /// back to the browser verbatim. The proxy does not slice — the upstream already did. /// [HttpGet("{trackId}")] public async Task GetTrack( string trackId, CancellationToken ct = default) { var rangeHeader = Request.Headers.Range.ToString(); _logger.LogInformation("Proxying track {TrackId} range '{Range}'", trackId, rangeHeader); var request = new HttpRequestMessage( HttpMethod.Get, $"api/track/{Uri.EscapeDataString(trackId)}"); // Forward the browser's Range header upstream so DeepDrftAPI slices the file. // TryAddWithoutValidation avoids RangeHeaderValue reparsing — we relay the raw // header verbatim, keeping the proxy transparent. if (!string.IsNullOrEmpty(rangeHeader)) request.Headers.TryAddWithoutValidation("Range", rangeHeader); HttpResponseMessage upstream; try { upstream = await _upstream.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId} failed", trackId); return StatusCode(502, "Upstream unavailable"); } // 416 Range Not Satisfiable is a legitimate upstream answer (seek past EOF); // relay it as-is rather than collapsing it into a 502. if ((int)upstream.StatusCode == StatusCodes.Status416RangeNotSatisfiable) { upstream.Dispose(); return StatusCode(StatusCodes.Status416RangeNotSatisfiable); } // 206 Partial Content reports IsSuccessStatusCode == true, so this guard only // catches genuine upstream failures (404, 5xx). if (!upstream.IsSuccessStatusCode) { upstream.Dispose(); _logger.LogWarning("DeepDrftAPI track/{TrackId} returned {Status}", trackId, (int)upstream.StatusCode); return StatusCode((int)upstream.StatusCode); } // Stream the body manually rather than via File(): FileStreamResult forces the // status to 200 (with enableRangeProcessing:false) and would clobber a relayed // 206. Writing status + headers + body directly keeps the partial-content // contract intact, with the proxy doing zero slicing of its own. HttpContext.Response.RegisterForDispose(upstream); Response.StatusCode = (int)upstream.StatusCode; Response.ContentType = upstream.Content.Headers.ContentType?.ToString() ?? "audio/wav"; // Relay range-related headers so the browser sees the same partial-content // contract the upstream emitted. if (upstream.Headers.AcceptRanges.Count > 0) Response.Headers.AcceptRanges = string.Join(", ", upstream.Headers.AcceptRanges); if (upstream.Content.Headers.ContentRange is { } contentRange) Response.Headers.ContentRange = contentRange.ToString(); if (upstream.Content.Headers.ContentLength is { } contentLength) Response.ContentLength = contentLength; var stream = await upstream.Content.ReadAsStreamAsync(ct); try { await stream.CopyToAsync(Response.Body, ct); return new EmptyResult(); } catch (OperationCanceledException) { // Client navigated away or issued a new seek — nothing to do. // The upstream connection is cleaned up via RegisterForDispose. return new EmptyResult(); } } /// /// 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"); } } }