| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- 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;
- /// <summary>
- /// YouTube 연동 해제 처리:
- /// 1. 회원의 MemberOAuthToken (Google provider) revoke + DB row 삭제
- /// 2. 회원의 Channel.WipeYouTubeData() 호출 — YT 출처 필드 모두 null·0
- /// 3. Redis YouTubeChannel:{channelID} 캐시 삭제
- ///
- /// 회원 본인 또는 Google Security 설정에서 권한 회수 시 호출됨.
- /// 탈퇴 Handler 와 동일한 wipe 절차, member.Withdraw() 호출만 제외.
- /// </summary>
- internal sealed class Handler(
- IAppDbContext db,
- IGoogleOAuthService oauthService,
- IYouTubeChannelCache youTubeChannelCache,
- IFieldEncryptor fieldEncryptor,
- ILogger<Handler> logger
- ) : ICommandHandler<Command, Result>
- {
- public async Task<Result> 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<string>();
- 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();
- }
- }
|