using Cleanuparr.Api.Common; using Cleanuparr.Domain.Enums; using Cleanuparr.Infrastructure.Stats; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Cleanuparr.Api.Controllers; [ApiController] [Route("api/v2/stats")] [Authorize] public class StatsV2Controller : ControllerBase { private readonly IStatsService _statsService; public StatsV2Controller(IStatsService statsService) { _statsService = statsService; } /// /// Aggregated statistics for the given timeframe. Every section except health is scoped to the timeframe and, by /// default, excludes dry-run activity. /// /// Timeframe in hours (default 168, range 1-8760) /// Include dry-run activity in the timeframe-scoped sections (default false) [HttpGet] public async Task GetStats([FromQuery] int hours = 168, [FromQuery] bool includeDryRun = false) { hours = TimelineWindow.ClampHours(hours); StatsV2Response stats = await _statsService.GetStatsV2Async(hours, includeDryRun); return Ok(stats); } /// /// Bucketed timeline for a single metric. /// /// strikesIssued | recovered | removed | malwareBlocked | events /// Timeframe in hours (default 720, range 1-8760) /// Bucket size: hour | day | week | month. When omitted, hourly for timeframes up to 24h, daily otherwise. /// Include dry-run activity (default false) [HttpGet("timeline")] public async Task GetTimeline( [FromQuery] string metric = "events", [FromQuery] int hours = 720, [FromQuery] string? bucket = null, [FromQuery] bool includeDryRun = false) { TimelineBucketSize? size = null; if (!string.IsNullOrWhiteSpace(bucket)) { if (!Enum.TryParse(bucket, ignoreCase: true, out TimelineBucketSize parsed) || !Enum.IsDefined(parsed)) { return BadRequest($"Unsupported bucket '{bucket}'. Supported values: hour, day, week, month."); } size = parsed; } hours = TimelineWindow.ClampHours(hours); List series = await _statsService.GetTimelineAsync(metric, hours, size, includeDryRun); return Ok(series); } }