using System.Text;
using Microsoft.AspNetCore.Mvc;
namespace DeepDrftPublic.Controllers;
///
/// Proxies the anonymous telemetry write endpoints (POST api/event/play / api/event/share)
/// to DeepDrftAPI so the WASM client never makes a cross-origin request (Phase 16 §2.2). Mirrors
/// 's idiom — the named "DeepDrft.API" client forwards the
/// request upstream — but for a POST write: the small JSON body is buffered and relayed verbatim, and
/// the upstream status (202 on success, 4xx on a rejected payload, 429 on rate limit) passes back so the
/// beacon's fire-and-forget contract is preserved end to end. SSR never posts these — they originate
/// from the browser player/share surfaces only.
///
// A sendBeacon POST cannot attach a Blazor antiforgery token, so the telemetry write routes opt out
// explicitly. They are anonymous, idempotent-enough fire-and-forget logging — there is no
// state-changing user action to protect with CSRF tokens, and the upstream rate-limits by IP.
[ApiController]
[Route("api/event")]
[IgnoreAntiforgeryToken]
public class EventProxyController : ControllerBase
{
private readonly HttpClient _upstream;
private readonly ILogger _logger;
public EventProxyController(IHttpClientFactory httpClientFactory, ILogger logger)
{
_upstream = httpClientFactory.CreateClient("DeepDrft.API");
_logger = logger;
}
/// Proxies a play event upstream. Body is opaque JSON — validated by DeepDrftAPI, not here.
[HttpPost("play")]
public Task ForwardPlay(CancellationToken ct = default) => Forward("api/event/play", ct);
/// Proxies a share event upstream.
[HttpPost("share")]
public Task ForwardShare(CancellationToken ct = default) => Forward("api/event/share", ct);
private async Task Forward(string upstreamPath, CancellationToken ct)
{
// Buffer the small JSON body and relay it verbatim. Reading the raw body keeps the proxy
// transparent — it does not deserialize or re-shape the payload, just forwards it.
string body;
using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
{
body = await reader.ReadToEndAsync(ct);
}
using var content = new StringContent(body, Encoding.UTF8, "application/json");
HttpResponseMessage upstream;
try
{
upstream = await _upstream.PostAsync(upstreamPath, content, ct);
}
catch (Exception ex)
{
_logger.LogError(ex, "Upstream call to DeepDrftAPI {Path} failed", upstreamPath);
return StatusCode(502, "Upstream unavailable");
}
// Relay the upstream status as-is. Telemetry is fire-and-forget; the beacon never reads the
// body, so there is nothing to relay beyond the code (202 / 400 / 429 / 5xx).
using (upstream)
{
return StatusCode((int)upstream.StatusCode);
}
}
}