feat(friend): implement single-target write methods on FriendService
SendApplyAsync, ApproveApplyAsync, RejectApplyAsync, CancelApplyAsync, RejectFriendAsync all implemented. 11 new tests appended; all 23 friend tests pass, full suite 1182/1182 green.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using SVSim.Database.Models;
|
||||
|
||||
namespace SVSim.Database.Services.Friend;
|
||||
|
||||
@@ -142,17 +143,105 @@ public sealed class FriendService : IFriendService, IPlayedTogetherWriter
|
||||
return await BuildFriendEntryAsync(targetViewerId, ct);
|
||||
}
|
||||
|
||||
public Task SendApplyAsync(long viewerId, int targetViewerId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
public async Task SendApplyAsync(long viewerId, int targetViewerId, CancellationToken ct)
|
||||
{
|
||||
if (targetViewerId == (int)viewerId)
|
||||
{
|
||||
_log.LogDebug("SendApply self-target ignored for viewer {ViewerId}", viewerId);
|
||||
return;
|
||||
}
|
||||
|
||||
public Task ApproveApplyAsync(long viewerId, int applyId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
bool targetExists = await _db.Viewers.AsNoTracking().AnyAsync(v => v.Id == targetViewerId, ct);
|
||||
if (!targetExists)
|
||||
{
|
||||
_log.LogDebug("SendApply target {Target} not found", targetViewerId);
|
||||
return;
|
||||
}
|
||||
|
||||
public Task RejectApplyAsync(long viewerId, int applyId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
bool alreadyFriends = await _db.ViewerFriends.AsNoTracking()
|
||||
.AnyAsync(f => f.OwnerViewerId == viewerId && f.FriendViewerId == targetViewerId, ct);
|
||||
if (alreadyFriends)
|
||||
{
|
||||
_log.LogDebug("SendApply ignored — viewer {ViewerId} already friends with {Target}", viewerId, targetViewerId);
|
||||
return;
|
||||
}
|
||||
|
||||
public Task CancelApplyAsync(long viewerId, int applyId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
bool alreadyPending = await _db.ViewerFriendApplies.AsNoTracking()
|
||||
.AnyAsync(a => a.FromViewerId == viewerId && a.ToViewerId == targetViewerId, ct);
|
||||
if (alreadyPending) return;
|
||||
|
||||
int outgoingCount = await _db.ViewerFriendApplies.CountAsync(a => a.FromViewerId == viewerId, ct);
|
||||
if (outgoingCount >= SendApplyMaxCount)
|
||||
{
|
||||
_log.LogInformation("SendApply hit cap of {Cap} for viewer {ViewerId}", SendApplyMaxCount, viewerId);
|
||||
return;
|
||||
}
|
||||
|
||||
_db.ViewerFriendApplies.Add(new ViewerFriendApply
|
||||
{
|
||||
FromViewerId = viewerId,
|
||||
ToViewerId = targetViewerId,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
MissionType = 0,
|
||||
});
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task ApproveApplyAsync(long viewerId, int applyId, CancellationToken ct)
|
||||
{
|
||||
var apply = await _db.ViewerFriendApplies
|
||||
.FirstOrDefaultAsync(a => a.Id == applyId && a.ToViewerId == viewerId, ct);
|
||||
if (apply is null)
|
||||
{
|
||||
_log.LogDebug("ApproveApply {ApplyId} not addressed to viewer {ViewerId}", applyId, viewerId);
|
||||
return;
|
||||
}
|
||||
|
||||
long otherViewer = apply.FromViewerId;
|
||||
|
||||
int myFriendCount = await _db.ViewerFriends.CountAsync(f => f.OwnerViewerId == viewerId, ct);
|
||||
int otherFriendCount = await _db.ViewerFriends.CountAsync(f => f.OwnerViewerId == otherViewer, ct);
|
||||
if (myFriendCount >= FriendMaxCount || otherFriendCount >= FriendMaxCount)
|
||||
{
|
||||
_log.LogInformation("ApproveApply hit friend cap (me={Me}, other={Other})", myFriendCount, otherFriendCount);
|
||||
return;
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
await using var tx = await _db.Database.BeginTransactionAsync(ct);
|
||||
|
||||
_db.ViewerFriendApplies.Remove(apply);
|
||||
|
||||
// Clean reverse-direction apply if it exists.
|
||||
var reverse = await _db.ViewerFriendApplies
|
||||
.FirstOrDefaultAsync(a => a.FromViewerId == viewerId && a.ToViewerId == otherViewer, ct);
|
||||
if (reverse is not null) _db.ViewerFriendApplies.Remove(reverse);
|
||||
|
||||
_db.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = viewerId, FriendViewerId = otherViewer, CreatedAt = now });
|
||||
_db.ViewerFriends.Add(new ViewerFriend { OwnerViewerId = otherViewer, FriendViewerId = viewerId, CreatedAt = now });
|
||||
|
||||
await _db.SaveChangesAsync(ct);
|
||||
await tx.CommitAsync(ct);
|
||||
}
|
||||
|
||||
public async Task RejectApplyAsync(long viewerId, int applyId, CancellationToken ct)
|
||||
{
|
||||
var apply = await _db.ViewerFriendApplies
|
||||
.FirstOrDefaultAsync(a => a.Id == applyId && a.ToViewerId == viewerId, ct);
|
||||
if (apply is null) return;
|
||||
_db.ViewerFriendApplies.Remove(apply);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public async Task CancelApplyAsync(long viewerId, int applyId, CancellationToken ct)
|
||||
{
|
||||
var apply = await _db.ViewerFriendApplies
|
||||
.FirstOrDefaultAsync(a => a.Id == applyId && a.FromViewerId == viewerId, ct);
|
||||
if (apply is null) return;
|
||||
_db.ViewerFriendApplies.Remove(apply);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public Task RejectAllAppliesAsync(long viewerId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
@@ -160,8 +249,17 @@ public sealed class FriendService : IFriendService, IPlayedTogetherWriter
|
||||
public Task CancelAllAppliesAsync(long viewerId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
public Task RejectFriendAsync(long viewerId, int targetViewerId, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
public async Task RejectFriendAsync(long viewerId, int targetViewerId, CancellationToken ct)
|
||||
{
|
||||
var rows = await _db.ViewerFriends
|
||||
.Where(f =>
|
||||
(f.OwnerViewerId == viewerId && f.FriendViewerId == targetViewerId) ||
|
||||
(f.OwnerViewerId == targetViewerId && f.FriendViewerId == viewerId))
|
||||
.ToListAsync(ct);
|
||||
if (rows.Count == 0) return;
|
||||
_db.ViewerFriends.RemoveRange(rows);
|
||||
await _db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
public Task RecordAsync(long ownerViewerId, long opponentViewerId, BattleParticipationContext ctx, CancellationToken ct) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
Reference in New Issue
Block a user