feat(stats): flip home Plays card live (Phase 16.5)
Add TotalPlays + UniqueListeners to HomeStatsDto, composed at StatsController from IEventService (no migration). Card reads via existing persistent-state-bridged round-trip.
This commit is contained in:
@@ -8,29 +8,52 @@ namespace DeepDrftAPI.Controllers;
|
||||
public class StatsController : ControllerBase
|
||||
{
|
||||
private readonly ITrackService _sqlTrackService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly ILogger<StatsController> _logger;
|
||||
|
||||
public StatsController(ITrackService sqlTrackService, ILogger<StatsController> logger)
|
||||
public StatsController(
|
||||
ITrackService sqlTrackService, IEventService eventService, ILogger<StatsController> logger)
|
||||
{
|
||||
_sqlTrackService = sqlTrackService;
|
||||
_eventService = eventService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// GET api/stats/home (unauthenticated)
|
||||
// Aggregate figures behind the public home hero stat row — one read for all three cards. Same auth
|
||||
// posture as the other public browse reads (GET api/track/page). The aggregation lives in the SQL
|
||||
// service/repository; this controller stays a thin HTTP boundary.
|
||||
// posture as the other public browse reads (GET api/track/page). The figures span two domains:
|
||||
// the track-domain aggregation (Cuts/Mixes cards) lives in the SQL track service; the play-domain
|
||||
// figures (Phase 16 Plays card — total plays + unique listeners) live in the event service. This
|
||||
// controller is the thin composition seam that assembles both into one HomeStatsDto — neither
|
||||
// domain reaches into the other's tables. Play/listener figures are best-effort: a telemetry read
|
||||
// failure (or the not-yet-applied migration) leaves them at zero rather than failing the whole card.
|
||||
[HttpGet("home")]
|
||||
public async Task<ActionResult> GetHome(CancellationToken ct = default)
|
||||
{
|
||||
var result = await _sqlTrackService.GetHomeStats(ct);
|
||||
if (!result.Success || result.Value is null)
|
||||
var trackResult = await _sqlTrackService.GetHomeStats(ct);
|
||||
if (!trackResult.Success || trackResult.Value is null)
|
||||
{
|
||||
var error = result.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
var error = trackResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
||||
_logger.LogError("GetHome stats failed: {Error}", error);
|
||||
return StatusCode(500, "Failed to load stats");
|
||||
}
|
||||
|
||||
return Ok(result.Value);
|
||||
var stats = trackResult.Value;
|
||||
|
||||
var playsResult = await _eventService.GetTotalPlayCount(ct);
|
||||
if (playsResult is { Success: true })
|
||||
stats.TotalPlays = playsResult.Value;
|
||||
else
|
||||
_logger.LogWarning("GetHome total-plays read failed; Plays card falls back to 0: {Error}",
|
||||
playsResult.Messages.FirstOrDefault()?.Message);
|
||||
|
||||
var listenersResult = await _eventService.GetDistinctListenerCount(ct);
|
||||
if (listenersResult is { Success: true })
|
||||
stats.UniqueListeners = listenersResult.Value;
|
||||
else
|
||||
_logger.LogWarning("GetHome unique-listeners read failed; secondary line falls back to 0: {Error}",
|
||||
listenersResult.Messages.FirstOrDefault()?.Message);
|
||||
|
||||
return Ok(stats);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user