using Application.Abstractions.Crypto;
using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Application.Abstractions.YouTube;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SharedKernel.Results;
namespace Application.Features.Api.MyPage.UnlinkYouTube;
///
/// YouTube 연동 해제 처리:
/// 1. 회원의 MemberOAuthToken (Google provider) revoke + DB row 삭제
/// 2. 회원의 Channel.WipeYouTubeData() 호출 — YT 출처 필드 모두 null·0
/// 3. Redis YouTubeChannel:{channelID} 캐시 삭제
///
/// 회원 본인 또는 Google Security 설정에서 권한 회수 시 호출됨.
/// 탈퇴 Handler 와 동일한 wipe 절차, member.Withdraw() 호출만 제외.
///
internal sealed class Handler(
IAppDbContext db,
IGoogleOAuthService oauthService,
IYouTubeChannelCache youTubeChannelCache,
IFieldEncryptor fieldEncryptor,
ILogger logger
) : ICommandHandler
{
public async Task Handle(Command request, CancellationToken ct)
{
var memberExists = await db.Member.AnyAsync(m => m.ID == request.MemberID, ct);
if (!memberExists)
{
return Result.Failure(Error.NotFound("MyPage.MemberNotFound", "회원 정보를 찾을 수 없습니다."));
}
var oauthTokens = await db.MemberOAuthToken.Where(t => t.MemberID == request.MemberID).ToListAsync(ct);
var purgedChannelIds = new List();
foreach (var token in oauthTokens)
{
var encrypted = token.RefreshTokenEnc ?? token.AccessTokenEnc;
if (!string.IsNullOrEmpty(encrypted))
{
try
{
var plaintext = fieldEncryptor.Decrypt(encrypted, 1);
if (!string.IsNullOrEmpty(plaintext))
{
await oauthService.RevokeTokenAsync(plaintext, ct);
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "[UnlinkYouTube] OAuth revoke 실패 (memberID={MemberID}) — row 삭제는 진행", request.MemberID);
}
}
db.MemberOAuthToken.Remove(token);
}
var channels = await db.Channel.Where(c => c.MemberID == request.MemberID).ToListAsync(ct);
foreach (var channel in channels)
{
if (!string.IsNullOrEmpty(channel.YouTubeChannelID))
{
purgedChannelIds.Add(channel.YouTubeChannelID);
}
channel.WipeYouTubeData();
}
if (oauthTokens.Count == 0 && channels.Count == 0)
{
return Result.Success();
}
await db.SaveChangesAsync(ct);
foreach (var channelID in purgedChannelIds)
{
try
{
await youTubeChannelCache.RemoveAsync(channelID);
}
catch (Exception ex)
{
logger.LogWarning(ex, "[UnlinkYouTube] Redis YouTube 캐시 제거 실패 (channelID={ChannelID})", channelID);
}
}
logger.LogInformation(
"[UnlinkYouTube] memberID={MemberID} — tokens={Tokens}, channels={Channels}, cache={Cache}",
request.MemberID, oauthTokens.Count, channels.Count, purgedChannelIds.Count
);
return Result.Success();
}
}