using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Domain.Entities.Members; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.Studio.ConnectYouTube; internal sealed class Handler( IAppDbContext db, IGoogleOAuthService oauthService, IYouTubeApiService youtubeApiService, IYouTubeChannelCache channelCache ) : ICommandHandler { public async Task Handle(Command request, CancellationToken ct) { var config = await db.Config.AsNoTracking().OrderByDescending(x => x.ID).FirstOrDefaultAsync(ct); if (config is null || string.IsNullOrEmpty(config.External.GoogleClientId) || string.IsNullOrEmpty(config.External.GoogleClientSecretEnc )) { throw new InvalidOperationException("Google OAuth 설정이 없습니다."); } var tokens = await oauthService.ExchangeCodeAsync( request.Code, request.RedirectUri, config.External.GoogleClientId, config.External.GoogleClientSecretEnc, ct ); if (tokens is null) { throw new InvalidOperationException("Google 인증 코드 교환에 실패했습니다."); } var ytInfo = await youtubeApiService.GetMyChannelAsync(tokens.AccessToken, ct); if (ytInfo is null) { throw new InvalidOperationException("YouTube 채널 정보를 가져올 수 없습니다."); } var youtubeUrl = ytInfo.CustomUrl is not null ? $"https://youtube.com/{ytInfo.CustomUrl}" : $"https://youtube.com/channel/{ytInfo.ChannelID}"; // Member 크리에이터 전환 var member = await db.Member.FirstAsync(m => m.ID == request.MemberID, ct); if (!member.IsCreator) { member.SetCreator(true); } // Channel 생성 또는 업데이트 var channel = await db.Channel.FirstOrDefaultAsync(c => c.MemberID == request.MemberID, ct); if (channel is null) { channel = Domain.Entities.Members.Channel.Create(request.MemberID, ytInfo.ChannelID, ytInfo.Title, youtubeUrl); db.Channel.Add(channel); } // YouTube 상세 정보 DB 저장 channel.UpdateYouTubeInfo( ytInfo.ChannelID, ytInfo.Title, ytInfo.CustomUrl, ytInfo.Description, ytInfo.ThumbnailUrl, ytInfo.BannerUrl, ytInfo.SubscriberCount, ytInfo.VideoCount, ytInfo.ViewCount, ytInfo.Email, ytInfo.PublishedAt ); // MemberOAuthToken 생성 또는 업데이트 (Provider = "YouTube") var oauthToken = await db.MemberOAuthToken.FirstOrDefaultAsync(t => t.MemberID == request.MemberID && t.Provider == "YouTube", ct); var scopesStr = string.Join(" ", tokens.Scopes); if (oauthToken is null) { oauthToken = MemberOAuthToken.Create( request.MemberID, "YouTube", tokens.AccessToken, tokens.RefreshToken, tokens.ExpiresAt, scopesStr ); db.MemberOAuthToken.Add(oauthToken); } else { oauthToken.UpdateTokens(tokens.AccessToken, tokens.RefreshToken, tokens.ExpiresAt, scopesStr); } await db.SaveChangesAsync(ct); // 캐시 갱신 await channelCache.SetAsync(ytInfo); } }