Handler.cs 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.Notification;
  4. using Domain.Entities.Notifications.ValueObject;
  5. using Microsoft.EntityFrameworkCore;
  6. using SharedKernel.Results;
  7. namespace Application.Features.Api.Note.GetDetail;
  8. internal sealed class Handler(
  9. IAppDbContext db,
  10. INotificationService notification
  11. ) : IQueryHandler<Query, Result<Response>>
  12. {
  13. public async Task<Result<Response>> Handle(Query r, CancellationToken ct)
  14. {
  15. // 받은쪽지(receiver 본인) 또는 보낸쪽지(sender 본인) 둘 다 조회 허용
  16. var note = await db.Note
  17. .Where(n => n.ID == r.NoteID && (
  18. (n.ReceiverMemberID == r.CurrentMemberID && !n.IsDeletedByReceiver) ||
  19. (n.SenderMemberID == r.CurrentMemberID && !n.IsDeletedBySender)
  20. ))
  21. .FirstOrDefaultAsync(ct);
  22. if (note is null)
  23. {
  24. return Result.Failure<Response>(Error.NotFound("Note.NotFound", "쪽지를 찾을 수 없습니다."));
  25. }
  26. var isReceiverView = note.ReceiverMemberID == r.CurrentMemberID;
  27. var wasUnread = !note.IsRead;
  28. // receiver 본인이 처음 조회한 경우만 읽음 처리
  29. if (isReceiverView && wasUnread)
  30. {
  31. note.MarkRead();
  32. await db.SaveChangesAsync(ct);
  33. }
  34. // 발신자 정보 + 채널 (시스템 쪽지면 sender=0 → null)
  35. var sender = note.SenderMemberID > 0
  36. ? await db.Member.AsNoTracking()
  37. .Where(m => m.ID == note.SenderMemberID)
  38. .Select(m => new {
  39. m.Name, m.Thumb, m.SID,
  40. ChannelName = m.Channel != null ? m.Channel.Name : null,
  41. ChannelHandle = m.Channel != null ? m.Channel.Handle : null
  42. })
  43. .FirstOrDefaultAsync(ct)
  44. : null;
  45. // 수신자 정보 + 채널
  46. var receiver = await db.Member.AsNoTracking()
  47. .Where(m => m.ID == note.ReceiverMemberID)
  48. .Select(m => new {
  49. m.Name, m.Thumb, m.SID,
  50. ChannelName = m.Channel != null ? m.Channel.Name : null,
  51. ChannelHandle = m.Channel != null ? m.Channel.Handle : null
  52. })
  53. .FirstOrDefaultAsync(ct);
  54. // 처음 읽었고 일반 쪽지면 발신자에게 NoteRead 알림 (receiver 본인 조회 시에만)
  55. if (isReceiverView && wasUnread && !note.IsSystem && note.SenderMemberID > 0)
  56. {
  57. // 채널명 > 회원 별명, 보조 @handle / #sid
  58. var primary = receiver?.ChannelName ?? receiver?.Name ?? "알 수 없는 회원";
  59. var secondary = !string.IsNullOrEmpty(receiver?.ChannelHandle)
  60. ? $"@{receiver.ChannelHandle}"
  61. : (!string.IsNullOrEmpty(receiver?.SID) ? $"#{receiver.SID}" : null);
  62. var receiverDisplay = secondary is null ? primary : $"{primary} ({secondary})";
  63. await notification.SendAsync(
  64. note.SenderMemberID,
  65. NotificationType.NoteRead,
  66. "쪽지를 읽었습니다",
  67. $"{receiverDisplay}님이 \"{note.Title}\" 쪽지를 읽었습니다.",
  68. actionUrl: null,
  69. relatedType: "Note",
  70. relatedID: note.ID,
  71. ct: ct
  72. );
  73. }
  74. return Result.Success(new Response(
  75. note.ID,
  76. note.IsSystem ? null : note.SenderMemberID,
  77. note.IsSystem ? "시스템" : sender?.Name,
  78. note.IsSystem ? null : sender?.Thumb,
  79. note.IsSystem ? null : sender?.SID,
  80. note.IsSystem ? null : sender?.ChannelName,
  81. note.IsSystem ? null : sender?.ChannelHandle,
  82. note.ReceiverMemberID,
  83. receiver?.Name,
  84. receiver?.Thumb,
  85. receiver?.SID,
  86. receiver?.ChannelName,
  87. receiver?.ChannelHandle,
  88. note.Title,
  89. note.Content,
  90. note.IsSystem,
  91. note.IsRead,
  92. note.CreatedAt
  93. ));
  94. }
  95. }