Wire NowPlayingStats to live aggregates: add SQL track duration column, stats endpoint, and duration backfill
This commit is contained in:
@@ -3,6 +3,7 @@ using Data.Errors;
|
||||
using DeepDrftData.Data;
|
||||
using DeepDrftModels.DTOs;
|
||||
using DeepDrftModels.Entities;
|
||||
using DeepDrftModels.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Models.Common;
|
||||
@@ -157,6 +158,57 @@ public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
||||
.Select(g => new { ReleaseId = g.Key, Count = g.Count() })
|
||||
.ToDictionaryAsync(x => x.ReleaseId, x => x.Count, ct);
|
||||
|
||||
// Aggregate figures for the public home hero stat row, assembled in as few round-trips as is clean.
|
||||
// All counts go through Query / the release set's !IsDeleted filter so soft-deleted rows never count.
|
||||
// Mix runtime sums DurationSeconds with a null-coalesce to 0 so not-yet-backfilled rows contribute
|
||||
// zero rather than throwing or skewing the total. The cut release-type breakdown is grouped here so
|
||||
// a zero-count type is simply absent from the result (no present-with-zero row).
|
||||
public async Task<HomeStatsDto> GetHomeStatsAsync(CancellationToken ct = default)
|
||||
{
|
||||
var releases = _context.Set<ReleaseEntity>().Where(r => !r.IsDeleted);
|
||||
|
||||
var cutTrackCount = await Query
|
||||
.CountAsync(t => t.Release != null && t.Release.Medium == ReleaseMedium.Cut, ct);
|
||||
|
||||
var cutReleaseTypeCounts = await releases
|
||||
.Where(r => r.Medium == ReleaseMedium.Cut)
|
||||
.GroupBy(r => r.ReleaseType)
|
||||
.Select(g => new CutReleaseTypeCount { ReleaseType = g.Key, Count = g.Count() })
|
||||
.ToListAsync(ct);
|
||||
|
||||
var mixReleaseCount = await releases
|
||||
.CountAsync(r => r.Medium == ReleaseMedium.Mix, ct);
|
||||
|
||||
var mixRuntimeSeconds = await Query
|
||||
.Where(t => t.Release != null && t.Release.Medium == ReleaseMedium.Mix)
|
||||
.SumAsync(t => t.DurationSeconds ?? 0d, ct);
|
||||
|
||||
return new HomeStatsDto
|
||||
{
|
||||
CutTrackCount = cutTrackCount,
|
||||
CutReleaseTypeCounts = cutReleaseTypeCounts,
|
||||
MixReleaseCount = mixReleaseCount,
|
||||
MixRuntimeSeconds = mixRuntimeSeconds,
|
||||
};
|
||||
}
|
||||
|
||||
// EntryKey + stored duration for non-deleted tracks whose SQL duration is still null — the work list
|
||||
// the one-time duration backfill iterates. The migration cannot read the vault, so duration is filled
|
||||
// at runtime: this lists which rows still need it, the backfill reads each from the vault and writes
|
||||
// it back via UpdateDurationAsync.
|
||||
public async Task<List<TrackEntity>> GetTracksMissingDurationAsync(CancellationToken ct = default)
|
||||
=> await Query.Where(t => t.DurationSeconds == null).ToListAsync(ct);
|
||||
|
||||
// Set-based duration write for one track (no load round-trip), used by the backfill. The
|
||||
// DurationSeconds == null guard keeps a re-run from re-stamping updated_at on an already-filled row
|
||||
// and from clobbering a value the upload path may have set in the meantime.
|
||||
public async Task<int> UpdateDurationAsync(long id, double durationSeconds, CancellationToken ct = default)
|
||||
=> await Query
|
||||
.Where(t => t.Id == id && t.DurationSeconds == null)
|
||||
.ExecuteUpdateAsync(s => s
|
||||
.SetProperty(t => t.DurationSeconds, durationSeconds)
|
||||
.SetProperty(t => t.UpdatedAt, DateTime.UtcNow), ct);
|
||||
|
||||
// Resolve an existing release by its natural key (title + artist). Returns null when no match,
|
||||
// signalling the manager to create one. Soft-deleted releases never match.
|
||||
public async Task<ReleaseEntity?> GetReleaseByTitleAndArtistAsync(
|
||||
@@ -211,6 +263,7 @@ public class TrackRepository : Repository<DeepDrftContext, TrackEntity>
|
||||
target.TrackName = source.TrackName;
|
||||
target.TrackNumber = source.TrackNumber;
|
||||
target.OriginalFileName = source.OriginalFileName;
|
||||
target.DurationSeconds = source.DurationSeconds;
|
||||
target.ReleaseId = source.ReleaseId;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user