using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.Notification; using Domain.Entities.Notifications.ValueObject; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Note.GetDetail; internal sealed class Handler( IAppDbContext db, INotificationService notification ) : IQueryHandler> { public async Task> Handle(Query r, CancellationToken ct) { // 받은쪽지(receiver 본인) 또는 보낸쪽지(sender 본인) 둘 다 조회 허용 var note = await db.Note .Where(n => n.ID == r.NoteID && ( (n.ReceiverMemberID == r.CurrentMemberID && !n.IsDeletedByReceiver) || (n.SenderMemberID == r.CurrentMemberID && !n.IsDeletedBySender) )) .FirstOrDefaultAsync(ct); if (note is null) { return Result.Failure(Error.NotFound("Note.NotFound", "쪽지를 찾을 수 없습니다.")); } var isReceiverView = note.ReceiverMemberID == r.CurrentMemberID; var wasUnread = !note.IsRead; // receiver 본인이 처음 조회한 경우만 읽음 처리 if (isReceiverView && wasUnread) { note.MarkRead(); await db.SaveChangesAsync(ct); } // 발신자 정보 + 채널 (시스템 쪽지면 sender=0 → null) var sender = note.SenderMemberID > 0 ? await db.Member.AsNoTracking() .Where(m => m.ID == note.SenderMemberID) .Select(m => new { m.Name, m.Thumb, m.SID, ChannelName = m.Channel != null ? m.Channel.Name : null, ChannelHandle = m.Channel != null ? m.Channel.Handle : null }) .FirstOrDefaultAsync(ct) : null; // 수신자 정보 + 채널 var receiver = await db.Member.AsNoTracking() .Where(m => m.ID == note.ReceiverMemberID) .Select(m => new { m.Name, m.Thumb, m.SID, ChannelName = m.Channel != null ? m.Channel.Name : null, ChannelHandle = m.Channel != null ? m.Channel.Handle : null }) .FirstOrDefaultAsync(ct); // 처음 읽었고 일반 쪽지면 발신자에게 NoteRead 알림 (receiver 본인 조회 시에만) if (isReceiverView && wasUnread && !note.IsSystem && note.SenderMemberID > 0) { // 채널명 > 회원 별명, 보조 @handle / #sid var primary = receiver?.ChannelName ?? receiver?.Name ?? "알 수 없는 회원"; var secondary = !string.IsNullOrEmpty(receiver?.ChannelHandle) ? $"@{receiver.ChannelHandle}" : (!string.IsNullOrEmpty(receiver?.SID) ? $"#{receiver.SID}" : null); var receiverDisplay = secondary is null ? primary : $"{primary} ({secondary})"; await notification.SendAsync( note.SenderMemberID, NotificationType.NoteRead, "쪽지를 읽었습니다", $"{receiverDisplay}님이 \"{note.Title}\" 쪽지를 읽었습니다.", actionUrl: null, relatedType: "Note", relatedID: note.ID, ct: ct ); } return Result.Success(new Response( note.ID, note.IsSystem ? null : note.SenderMemberID, note.IsSystem ? "시스템" : sender?.Name, note.IsSystem ? null : sender?.Thumb, note.IsSystem ? null : sender?.SID, note.IsSystem ? null : sender?.ChannelName, note.IsSystem ? null : sender?.ChannelHandle, note.ReceiverMemberID, receiver?.Name, receiver?.Thumb, receiver?.SID, receiver?.ChannelName, receiver?.ChannelHandle, note.Title, note.Content, note.IsSystem, note.IsRead, note.CreatedAt )); } }