Files
MilkyShots/Lactose/Controllers/StatsController.cs
T
REDCODE e1bb7d3330 refactor(stats): consolidate and async-ify stats queries
Rewrites StatsRepository to be fully async and collapses the ~30
synchronous round-trips into a bounded set of aggregated queries:

- Single grouped pass over non-deleted assets computes totals, storage,
  visibility breakdown, orphans, 7/30-day activity, and all
  data-completeness counts (missing metadata/thumbnail/preview/phash).
- AssetsByType + StorageByType come from one GROUP BY on Type.
- UsersByAccessLevel, TotalUsers, and users registered in 30 days come
  from one GROUP BY on AccessLevel.
- MIME breakdown and resolution buckets are now GROUP BY'd in SQL; the
  resolution bucket key is a translatable CASE expression on the max
  dimension, so no more loading every asset's dimensions into memory.
- AssetsWithNoAlbum / AssetsWithNoPerson use set-based NOT IN anti-joins
  over the Album.Assets navigation instead of per-row NOT EXISTS.
- TopTags uses the Tag.Assets navigation (join table indexed on TagsId).
- Drops IDisposable/Dispose() so the transient repo no longer disposes
  the shared scoped DbContext.
- StatsController action is now async.

StatsDto shape is unchanged, so the existing .http tests remain valid.

Refs #173
2026-08-17 22:52:27 +02:00

32 lines
1.1 KiB
C#

using Butter.Dtos.Stats;
using Lactose.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Lactose.Controllers;
/// <summary>
/// Provides aggregate statistics about the MilkyShots instance.
/// </summary>
/// <param name="logger">Logger instance.</param>
/// <param name="statsRepository">Stats repository.</param>
[ApiController]
[Route("api/[controller]")]
[Authorize(Roles = "Curator,Admin")]
public class StatsController(
ILogger<StatsController> logger,
IStatsRepository statsRepository
) : ControllerBase {
/// <summary>
/// Returns comprehensive aggregate statistics about the instance.
/// </summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A <see cref="StatsDto"/> with all gathered statistics.</returns>
[HttpGet]
public async Task<ActionResult<StatsDto>> Get(CancellationToken cancellationToken) {
logger.LogTrace("Stats requested");
var stats = await statsRepository.GetStatsAsync(cancellationToken);
return Ok(stats);
}
}