feat(repo): GetStoryDecksByClass joins story-deck presentation to product card lists

Adds StoryDeckView projection, IBuildDeckRepository.GetStoryDecksByClass interface method,
and BuildDeckRepository implementation that loads StoryDeckEntry rows for a class, fetches
matching BuildDeckProductEntry card lists, and expands each card by Number into a flat
CardIdArray. TDD: 2 tests in StoryDeckRepositoryTests (expand + empty-class).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-05-29 10:36:14 -04:00
parent e792e8d79d
commit 68d783192d
4 changed files with 129 additions and 0 deletions

View File

@@ -64,4 +64,38 @@ public class BuildDeckRepository : IBuildDeckRepository
await _db.SaveChangesAsync();
return row.PurchaseCount;
}
public async Task<List<StoryDeckView>> GetStoryDecksByClass(int classId)
{
var decks = await _db.StoryDecks.Where(d => d.ClassId == classId).ToListAsync();
if (decks.Count == 0) return new();
var ids = decks.Select(d => d.DeckNo).ToList();
var products = await _db.BuildDeckProducts
.Where(p => ids.Contains(p.Id))
.Include(p => p.Cards)
.AsSplitQuery()
.ToListAsync();
// Expand each product's owned card rows by Number into a flat card_id list (spots included —
// validated against the prod capture, 112/112 match).
var cardsById = products.ToDictionary(
p => p.Id,
p => p.Cards.SelectMany(c => Enumerable.Repeat(c.CardId, c.Number)).ToList());
return decks.Select(d => new StoryDeckView
{
DeckNo = d.DeckNo,
Kind = d.Kind,
ClassId = d.ClassId,
DeckName = d.DeckName,
SleeveId = d.SleeveId,
LeaderSkinId = d.LeaderSkinId,
IsRecommend = d.IsRecommend,
OrderNum = d.OrderNum,
EntryNo = d.EntryNo,
DeckFormat = d.DeckFormat,
CardIdArray = cardsById.TryGetValue(d.DeckNo, out var cards) ? cards : new(),
}).ToList();
}
}

View File

@@ -26,4 +26,11 @@ public interface IBuildDeckRepository
/// Returns the new total.
/// </summary>
Task<int> IncrementPurchaseCount(long viewerId, int productId);
/// <summary>
/// Story deck-select decks for a class: StoryDeckEntry presentation rows joined to the matching
/// BuildDeckProductEntry card lists (deck_no == product_id), expanded to a flat card_id array.
/// Returns build and trial decks together; the caller splits by Kind.
/// </summary>
Task<List<StoryDeckView>> GetStoryDecksByClass(int classId);
}

View File

@@ -0,0 +1,22 @@
using SVSim.Database.Enums;
namespace SVSim.Database.Repositories.BuildDeck;
/// <summary>
/// A story-select deck ready for the wire: presentation metadata from StoryDeckEntry plus the
/// 40-card list expanded from the matching BuildDeckProductEntry. Plain projection, not an entity.
/// </summary>
public sealed class StoryDeckView
{
public int DeckNo { get; init; }
public StoryDeckKind Kind { get; init; }
public int ClassId { get; init; }
public string DeckName { get; init; } = string.Empty;
public int SleeveId { get; init; }
public int LeaderSkinId { get; init; }
public int IsRecommend { get; init; }
public int OrderNum { get; init; }
public int EntryNo { get; init; }
public int? DeckFormat { get; init; }
public List<long> CardIdArray { get; init; } = new();
}