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(); } } }