From 86d86f6ead0a1406da3f63f03590bfaac2e65080 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 10 Jun 2026 07:48:18 -0400 Subject: [PATCH] feat(replay): add ReplayHistoryReader for newest-first list query Co-Authored-By: Claude Opus 4.7 --- .../Services/Replay/IReplayHistoryReader.cs | 7 +++++ .../Services/Replay/ReplayHistoryEntry.cs | 27 +++++++++++++++++++ .../Services/Replay/ReplayHistoryReader.cs | 27 +++++++++++++++++++ SVSim.EmulatedEntrypoint/Program.cs | 1 + 4 files changed, 62 insertions(+) create mode 100644 SVSim.Database/Services/Replay/IReplayHistoryReader.cs create mode 100644 SVSim.Database/Services/Replay/ReplayHistoryEntry.cs create mode 100644 SVSim.Database/Services/Replay/ReplayHistoryReader.cs diff --git a/SVSim.Database/Services/Replay/IReplayHistoryReader.cs b/SVSim.Database/Services/Replay/IReplayHistoryReader.cs new file mode 100644 index 0000000..bee5f59 --- /dev/null +++ b/SVSim.Database/Services/Replay/IReplayHistoryReader.cs @@ -0,0 +1,7 @@ +namespace SVSim.Database.Services.Replay; + +public interface IReplayHistoryReader +{ + /// Newest-first by CreateTime. Caps at (default 50). + Task> GetRecentAsync(long viewerId, int take, CancellationToken ct); +} diff --git a/SVSim.Database/Services/Replay/ReplayHistoryEntry.cs b/SVSim.Database/Services/Replay/ReplayHistoryEntry.cs new file mode 100644 index 0000000..2143df3 --- /dev/null +++ b/SVSim.Database/Services/Replay/ReplayHistoryEntry.cs @@ -0,0 +1,27 @@ +namespace SVSim.Database.Services.Replay; + +/// +/// Read-side row returned by . The /replay/info +/// controller maps this to its wire DTO (all-stringified per prod capture). +/// +public sealed record ReplayHistoryEntry( + long BattleId, + int BattleType, + int DeckFormat, + int TwoPickType, + int IsLimitTurn, + int SelfClassId, + int SelfSubClassId, + int SelfCharaId, + string SelfRotationId, + int OpponentClassId, + int OpponentSubClassId, + int OpponentCharaId, + string OpponentName, + string OpponentCountryCode, + long OpponentEmblemId, + long OpponentDegreeId, + string OpponentRotationId, + bool IsWin, + DateTime BattleStartTime, + DateTime CreateTime); diff --git a/SVSim.Database/Services/Replay/ReplayHistoryReader.cs b/SVSim.Database/Services/Replay/ReplayHistoryReader.cs new file mode 100644 index 0000000..7c042d2 --- /dev/null +++ b/SVSim.Database/Services/Replay/ReplayHistoryReader.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; + +namespace SVSim.Database.Services.Replay; + +public sealed class ReplayHistoryReader : IReplayHistoryReader +{ + private readonly SVSimDbContext _db; + + public ReplayHistoryReader(SVSimDbContext db) => _db = db; + + public async Task> GetRecentAsync(long viewerId, int take, CancellationToken ct) + { + return await _db.ViewerBattleHistories + .AsNoTracking() + .Where(h => h.ViewerId == viewerId) + .OrderByDescending(h => h.CreateTime) + .Take(take) + .Select(h => new ReplayHistoryEntry( + h.BattleId, h.BattleType, h.DeckFormat, h.TwoPickType, h.IsLimitTurn, + h.SelfClassId, h.SelfSubClassId, h.SelfCharaId, h.SelfRotationId, + h.OpponentClassId, h.OpponentSubClassId, h.OpponentCharaId, + h.OpponentName, h.OpponentCountryCode, + h.OpponentEmblemId, h.OpponentDegreeId, h.OpponentRotationId, + h.IsWin, h.BattleStartTime, h.CreateTime)) + .ToListAsync(ct); + } +} diff --git a/SVSim.EmulatedEntrypoint/Program.cs b/SVSim.EmulatedEntrypoint/Program.cs index d8de41b..ade7ac4 100644 --- a/SVSim.EmulatedEntrypoint/Program.cs +++ b/SVSim.EmulatedEntrypoint/Program.cs @@ -119,6 +119,7 @@ public class Program builder.Services.AddSingleton(); builder.Services.AddScoped(); + builder.Services.AddScoped(); // Deck-code mint/resolve uses IMemoryCache for ephemeral (3-min TTL) storage; no DB // row, no migration. Singleton because the cache + RNG seam are process-wide.