using Application.Abstractions.Cache; using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; namespace Application.Features.Api.Channel.GetCreators; public sealed class Handler( IAppDbContext db, IYouTubeChannelCache channelCache, IYouTubeLiveStateStore liveStateStore, ICacheService cache ) : IQueryHandler> { private const string CacheKey = "channel:creators:main"; private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(30); public async Task> Handle(Query request, CancellationToken ct) { var cached = await cache.GetAsync>(CacheKey, ct); if (cached is not null) { return new Response(cached); } var items = await BuildAsync(ct); await cache.SetAsync(CacheKey, items, CacheTtl, ct); return new Response(items); } private async Task> BuildAsync(CancellationToken ct) { // 활성 채널 + 회원 집계(팔로워)·등급 조인. 구독자/썸네일은 아래 YouTube 캐시에서 보강. var rows = await db.Channel.AsNoTracking().Where(c => c.IsActive).Select(c => new { c.SID, c.Name, c.Handle, FollowerCount = c.Member.MemberStats != null ? c.Member.MemberStats.FollowerCount : 0, GradeName = c.Member.MemberGrade != null ? c.Member.MemberGrade.KorName : null, GradeImage = c.Member.MemberGrade != null ? c.Member.MemberGrade.Image : null, GradeColor = c.Member.MemberGrade != null ? c.Member.MemberGrade.TextColor : null }).ToListAsync(ct); if (rows.Count == 0) { return []; } // 구독자 수·썸네일은 Redis YouTube 캐시에서 (엔티티 컬럼은 30일 미동기 시 0/null 로 비워짐) var ytCache = await channelCache.GetManyAsync(rows.Select(r => r.SID)); var allLive = await liveStateStore.GetAllLiveAsync(); var liveMap = allLive.Where(l => l.IsLive).ToDictionary(l => l.ChannelId); var items = new List(rows.Count); foreach (var r in rows) { var hasYt = ytCache.TryGetValue(r.SID, out var yt); items.Add(new CreatorItem( r.SID, r.Name, r.Handle, hasYt ? yt!.ThumbnailUrl : null, hasYt ? yt!.SubscriberCount : 0, r.FollowerCount, r.GradeName, r.GradeImage, r.GradeColor, liveMap.ContainsKey(r.SID) )); } return items; } }