| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- using System.Text.Json;
- using Application.Abstractions.YouTube;
- using Microsoft.Extensions.Logging;
- namespace Infrastructure.YouTube;
- internal sealed class YouTubeApiConnectionTester(
- IHttpClientFactory httpClientFactory,
- ILogger<YouTubeApiConnectionTester> logger
- ) : IYouTubeApiConnectionTester
- {
- public async Task<YouTubeApiTestResult> TestAsync(string testChannelId, CancellationToken ct)
- {
- // "YouTubeApi" 명명 HttpClient = YouTubeApiKeyHandler가 자동으로 ?key= 붙여줌
- var client = httpClientFactory.CreateClient("YouTubeApi");
- var url = $"https://www.googleapis.com/youtube/v3/channels?part=snippet&id={Uri.EscapeDataString(testChannelId)}";
- try
- {
- using var response = await client.GetAsync(url, ct);
- var body = await response.Content.ReadAsStringAsync(ct);
- var status = (int)response.StatusCode;
- if (!response.IsSuccessStatusCode)
- {
- var msg = ExtractGoogleErrorMessage(body) ?? Truncate(body, 300);
- logger.LogWarning("[YouTube] API key test failed: {Status} — {Body}", status, Truncate(body, 500));
- return new YouTubeApiTestResult(
- Ok: false,
- HttpStatus: status,
- ChannelTitle: null,
- ChannelId: testChannelId,
- ErrorMessage: $"HTTP {status} {response.StatusCode} — {msg}"
- );
- }
- try
- {
- var doc = JsonSerializer.Deserialize<JsonElement>(body);
- if (!doc.TryGetProperty("items", out var items) || items.GetArrayLength() == 0)
- {
- return new YouTubeApiTestResult(
- Ok: false,
- HttpStatus: status,
- ChannelTitle: null,
- ChannelId: testChannelId,
- ErrorMessage: "응답의 items가 비어있습니다. API Key는 유효하나 채널 조회가 제한됐을 수 있음 (Cloud Console에서 API Key 제한 설정 확인)."
- );
- }
- var title = items[0].GetProperty("snippet").GetProperty("title").GetString();
- return new YouTubeApiTestResult(
- Ok: true,
- HttpStatus: status,
- ChannelTitle: title,
- ChannelId: testChannelId,
- ErrorMessage: null
- );
- }
- catch (JsonException ex)
- {
- return new YouTubeApiTestResult(
- Ok: false,
- HttpStatus: status,
- ChannelTitle: null,
- ChannelId: testChannelId,
- ErrorMessage: $"응답 JSON 파싱 실패: {ex.Message}"
- );
- }
- }
- catch (Exception ex)
- {
- logger.LogError(ex, "[YouTube] API key test threw");
- return new YouTubeApiTestResult(
- Ok: false,
- HttpStatus: null,
- ChannelTitle: null,
- ChannelId: testChannelId,
- ErrorMessage: $"요청 실패: {ex.Message}"
- );
- }
- }
- // Google API 에러 응답: { "error": { "code": 403, "message": "...", "errors": [{reason:"keyInvalid"}] } }
- private static string? ExtractGoogleErrorMessage(string body)
- {
- if (string.IsNullOrWhiteSpace(body))
- {
- return null;
- }
- try
- {
- var doc = JsonSerializer.Deserialize<JsonElement>(body);
- if (doc.TryGetProperty("error", out var err))
- {
- var msg = err.TryGetProperty("message", out var m) ? m.GetString() : null;
- var reason = err.TryGetProperty("errors", out var errs) && errs.GetArrayLength() > 0
- && errs[0].TryGetProperty("reason", out var r) ? r.GetString() : null;
- if (!string.IsNullOrEmpty(msg))
- {
- return reason is not null ? $"[{reason}] {msg}" : msg;
- }
- }
- }
- catch
- {
- // ignore — fallback to raw body
- }
- return null;
- }
- private static string Truncate(string s, int max) =>
- s.Length <= max ? s : s[..max] + "...";
- }
|