using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Channel.List; public sealed class Handler(IAppDbContext db, IYouTubeLiveStateStore liveStateStore, IYouTubeChannelCache channelCache) : IQueryHandler> { public async Task> Handle(Query request, CancellationToken ct) { // 1. 활성 채널 목록 조회 var channels = await db.Channel.AsNoTracking().Where(c => c.IsActive).Select(c => new { c.SID, c.Name, c.Handle }).ToListAsync(ct); if (channels.Count == 0) { return new Response([]); } // 2. YouTube 채널 정보 캐시 일괄 조회 (Redis — API 호출 0) var channelIds = channels.Select(c => c.SID).ToList(); var ytCache = await channelCache.GetManyAsync(channelIds); // 3. 현재 라이브 중인 채널 상태 조회 (Redis — 0 unit) var allLive = await liveStateStore.GetAllLiveAsync(); var liveMap = allLive.ToDictionary(l => l.ChannelId); // 4. 채널별 정보 조합 var items = new List(); foreach (var ch in channels) { var isLive = liveMap.TryGetValue(ch.SID, out var liveInfo); var hasYtInfo = ytCache.TryGetValue(ch.SID, out var ytInfo); items.Add(new ChannelItem( ch.SID, ch.Name, ch.Handle, hasYtInfo ? ytInfo!.ThumbnailUrl : null, hasYtInfo ? ytInfo!.SubscriberCount : 0, isLive, 0, // viewerCount — 추후 사이트 자체 접속자 수로 대체 isLive ? liveInfo!.VideoId : null )); } // 5. 정렬: 온라인(시청자 많은 순) → 오프라인(이름순) items.Sort((a, b) => { if (a.IsLive && !b.IsLive) return -1; if (!a.IsLive && b.IsLive) return 1; if (a.IsLive && b.IsLive) return b.ViewerCount.CompareTo(a.ViewerCount); return string.Compare(a.Name, b.Name, StringComparison.OrdinalIgnoreCase); }); return new Response(items); } }