feat(replay): add ReplayHistoryReader for newest-first list query
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
7
SVSim.Database/Services/Replay/IReplayHistoryReader.cs
Normal file
7
SVSim.Database/Services/Replay/IReplayHistoryReader.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace SVSim.Database.Services.Replay;
|
||||
|
||||
public interface IReplayHistoryReader
|
||||
{
|
||||
/// <summary>Newest-first by CreateTime. Caps at <paramref name="take"/> (default 50).</summary>
|
||||
Task<IReadOnlyList<ReplayHistoryEntry>> GetRecentAsync(long viewerId, int take, CancellationToken ct);
|
||||
}
|
||||
27
SVSim.Database/Services/Replay/ReplayHistoryEntry.cs
Normal file
27
SVSim.Database/Services/Replay/ReplayHistoryEntry.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace SVSim.Database.Services.Replay;
|
||||
|
||||
/// <summary>
|
||||
/// Read-side row returned by <see cref="IReplayHistoryReader"/>. The /replay/info
|
||||
/// controller maps this to its wire DTO (all-stringified per prod capture).
|
||||
/// </summary>
|
||||
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);
|
||||
27
SVSim.Database/Services/Replay/ReplayHistoryReader.cs
Normal file
27
SVSim.Database/Services/Replay/ReplayHistoryReader.cs
Normal file
@@ -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<IReadOnlyList<ReplayHistoryEntry>> 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);
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,7 @@ public class Program
|
||||
|
||||
builder.Services.AddSingleton<IBattleContextStore, InMemoryBattleContextStore>();
|
||||
builder.Services.AddScoped<IBattleHistoryWriter, BattleHistoryWriter>();
|
||||
builder.Services.AddScoped<IReplayHistoryReader, ReplayHistoryReader>();
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user