feat(friend): bulk apply ops + IPlayedTogetherWriter with retention cap

Implements RejectAllAppliesAsync, CancelAllAppliesAsync (ExecuteDelete bulk
deletes on incoming/outgoing applies respectively) and RecordAsync (upsert
played-together row with 50-row per-viewer retention eviction). 4 new tests
added; all 1186 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-09 22:04:04 -04:00
parent 17591a6ebd
commit b5b4781693
2 changed files with 159 additions and 6 deletions

View File

@@ -243,11 +243,19 @@ public sealed class FriendService : IFriendService, IPlayedTogetherWriter
await _db.SaveChangesAsync(ct);
}
public Task RejectAllAppliesAsync(long viewerId, CancellationToken ct) =>
throw new NotImplementedException();
public async Task RejectAllAppliesAsync(long viewerId, CancellationToken ct)
{
await _db.ViewerFriendApplies
.Where(a => a.ToViewerId == viewerId)
.ExecuteDeleteAsync(ct);
}
public Task CancelAllAppliesAsync(long viewerId, CancellationToken ct) =>
throw new NotImplementedException();
public async Task CancelAllAppliesAsync(long viewerId, CancellationToken ct)
{
await _db.ViewerFriendApplies
.Where(a => a.FromViewerId == viewerId)
.ExecuteDeleteAsync(ct);
}
public async Task RejectFriendAsync(long viewerId, int targetViewerId, CancellationToken ct)
{
@@ -261,8 +269,49 @@ public sealed class FriendService : IFriendService, IPlayedTogetherWriter
await _db.SaveChangesAsync(ct);
}
public Task RecordAsync(long ownerViewerId, long opponentViewerId, BattleParticipationContext ctx, CancellationToken ct) =>
throw new NotImplementedException();
public async Task RecordAsync(long ownerViewerId, long opponentViewerId, BattleParticipationContext ctx, CancellationToken ct)
{
if (ownerViewerId == opponentViewerId) return;
var now = DateTime.UtcNow;
var existing = await _db.ViewerPlayedTogethers
.FirstOrDefaultAsync(p => p.OwnerViewerId == ownerViewerId && p.OpponentViewerId == opponentViewerId, ct);
if (existing is null)
{
// Enforce per-viewer retention BEFORE insert: if at cap, drop the oldest first.
int currentCount = await _db.ViewerPlayedTogethers.CountAsync(p => p.OwnerViewerId == ownerViewerId, ct);
if (currentCount >= PlayedTogetherRetention)
{
var toEvict = await _db.ViewerPlayedTogethers
.Where(p => p.OwnerViewerId == ownerViewerId)
.OrderBy(p => p.PlayedAt).ThenBy(p => p.OpponentViewerId)
.FirstAsync(ct);
_db.ViewerPlayedTogethers.Remove(toEvict);
}
_db.ViewerPlayedTogethers.Add(new ViewerPlayedTogether
{
OwnerViewerId = ownerViewerId,
OpponentViewerId = opponentViewerId,
PlayedAt = now,
PlayedMode = ctx.PlayedMode,
BattleType = ctx.BattleType,
DeckFormat = ctx.DeckFormat,
TwoPickType = ctx.TwoPickType,
});
}
else
{
existing.PlayedAt = now;
existing.PlayedMode = ctx.PlayedMode;
existing.BattleType = ctx.BattleType;
existing.DeckFormat = ctx.DeckFormat;
existing.TwoPickType = ctx.TwoPickType;
}
await _db.SaveChangesAsync(ct);
}
// --- helpers ---