| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Application.Abstractions.YouTube;
- using Domain.Entities.Donations.ValueObject;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Crew.SearchMember;
- internal sealed class Handler(
- IAppDbContext db,
- IYouTubeChannelCache channelCache
- ) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query request, CancellationToken ct)
- {
- if (string.IsNullOrWhiteSpace(request.Keyword) || request.Keyword.Trim().Length < 2)
- {
- return new Response([]);
- }
- var kw = request.Keyword.Trim();
- // 이미 등록된 크루원의 MemberID 목록
- var existingMemberIDs = await db.CrewMember.AsNoTracking()
- .Where(m => m.CrewID == request.CrewID)
- .Select(m => m.MemberID)
- .ToListAsync(ct);
- // 진행 중인 초대의 TargetMemberID 목록 (중복 초대 방지)
- var pendingInviteMemberIDs = await db.CrewInvitation.AsNoTracking()
- .Where(i => i.CrewID == request.CrewID && i.Status == CrewInviteStatus.Pending)
- .Select(i => i.TargetMemberID)
- .ToListAsync(ct);
- var excludeMemberIDs = existingMemberIDs.Concat(pendingInviteMemberIDs).Distinct().ToList();
- var list = await db.Member.AsNoTracking()
- .Where(m => !m.IsWithdraw && !m.IsDenied)
- .Where(m => !excludeMemberIDs.Contains(m.ID))
- .Where(m => db.Channel.Any(c => c.MemberID == m.ID))
- .Where(m =>
- m.Email.Contains(kw) ||
- db.Channel.Any(c => c.MemberID == m.ID && (
- c.Name.Contains(kw) ||
- (c.Handle != null && c.Handle.Contains(kw))
- ))
- )
- .Take(20)
- .Select(m => new
- {
- m.ID,
- m.Name,
- m.Email,
- m.Thumb,
- Channel = db.Channel.AsNoTracking()
- .Where(c => c.MemberID == m.ID)
- .Select(c => new { c.SID, c.Name, c.Handle, c.ThumbnailUrl })
- .FirstOrDefault()
- })
- .ToListAsync(ct);
- // YouTube 채널 정보 캐시 일괄 조회 (Redis 우선, 미스 시 DB fallback)
- var channelSIDs = list.Where(x => x.Channel != null).Select(x => x.Channel!.SID).ToList();
- var ytCache = channelSIDs.Count > 0
- ? await channelCache.GetManyAsync(channelSIDs)
- : [];
- var result = list.Select(x =>
- {
- string? channelThumb = null;
- if (x.Channel != null)
- {
- channelThumb = ytCache.TryGetValue(x.Channel.SID, out var ytInfo)
- ? ytInfo.ThumbnailUrl
- : x.Channel.ThumbnailUrl;
- }
- return new SearchResultItem(
- x.ID,
- x.Name,
- x.Email,
- x.Thumb,
- x.Channel?.Name,
- channelThumb,
- x.Channel?.Handle
- );
- }).ToList();
- return new Response(result);
- }
- }
|