using Microsoft.EntityFrameworkCore; using SVSim.Database.Models; namespace SVSim.Database.Repositories.BattlePass; public sealed class BattlePassRepository : IBattlePassRepository { private readonly SVSimDbContext _db; // Process-level cache for the immutable level curve. Bootstrap re-baseline = host restart = cache cleared. private static IReadOnlyList? _curveCache; private static readonly SemaphoreSlim _curveCacheLock = new(1, 1); public BattlePassRepository(SVSimDbContext db) { _db = db; } public async Task GetActiveSeasonAsync(DateTimeOffset when, CancellationToken ct) { return await _db.BattlePassSeasons .AsNoTracking() .Where(s => s.StartDate <= when && s.EndDate > when) .OrderByDescending(s => s.StartDate) .FirstOrDefaultAsync(ct); } public Task GetSeasonAsync(int seasonId, CancellationToken ct) => _db.BattlePassSeasons.AsNoTracking().FirstOrDefaultAsync(s => s.Id == seasonId, ct); public async Task> GetSeasonRewardsAsync(int seasonId, CancellationToken ct) => await _db.BattlePassRewards.AsNoTracking() .Where(r => r.SeasonId == seasonId) .OrderBy(r => r.Track).ThenBy(r => r.Level) .ToListAsync(ct); public async Task> GetLevelCurveAsync(CancellationToken ct) { if (_curveCache is not null) return _curveCache; await _curveCacheLock.WaitAsync(ct); try { if (_curveCache is null) { _curveCache = await _db.BattlePassLevels.AsNoTracking() .OrderBy(e => e.Level) .ToListAsync(ct); } return _curveCache; } finally { _curveCacheLock.Release(); } } /// /// Drops the process-level level-curve cache. Tests that seed BattlePassLevels after the /// cache has already been populated (by an earlier test's HTTP call) must call this before /// re-seeding so the next read fetches fresh rows. /// internal static void ResetLevelCurveCache() => _curveCache = null; }