using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Member.Follow.Toggle; public sealed class Handler(IAppDbContext db) : ICommandHandler> { public async Task> Handle(Command request, CancellationToken ct) { var followee = await db.Member.AsNoTracking().Where(x => x.SID == request.FolloweeSID && !x.IsWithdraw).Select(x => new { x.ID }).FirstOrDefaultAsync(ct); if (followee is null) { return Result.Failure(Error.NotFound("Follow.MemberNotFound", "회원을 찾을 수 없습니다.")); } if (followee.ID == request.FollowerMemberID) { return Result.Failure(Error.Problem("Follow.Self", "자기 자신을 팔로우할 수 없습니다.")); } var existing = await db.MemberFollow.FirstOrDefaultAsync(x => x.FollowerMemberID == request.FollowerMemberID && x.FolloweeMemberID == followee.ID, ct); var followerStats = await db.MemberStats.FirstOrDefaultAsync(x => x.MemberID == request.FollowerMemberID, ct); var followeeStats = await db.MemberStats.FirstOrDefaultAsync(x => x.MemberID == followee.ID, ct); // 기존 회원이 MemberStats 없는 경우 자가 복구 — 과거 가입 경로에서 누락된 데이터 메우기 if (followerStats is null) { followerStats = Domain.Entities.Members.MemberStats.Create(request.FollowerMemberID); await db.MemberStats.AddAsync(followerStats, ct); } if (followeeStats is null) { followeeStats = Domain.Entities.Members.MemberStats.Create(followee.ID); await db.MemberStats.AddAsync(followeeStats, ct); } bool isFollowing; if (existing is not null) { db.MemberFollow.Remove(existing); followerStats.FollowingCount = Math.Max(0, followerStats.FollowingCount - 1); followeeStats.FollowerCount = Math.Max(0, followeeStats.FollowerCount - 1); isFollowing = false; } else { var follow = Domain.Entities.Members.MemberFollow.Create(request.FollowerMemberID, followee.ID); db.MemberFollow.Add(follow); followerStats.FollowingCount += 1; followeeStats.FollowerCount += 1; isFollowing = true; } await db.SaveChangesAsync(ct); return new Response(isFollowing, followeeStats.FollowerCount); } }