using Application.Abstractions.Data; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Infrastructure.YouTube; /// /// YouTube Data API v3 요청에 API Key를 자동으로 추가하는 DelegatingHandler /// DB Config에서 YouTube API Key를 조회하여 쿼리스트링에 &key=xxx 추가 /// internal sealed class YouTubeApiKeyHandler( IServiceProvider serviceProvider, ILogger logger ) : DelegatingHandler { private string? _cachedApiKey; private DateTime _cachedAt = DateTime.MinValue; private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(30); protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var apiKey = await GetApiKeyAsync(cancellationToken); if (!string.IsNullOrEmpty(apiKey) && request.RequestUri is not null) { // 이미 Authorization 헤더(OAuth)가 있으면 API Key 추가 안 함 if (request.Headers.Authorization is null) { var uriStr = request.RequestUri.ToString(); var separator = uriStr.Contains('?') ? "&" : "?"; request.RequestUri = new Uri($"{uriStr}{separator}key={Uri.EscapeDataString(apiKey)}"); } } else { logger.LogWarning("[YouTube] API Key가 비어있습니다. DB Config > External > YouTubeApiKeyEnc를 확인하세요."); } return await base.SendAsync(request, cancellationToken); } private async Task GetApiKeyAsync(CancellationToken ct) { if (_cachedApiKey is not null && DateTime.UtcNow - _cachedAt < CacheDuration) { return _cachedApiKey; } try { using var scope = serviceProvider.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var config = await db.Config.AsNoTracking().FirstOrDefaultAsync(ct); if (config is null) { return null; } // Config.External.YouTubeApiKeyEnc에서 API Key 가져옴 _cachedApiKey = config.External?.YouTubeApiKeyEnc; _cachedAt = DateTime.UtcNow; return _cachedApiKey; } catch (Exception ex) { logger.LogWarning(ex, "[YouTube] API Key 조회 실패"); return _cachedApiKey; // 이전 캐시 반환 } } }