using Application.Abstractions.Messaging; using Application.Abstractions.Data; using Domain.Entities.Forum.ValueObject; using SharedKernel.Results; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Forum.PostReaction.Toggle; public sealed class Handler(IAppDbContext db) : ICommandHandler { public async Task Handle(Command request, CancellationToken ct) { var post = await db.Post.FirstOrDefaultAsync(x => x.ID == request.PostID, ct); if (post is null) { return Result.Failure(Error.NotFound("PostReaction.PostNotFound", "게시글을 찾을 수 없습니다.")); } var existing = await db.PostReaction.FirstOrDefaultAsync(x => x.PostID == request.PostID && x.MemberID == request.MemberID, ct); // MemberStats 조회 (Like만 카운트) — 1회 쿼리로 giver/receiver 동시 로드 var memberIDs = post.MemberID.HasValue && post.MemberID.Value != request.MemberID ? new[] { request.MemberID, post.MemberID.Value } : new[] { request.MemberID }; var statsList = await db.MemberStats .Where(x => memberIDs.Contains(x.MemberID)) .ToListAsync(ct); var giverStats = statsList.FirstOrDefault(x => x.MemberID == request.MemberID); var receiverStats = post.MemberID.HasValue && post.MemberID.Value != request.MemberID ? statsList.FirstOrDefault(x => x.MemberID == post.MemberID.Value) : null; if (existing is not null) { // 같은 반응이면 취소 if (existing.Reaction == request.Reaction) { if (existing.Reaction == Reaction.Like) { post.Likes--; if (giverStats is not null) { giverStats.LikeGivenCount--; } if (receiverStats is not null) { receiverStats.LikeReceivedCount--; } } else { post.Dislikes--; } db.PostReaction.Remove(existing); } else { // 다른 반응이면 전환 if (existing.Reaction == Reaction.Like) { post.Likes--; post.Dislikes++; // Like → Dislike: Like 카운트 감소 if (giverStats is not null) { giverStats.LikeGivenCount--; } if (receiverStats is not null) { receiverStats.LikeReceivedCount--; } } else { post.Dislikes--; post.Likes++; // Dislike → Like: Like 카운트 증가 if (giverStats is not null) { giverStats.LikeGivenCount++; } if (receiverStats is not null) { receiverStats.LikeReceivedCount++; } } existing.Reaction = request.Reaction; existing.UpdatedAt = DateTime.UtcNow; } } else { // 새 반응 var reaction = new Domain.Entities.Forum.Posts.PostReaction { BoardID = post.BoardID, PostID = request.PostID, MemberID = request.MemberID, Reaction = request.Reaction }; await db.PostReaction.AddAsync(reaction, ct); if (request.Reaction == Reaction.Like) { post.Likes++; if (giverStats is not null) { giverStats.LikeGivenCount++; } if (receiverStats is not null) { receiverStats.LikeReceivedCount++; } } else { post.Dislikes++; } } await db.SaveChangesAsync(ct); return Result.Success(); } }