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:
@@ -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 ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user