feature: CMS Opus status surfaces — backfill missing-N badge + upload Post-Processing phase (18.6)

This commit is contained in:
daniel-c-harvey
2026-06-23 14:06:21 -04:00
parent e5366bc4ec
commit 59f48bb8cb
9 changed files with 240 additions and 10 deletions
@@ -249,6 +249,40 @@ public class TrackController : ControllerBase
return Ok(status);
}
// GET api/track/opus-status ([ApiKeyAuthorize])
// Admin Post-Processing view (18.6): returns every track with a flag for whether it carries a COMPLETE
// Opus artifact — both the Opus audio AND the seek/setup sidecar present (TrackFormatResolver.HasOpusAsync,
// the same completeness rule the 18.5 Backfill-Opus pass enqueues against; a half-derived track counts as
// missing). Mirrors GET waveform-status exactly: same ApiKey auth, same unpaged whole-catalogue shape, same
// literal-route placement before "{trackId}". The CMS reads it to show the Backfill-Opus "missing N" badge
// and to poll per-track Post-Processing status after an upload.
[ApiKeyAuthorize]
[HttpGet("opus-status")]
public async Task<ActionResult> GetOpusStatus()
{
var tracks = await _sqlTrackService.GetAll();
if (!tracks.Success || tracks.Value is null)
{
var error = tracks.Messages.FirstOrDefault()?.Message ?? "Unknown error";
_logger.LogError("GetOpusStatus failed to load tracks: {Error}", error);
return StatusCode(500, "Failed to load tracks");
}
var status = new List<OpusStatusDto>(tracks.Value.Count);
foreach (var track in tracks.Value)
{
status.Add(new OpusStatusDto
{
TrackId = track.Id,
EntryKey = track.EntryKey,
TrackName = track.TrackName,
HasOpus = await _formatResolver.HasOpusAsync(track.EntryKey),
});
}
return Ok(status);
}
// POST api/track/duration/backfill ([ApiKeyAuthorize], no body)
// One-time admin backfill: for every track whose SQL duration is still null, read the duration from
// the vault audio and write it to SQL. Mirrors the waveform backfill posture. Idempotent — a re-run