using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel; using SharedKernel.Results; namespace Application.Features.Api.Crew.GetInvite; internal sealed class Handler( IAppDbContext db ) : IQueryHandler> { public async Task> Handle(Query request, CancellationToken ct) { var invitation = await db.CrewInvitation.AsNoTracking().FirstOrDefaultAsync(i => i.ID == request.InvitationID, ct); if (invitation is null) { return Result.Failure(Error.NotFound("CrewInvitation.NotFound", "초대를 찾을 수 없습니다.")); } if (invitation.TargetMemberID != request.RequesterMemberID) { return Result.Failure(Error.Forbidden("CrewInvitation.NotForYou", "본인에게 온 초대만 조회할 수 있습니다.")); } var crew = await db.Crew.AsNoTracking().FirstOrDefaultAsync(c => c.ID == invitation.CrewID, ct); if (crew is null) { return Result.Failure(Error.NotFound("Crew.NotFound", "크루를 찾을 수 없습니다.")); } // 초대자 채널명 — Channel 우선, 없으면 Member.Name fallback var inviterChannelName = await db.Channel.AsNoTracking() .Where(c => c.MemberID == invitation.InvitedByMemberID) .Select(c => c.Name) .FirstOrDefaultAsync(ct); if (string.IsNullOrWhiteSpace(inviterChannelName)) { inviterChannelName = await db.Member.AsNoTracking() .Where(m => m.ID == invitation.InvitedByMemberID) .Select(m => m.Name) .FirstOrDefaultAsync(ct) ?? "(알 수 없음)"; } var isExpired = invitation.IsExpired(); return Result.Success(new Response( invitation.ID, invitation.CrewID, crew.Name, inviterChannelName, invitation.Nickname, (byte)invitation.Status, invitation.InvitedAt, invitation.ExpiresAt, isExpired )); } }