using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using SharedKernel; namespace Application.Features.Admin.Channel.YouTubePubSub.GetHealth; internal sealed class Handler( IAppDbContext db, IYouTubePubSubService pubSubService, IYouTubeLiveChatService liveChatService, IYouTubeLiveStateStore liveStateStore, IOptions settings ) : IQueryHandler { public async Task Handle(Query request, CancellationToken ct) { var cfg = await db.Config.AsNoTracking().FirstOrDefaultAsync(ct); var apiKey = cfg?.External?.YouTubeApiKeyEnc; var hmac = settings.Value.YouTube.HmacSecret; var subscribedIds = (await pubSubService.GetSubscribedChannelIdsAsync(ct)).ToHashSet(); var lastPolled = await pubSubService.GetAllLastPolledAtAsync(ct); var leaseExpiries = await pubSubService.GetAllLeaseExpiriesAsync(ct); var lastProcessed = await pubSubService.GetAllLastProcessedVideoIdsAsync(ct); var liveChannels = await liveStateStore.GetAllLiveAsync(); var channels = await db.Channel.AsNoTracking() .Where(c => c.YouTubeChannelID != null && c.YouTubeChannelID != "") .OrderByDescending(c => c.IsActive) .ThenBy(c => c.Name) .Select(c => new { c.YouTubeChannelID, c.Name, c.Handle, c.IsActive }) .ToListAsync(ct); var items = channels.Select(c => { var ytId = c.YouTubeChannelID!; return new ChannelHealthItem( ytId, c.Name, c.Handle, c.IsActive, subscribedIds.Contains(ytId), lastPolled.TryGetValue(ytId, out var lp) ? lp : null, leaseExpiries.TryGetValue(ytId, out var le) ? le : null, lastProcessed.TryGetValue(ytId, out var vid) ? vid : null ); }).ToList(); return new Response( ApiKeyConfigured: !string.IsNullOrEmpty(apiKey), ApiKeyMaskedTail: MaskTail(apiKey), HmacSecretConfigured: !string.IsNullOrEmpty(hmac), HmacSecretMaskedTail: MaskTail(hmac), CallbackUrl: settings.Value.YouTube.CallbackUrl, FeedPollingIntervalMinutes: settings.Value.YouTube.FeedPollingIntervalMinutes > 0 ? settings.Value.YouTube.FeedPollingIntervalMinutes : 3, ActiveLiveChatCollectors: liveChatService.GetActiveChatIds().Count, ActiveLiveVideosCount: liveChannels.Count, SubscribedChannelsCount: subscribedIds.Count, ActiveInternalChannelsCount: channels.Count(c => c.IsActive), Channels: items ); } // 키 앞뒤 노출 방지 — 마지막 4글자만 표시 private static string? MaskTail(string? value) { if (string.IsNullOrEmpty(value)) { return null; } return value.Length <= 4 ? new string('*', value.Length) : $"****{value[^4..]}"; } }