feat(inventory): scaffold InventoryService namespace types
Empty interfaces + records for IInventoryService, IInventoryTransaction, InventoryCommitResult, InventoryLoadConfig, InventoryCatalogException. Implementation lands in subsequent commits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
28
SVSim.Database/Services/Inventory/IInventoryService.cs
Normal file
28
SVSim.Database/Services/Inventory/IInventoryService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Services;
|
||||
|
||||
namespace SVSim.Database.Services.Inventory;
|
||||
|
||||
public interface IInventoryService
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads the viewer with the canonical inventory graph (Cards.Card, Sleeves, Emblems,
|
||||
/// LeaderSkins, Degrees, MyPageBackgrounds, Items.Item under AsSplitQuery), opens a DB
|
||||
/// transaction, and returns a builder for queueing operations. Throws
|
||||
/// <see cref="InventoryViewerNotFoundException"/> if the viewer does not exist.
|
||||
/// </summary>
|
||||
Task<IInventoryTransaction> BeginAsync(
|
||||
long viewerId,
|
||||
CancellationToken ct = default,
|
||||
Action<InventoryLoadConfig>? configure = null);
|
||||
|
||||
Task<IReadOnlyList<OwnedCardEntry>> EffectiveOwnedCardsAsync(Viewer viewer, CancellationToken ct = default);
|
||||
Task<EffectiveCosmetics> EffectiveCosmeticsAsync(Viewer viewer, CancellationToken ct = default);
|
||||
long EffectiveBalance(Viewer viewer, SpendCurrency currency);
|
||||
}
|
||||
|
||||
public sealed class InventoryViewerNotFoundException : Exception
|
||||
{
|
||||
public InventoryViewerNotFoundException(long viewerId)
|
||||
: base($"Viewer {viewerId} not found") { }
|
||||
}
|
||||
30
SVSim.Database/Services/Inventory/IInventoryTransaction.cs
Normal file
30
SVSim.Database/Services/Inventory/IInventoryTransaction.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.Database.Services;
|
||||
|
||||
namespace SVSim.Database.Services.Inventory;
|
||||
|
||||
/// <summary>
|
||||
/// Scoped builder returned by <see cref="IInventoryService.BeginAsync"/>. Queue spend +
|
||||
/// grant operations; commit to save and assemble the <see cref="InventoryCommitResult"/>.
|
||||
/// <para>
|
||||
/// Dispose without committing rolls back the underlying DB transaction and detaches any
|
||||
/// in-memory mutations. <b>Always</b> wrap in <c>await using</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IInventoryTransaction : IAsyncDisposable
|
||||
{
|
||||
Viewer Viewer { get; }
|
||||
bool IsFreeplay { get; }
|
||||
|
||||
Task<SpendResult> TrySpendAsync(SpendCurrency currency, long cost, CancellationToken ct = default);
|
||||
Task<SpendResult> TryDebitAsync(UserGoodsType type, long detailId, int num, CancellationToken ct = default);
|
||||
Task<IReadOnlyList<GrantedReward>> GrantAsync(UserGoodsType type, long detailId, int num, CancellationToken ct = default);
|
||||
Task<int> BackfillCardCosmeticsAsync(CancellationToken ct = default);
|
||||
|
||||
long EffectiveBalance(SpendCurrency currency);
|
||||
bool OwnsCard(long cardId);
|
||||
bool OwnsCosmetic(CosmeticType type, int id);
|
||||
|
||||
Task<InventoryCommitResult> CommitAsync(CancellationToken ct = default);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace SVSim.Database.Services.Inventory;
|
||||
|
||||
/// <summary>
|
||||
/// Thrown when an inventory operation references a catalog id that doesn't exist
|
||||
/// (unknown card / item / cosmetic). Programmer error — bubbles to the global error handler.
|
||||
/// </summary>
|
||||
public sealed class InventoryCatalogException : Exception
|
||||
{
|
||||
public InventoryCatalogException(string message) : base(message) { }
|
||||
}
|
||||
20
SVSim.Database/Services/Inventory/InventoryCommitResult.cs
Normal file
20
SVSim.Database/Services/Inventory/InventoryCommitResult.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using SVSim.Database.Services;
|
||||
|
||||
namespace SVSim.Database.Services.Inventory;
|
||||
|
||||
/// <summary>
|
||||
/// Result of <see cref="IInventoryTransaction.CommitAsync"/>.
|
||||
/// <para>
|
||||
/// <see cref="RewardList"/> — wire-shape entries with currency-collision resolved (one entry per
|
||||
/// (type, id); for currencies that were both spent and granted, the last post-state in op order
|
||||
/// wins). Use this for response <c>reward_list</c> fields.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="Deltas"/> — verbatim ordered (type, id, num) sequence the caller queued. No
|
||||
/// collapse, no cosmetic-cascade entries. Use this for BP <c>achieved_info</c> and Story
|
||||
/// <c>story_reward_list</c> popups.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed record InventoryCommitResult(
|
||||
IReadOnlyList<GrantedReward> RewardList,
|
||||
IReadOnlyList<GrantedReward> Deltas);
|
||||
31
SVSim.Database/Services/Inventory/InventoryLoadConfig.cs
Normal file
31
SVSim.Database/Services/Inventory/InventoryLoadConfig.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Query;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Database.Services.Inventory;
|
||||
|
||||
/// <summary>
|
||||
/// Caller-supplied extra <c>.Include</c> chains on top of the canonical viewer-inventory query
|
||||
/// in <see cref="IInventoryService.BeginAsync"/>. Use to bring in extra collections needed by
|
||||
/// the calling controller (e.g. <c>MissionData</c>, <c>BuildDeckPurchases</c>).
|
||||
/// </summary>
|
||||
public sealed class InventoryLoadConfig
|
||||
{
|
||||
internal List<Func<IQueryable<Viewer>, IQueryable<Viewer>>> Includes { get; } = new();
|
||||
|
||||
public InventoryLoadConfig WithInclude<TProperty>(
|
||||
Expression<Func<Viewer, TProperty>> path)
|
||||
{
|
||||
Includes.Add(q => q.Include(path));
|
||||
return this;
|
||||
}
|
||||
|
||||
public InventoryLoadConfig WithInclude<TProperty, TThen>(
|
||||
Expression<Func<Viewer, IEnumerable<TProperty>>> collectionPath,
|
||||
Expression<Func<TProperty, TThen>> thenPath)
|
||||
{
|
||||
Includes.Add(q => q.Include(collectionPath).ThenInclude(thenPath));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user