Handler.cs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. using Application.Abstractions.Messaging;
  2. using Application.Abstractions.Data;
  3. using Domain.Entities.Forum.ValueObject;
  4. using SharedKernel.Results;
  5. using Microsoft.EntityFrameworkCore;
  6. namespace Application.Features.Api.Forum.PostReaction.Toggle;
  7. public sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result>
  8. {
  9. public async Task<Result> Handle(Command request, CancellationToken ct)
  10. {
  11. var post = await db.Post.FirstOrDefaultAsync(x => x.ID == request.PostID, ct);
  12. if (post is null)
  13. {
  14. return Result.Failure(Error.NotFound("PostReaction.PostNotFound", "게시글을 찾을 수 없습니다."));
  15. }
  16. var existing = await db.PostReaction.FirstOrDefaultAsync(x => x.PostID == request.PostID && x.MemberID == request.MemberID, ct);
  17. // MemberStats 조회 (Like만 카운트) — 1회 쿼리로 giver/receiver 동시 로드
  18. var memberIDs = post.MemberID.HasValue && post.MemberID.Value != request.MemberID
  19. ? new[] { request.MemberID, post.MemberID.Value }
  20. : new[] { request.MemberID };
  21. var statsList = await db.MemberStats
  22. .Where(x => memberIDs.Contains(x.MemberID))
  23. .ToListAsync(ct);
  24. var giverStats = statsList.FirstOrDefault(x => x.MemberID == request.MemberID);
  25. var receiverStats = post.MemberID.HasValue && post.MemberID.Value != request.MemberID
  26. ? statsList.FirstOrDefault(x => x.MemberID == post.MemberID.Value)
  27. : null;
  28. if (existing is not null)
  29. {
  30. // 같은 반응이면 취소
  31. if (existing.Reaction == request.Reaction)
  32. {
  33. if (existing.Reaction == Reaction.Like)
  34. {
  35. post.Likes--;
  36. if (giverStats is not null) { giverStats.LikeGivenCount--; }
  37. if (receiverStats is not null) { receiverStats.LikeReceivedCount--; }
  38. }
  39. else
  40. {
  41. post.Dislikes--;
  42. }
  43. db.PostReaction.Remove(existing);
  44. }
  45. else
  46. {
  47. // 다른 반응이면 전환
  48. if (existing.Reaction == Reaction.Like)
  49. {
  50. post.Likes--;
  51. post.Dislikes++;
  52. // Like → Dislike: Like 카운트 감소
  53. if (giverStats is not null) { giverStats.LikeGivenCount--; }
  54. if (receiverStats is not null) { receiverStats.LikeReceivedCount--; }
  55. }
  56. else
  57. {
  58. post.Dislikes--;
  59. post.Likes++;
  60. // Dislike → Like: Like 카운트 증가
  61. if (giverStats is not null) { giverStats.LikeGivenCount++; }
  62. if (receiverStats is not null) { receiverStats.LikeReceivedCount++; }
  63. }
  64. existing.Reaction = request.Reaction;
  65. existing.UpdatedAt = DateTime.UtcNow;
  66. }
  67. }
  68. else
  69. {
  70. // 새 반응
  71. var reaction = new Domain.Entities.Forum.Posts.PostReaction
  72. {
  73. BoardID = post.BoardID,
  74. PostID = request.PostID,
  75. MemberID = request.MemberID,
  76. Reaction = request.Reaction
  77. };
  78. await db.PostReaction.AddAsync(reaction, ct);
  79. if (request.Reaction == Reaction.Like)
  80. {
  81. post.Likes++;
  82. if (giverStats is not null) { giverStats.LikeGivenCount++; }
  83. if (receiverStats is not null) { receiverStats.LikeReceivedCount++; }
  84. }
  85. else
  86. {
  87. post.Dislikes++;
  88. }
  89. }
  90. await db.SaveChangesAsync(ct);
  91. return Result.Success();
  92. }
  93. }