| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Microsoft.EntityFrameworkCore;
- namespace Application.Features.Api.Note.SearchRecipient;
- /// <summary>
- /// 쪽지 수신자 검색 — 보안 정책상 본인이 팔로우한 회원/채널만 검색됨 (enumeration 차단).
- /// 직접 진입 (프로필/채널 페이지 "쪽지 보내기" 버튼)은 별도 — 검색은 빠른 발송용.
- /// </summary>
- internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
- {
- public async Task<Response> Handle(Query r, CancellationToken ct)
- {
- var kw = r.Keyword?.Trim() ?? string.Empty;
- if (kw.Length < 2)
- {
- return new Response([], false);
- }
- var offset = r.Offset < 0 ? 0 : r.Offset;
- var limit = r.Limit is < 1 or > 50 ? 20 : r.Limit;
- // 본인이 팔로우 중인 회원 IDs (보안 필터)
- var followingIDs = db.MemberFollow.AsNoTracking()
- .Where(f => f.FollowerMemberID == r.CurrentMemberID)
- .Select(f => f.FolloweeMemberID);
- // Member LEFT JOIN Channel — 닉네임/SID/채널명/핸들 OR 검색
- var rows = await (
- from m in db.Member.AsNoTracking()
- where !m.IsWithdraw && !m.IsDenied
- && m.ID != r.CurrentMemberID
- && followingIDs.Contains(m.ID)
- from c in db.Channel.AsNoTracking().Where(c => c.MemberID == m.ID).DefaultIfEmpty()
- where (m.Name != null && m.Name.Contains(kw))
- || m.SID.Contains(kw)
- || (c != null && c.Name.Contains(kw))
- || (c != null && c.Handle != null && c.Handle.Contains(kw))
- orderby m.ID descending
- select new
- {
- m.ID,
- MemberName = m.Name,
- m.SID,
- MemberThumb = m.Thumb,
- ChannelName = c != null ? c.Name : null,
- ChannelHandle = c != null ? c.Handle : null,
- ChannelThumbnail = c != null ? c.ThumbnailUrl : null
- }
- )
- .Skip(offset)
- .Take(limit + 1)
- .ToListAsync(ct);
- var hasMore = rows.Count > limit;
- var page = rows.Take(limit).ToList();
- // 채널 보유 여부와 무관하게 일관된 UI 데이터 — (썸네일, 이름, 부제)
- var list = page.Select(x => new RecipientItem(
- x.ID,
- x.ChannelName ?? x.MemberName ?? x.SID,
- x.ChannelHandle != null ? "@" + x.ChannelHandle : "@" + x.SID,
- x.ChannelThumbnail ?? x.MemberThumb
- )).ToList();
- return new Response(list, hasMore);
- }
- }
|