feat(bp): /battle_pass/item_list — derives product per active season

Adds BattlePassSalesPeriodInfoDto, BattlePassProductDto, BattlePassItemListResponse DTOs,
GetItemListAsync on BattlePassService (one product if not premium + CanPurchase, empty if
already premium or off-season), and the /battle_pass/item_list controller action.
2 new integration tests; all 408 pass.
This commit is contained in:
gamer147
2026-05-26 23:26:46 -04:00
parent d877febcb8
commit 0ceab721e9
7 changed files with 215 additions and 0 deletions

View File

@@ -94,6 +94,44 @@ public sealed class BattlePassService : IBattlePassService
};
}
public async Task<BattlePassItemListResponse?> GetItemListAsync(long viewerId, CancellationToken ct)
{
var now = _time.GetUtcNow();
var season = await _bp.GetActiveSeasonAsync(now, ct);
if (season is null) return null;
var progress = await _viewerBp.GetOrCreateProgressAsync(viewerId, season.Id, ct);
var response = new BattlePassItemListResponse
{
PremiumPassDescription = season.Description,
SalesPeriodInfo = new BattlePassSalesPeriodInfoDto
{
SalesPeriodTime = FormatWireDate(season.EndDate),
},
Products = new List<BattlePassProductDto>(),
};
// One product per active season; empty if viewer is already premium.
if (!progress.IsPremium && season.CanPurchase)
{
response.Products.Add(new BattlePassProductDto
{
Id = season.Id * 1000,
SeasonId = season.Id,
Name = $"{season.Name} Premium Pass",
PriceCrystal = season.PriceCrystal,
Description = season.Description,
SalesPeriodInfo = new BattlePassSalesPeriodInfoDto
{
SalesPeriodTime = FormatWireDate(season.EndDate),
},
});
}
return response;
}
internal static int ComputeLevel(IReadOnlyList<BattlePassLevelEntry> curve, int point)
{
if (curve.Count == 0) return 1;

View File

@@ -13,4 +13,10 @@ public interface IBattlePassService
/// (controller emits empty body in that case).
/// </summary>
Task<BattlePassInfoResponse?> GetInfoAsync(long viewerId, CancellationToken ct);
/// <summary>
/// /battle_pass/item_list payload. Returns one product per active season; empty products
/// array if the viewer already owns premium for the active season. Null when no active season.
/// </summary>
Task<BattlePassItemListResponse?> GetItemListAsync(long viewerId, CancellationToken ct);
}