using Microsoft.AspNetCore.Mvc; namespace DeepDrftPublic.Controllers; /// /// Proxies the public release read surface (Phase 9) to DeepDrftAPI so the browser never /// makes a cross-origin request. Mirrors : the WASM client /// issues relative api/release/* requests against this host, which forwards them /// upstream. SSR prerender calls DeepDrftAPI directly via the same named client — no proxy /// hop on the server side. All forwarded routes are unauthenticated reads. /// [ApiController] [Route("api/release")] public class ReleaseProxyController : ControllerBase { private readonly HttpClient _upstream; private readonly ILogger _logger; public ReleaseProxyController(IHttpClientFactory httpClientFactory, ILogger logger) { _upstream = httpClientFactory.CreateClient("DeepDrft.API"); _logger = logger; } /// Proxies the paged release list, forwarding the optional medium, search (q), genre, and sort params. [HttpGet] public async Task GetReleases( [FromQuery] string? medium = null, [FromQuery] string? q = null, [FromQuery] string? genre = null, [FromQuery] int page = 1, [FromQuery] int pageSize = 20, [FromQuery] string? sortColumn = null, [FromQuery] bool sortDescending = false, CancellationToken ct = default) { var query = $"api/release?page={page}&pageSize={pageSize}&sortDescending={sortDescending}"; if (!string.IsNullOrWhiteSpace(medium)) query += $"&medium={Uri.EscapeDataString(medium)}"; if (!string.IsNullOrWhiteSpace(q)) query += $"&q={Uri.EscapeDataString(q)}"; if (!string.IsNullOrWhiteSpace(genre)) query += $"&genre={Uri.EscapeDataString(genre)}"; if (!string.IsNullOrWhiteSpace(sortColumn)) query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}"; return await RelayJson(query, "release list"); } /// Proxies a single release, addressed by its opaque EntryKey. A 404 (no such release) passes through verbatim. [HttpGet("{entryKey}")] public async Task GetReleaseByEntryKey(string entryKey, CancellationToken ct = default) => await RelayJson($"api/release/{Uri.EscapeDataString(entryKey)}", $"release {entryKey}", ct); // Small JSON payloads, buffered and relayed. Non-success statuses (notably 404) pass through // so the client renders them as valid states rather than collapsing to a 502. private async Task RelayJson(string upstreamPath, string description, CancellationToken ct = default) { HttpResponseMessage upstream; try { upstream = await _upstream.GetAsync(upstreamPath, HttpCompletionOption.ResponseHeadersRead, ct); } catch (Exception ex) { _logger.LogError(ex, "Upstream call to DeepDrftAPI {Description} failed", description); return StatusCode(502, "Upstream unavailable"); } using (upstream) { if (!upstream.IsSuccessStatusCode) { _logger.LogWarning("DeepDrftAPI {Description} returned {Status}", description, (int)upstream.StatusCode); return StatusCode((int)upstream.StatusCode); } var json = await upstream.Content.ReadAsStringAsync(ct); return Content(json, "application/json"); } } }