Handler.cs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.YouTube;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.Extensions.Options;
  6. using SharedKernel;
  7. namespace Application.Features.Admin.Channel.YouTubePubSub.GetHealth;
  8. internal sealed class Handler(
  9. IAppDbContext db,
  10. IYouTubePubSubService pubSubService,
  11. IYouTubeLiveChatService liveChatService,
  12. IYouTubeLiveStateStore liveStateStore,
  13. IOptions<AppSettings> settings
  14. ) : IQueryHandler<Query, Response>
  15. {
  16. public async Task<Response> Handle(Query request, CancellationToken ct)
  17. {
  18. var cfg = await db.Config.AsNoTracking().FirstOrDefaultAsync(ct);
  19. var apiKey = cfg?.External?.YouTubeApiKeyEnc;
  20. var hmac = settings.Value.YouTube.HmacSecret;
  21. var subscribedIds = (await pubSubService.GetSubscribedChannelIdsAsync(ct)).ToHashSet();
  22. var lastPolled = await pubSubService.GetAllLastPolledAtAsync(ct);
  23. var leaseExpiries = await pubSubService.GetAllLeaseExpiriesAsync(ct);
  24. var lastProcessed = await pubSubService.GetAllLastProcessedVideoIdsAsync(ct);
  25. var liveChannels = await liveStateStore.GetAllLiveAsync();
  26. var channels = await db.Channel.AsNoTracking()
  27. .Where(c => c.YouTubeChannelID != null && c.YouTubeChannelID != "")
  28. .OrderByDescending(c => c.IsActive)
  29. .ThenBy(c => c.Name)
  30. .Select(c => new
  31. {
  32. c.YouTubeChannelID,
  33. c.Name,
  34. c.Handle,
  35. c.IsActive
  36. })
  37. .ToListAsync(ct);
  38. var items = channels.Select(c =>
  39. {
  40. var ytId = c.YouTubeChannelID!;
  41. return new ChannelHealthItem(
  42. ytId,
  43. c.Name,
  44. c.Handle,
  45. c.IsActive,
  46. subscribedIds.Contains(ytId),
  47. lastPolled.TryGetValue(ytId, out var lp) ? lp : null,
  48. leaseExpiries.TryGetValue(ytId, out var le) ? le : null,
  49. lastProcessed.TryGetValue(ytId, out var vid) ? vid : null
  50. );
  51. }).ToList();
  52. return new Response(
  53. ApiKeyConfigured: !string.IsNullOrEmpty(apiKey),
  54. ApiKeyMaskedTail: MaskTail(apiKey),
  55. HmacSecretConfigured: !string.IsNullOrEmpty(hmac),
  56. HmacSecretMaskedTail: MaskTail(hmac),
  57. CallbackUrl: settings.Value.YouTube.CallbackUrl,
  58. FeedPollingIntervalMinutes: settings.Value.YouTube.FeedPollingIntervalMinutes > 0
  59. ? settings.Value.YouTube.FeedPollingIntervalMinutes
  60. : 3,
  61. ActiveLiveChatCollectors: liveChatService.GetActiveChatIds().Count,
  62. ActiveLiveVideosCount: liveChannels.Count,
  63. SubscribedChannelsCount: subscribedIds.Count,
  64. ActiveInternalChannelsCount: channels.Count(c => c.IsActive),
  65. Channels: items
  66. );
  67. }
  68. // 키 앞뒤 노출 방지 — 마지막 4글자만 표시
  69. private static string? MaskTail(string? value)
  70. {
  71. if (string.IsNullOrEmpty(value))
  72. {
  73. return null;
  74. }
  75. return value.Length <= 4
  76. ? new string('*', value.Length)
  77. : $"****{value[^4..]}";
  78. }
  79. }