diff --git a/SVSim.Database/Repositories/Card/CardInventoryRepository.cs b/SVSim.Database/Repositories/Card/CardInventoryRepository.cs index fb7690f..6c1cc53 100644 --- a/SVSim.Database/Repositories/Card/CardInventoryRepository.cs +++ b/SVSim.Database/Repositories/Card/CardInventoryRepository.cs @@ -2,18 +2,19 @@ using Microsoft.EntityFrameworkCore; using SVSim.Database.Enums; using SVSim.Database.Models; using SVSim.Database.Services; +using SVSim.Database.Services.Inventory; namespace SVSim.Database.Repositories.Card; public class CardInventoryRepository : ICardInventoryRepository { private readonly SVSimDbContext _db; - private readonly RewardGrantService _grants; + private readonly IInventoryService _inv; - public CardInventoryRepository(SVSimDbContext db, RewardGrantService grants) + public CardInventoryRepository(SVSimDbContext db, IInventoryService inv) { _db = db; - _grants = grants; + _inv = inv; } public async Task DestructCards(long viewerId, IReadOnlyDictionary destructCounts) @@ -129,30 +130,27 @@ public class CardInventoryRepository : ICardInventoryRepository totalCost += (ulong)catalogCard.CollectionInfo.CraftCost * (ulong)num; } - // insufficient_vials checked after summing the full batch — all-or-nothing + // insufficient_vials pre-check (validation-before-mutation atomicity, keeps same error ordering) if (viewer.Currency.RedEther < totalCost) return CreateOutcome.Fail(CreateError.InsufficientVials); - using var tx = await _db.Database.BeginTransactionAsync(); + // Mutation phase via InventoryService transaction — freeplay-aware RedEther debit, + // card grants with cosmetic cascade. + await using var tx = await _inv.BeginAsync(viewerId); - // Debit RedEther directly. ApplyAsync only credits — debit-pair operations live in this - // repo, symmetric with destruct. - viewer.Currency.RedEther -= totalCost; + var spendResult = await tx.TrySpendAsync(SpendCurrency.RedEther, (long)totalCost); + if (!spendResult.Success) + return CreateOutcome.Fail(CreateError.InsufficientVials); - // Per-card grant via RewardGrantService — single source of truth for Card-typed grants, - // and fires the CardCosmeticReward cascade for first-time owners. See - // feedback_reward_grant_service memory. var allGrants = new List(); foreach (var (cardId, num) in createCounts) { - var granted = await _grants.ApplyAsync(viewer, UserGoodsType.Card, cardId, num); + var granted = await tx.GrantAsync(UserGoodsType.Card, cardId, num); allGrants.AddRange(granted); } - await _db.SaveChangesAsync(); await tx.CommitAsync(); - - return CreateOutcome.Ok(new CreateResult(viewer.Currency.RedEther, allGrants)); + return CreateOutcome.Ok(new CreateResult(tx.Viewer.Currency.RedEther, allGrants)); } public async Task SetProtected(long viewerId, long cardId, bool isProtected)