be1a55fd37
Add TotalPlays + UniqueListeners to HomeStatsDto, composed at StatsController from IEventService (no migration). Card reads via existing persistent-state-bridged round-trip.
60 lines
2.6 KiB
C#
60 lines
2.6 KiB
C#
using DeepDrftData;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace DeepDrftAPI.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class StatsController : ControllerBase
|
|
{
|
|
private readonly ITrackService _sqlTrackService;
|
|
private readonly IEventService _eventService;
|
|
private readonly 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 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 trackResult = await _sqlTrackService.GetHomeStats(ct);
|
|
if (!trackResult.Success || trackResult.Value is null)
|
|
{
|
|
var error = trackResult.Messages.FirstOrDefault()?.Message ?? "Unknown error";
|
|
_logger.LogError("GetHome stats failed: {Error}", error);
|
|
return StatusCode(500, "Failed to load stats");
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|