using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using SharedKernel.Results; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Application.Features.Api.Studio.DisconnectYouTube; internal sealed class Handler( IAppDbContext db, IYouTubeChannelCache channelCache, IGoogleOAuthService oauthService, IYouTubePubSubService pubSubService, ILogger logger ) : ICommandHandler { public async Task Handle(Command request, CancellationToken ct) { var channel = await db.Channel.FirstOrDefaultAsync(c => c.MemberID == request.MemberID && c.IsActive, ct); if (channel is null) { return Result.Failure(Error.NotFound("YouTube.ChannelNotConnected", "연동된 채널이 없습니다.")); } // OAuth 토큰 조회 → revoke → 삭제 var oauthToken = await db.MemberOAuthToken.FirstOrDefaultAsync(t => t.MemberID == request.MemberID && t.Provider == "YouTube", ct); if (oauthToken is not null) { // Google OAuth revoke (실패해도 무시) await oauthService.RevokeTokenAsync(oauthToken.AccessTokenEnc, ct); db.MemberOAuthToken.Remove(oauthToken); } // 채널 비활성화 channel.Deactivate(); // Redis 캐시 삭제 var cacheKey = channel.YouTubeChannelID ?? channel.SID; await channelCache.RemoveAsync(cacheKey); await db.SaveChangesAsync(ct); // PubSub 구독 해지 (실패해도 DB는 이미 해지됨) if (!string.IsNullOrEmpty(channel.YouTubeChannelID)) { try { await pubSubService.UnsubscribeAsync(channel.YouTubeChannelID, ct); } catch (Exception ex) { logger.LogError(ex, "[DisconnectYouTube] PubSub 구독 해지 중 예외: channelId={ChannelId}", channel.YouTubeChannelID); } } logger.LogInformation("YouTube 연동 해지: MemberID={MemberID}, ChannelID={ChannelID}", request.MemberID, channel.ID); return Result.Success(); } }