Handler.cs 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Application.Abstractions.YouTube;
  4. using Microsoft.EntityFrameworkCore;
  5. using Microsoft.Extensions.Logging;
  6. using SharedKernel.Results;
  7. namespace Application.Features.Api.MyPage.Sns.Disconnect;
  8. internal sealed class Handler(
  9. IAppDbContext db,
  10. IGoogleOAuthService oauthService,
  11. ILogger<Handler> logger
  12. ) : ICommandHandler<Command, Result<Response>>
  13. {
  14. private static readonly string[] AllowedProviders = ["Google"];
  15. private static readonly TimeSpan RetryDelay = TimeSpan.FromMilliseconds(500);
  16. public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
  17. {
  18. if (!AllowedProviders.Contains(request.Provider))
  19. {
  20. return Result.Failure<Response>(Error.Problem("Sns.UnsupportedProvider", "지원하지 않는 SNS provider 입니다."));
  21. }
  22. var token = await db.MemberOAuthToken.FirstOrDefaultAsync(t => t.MemberID == request.MemberID && t.Provider == request.Provider, ct);
  23. if (token is null)
  24. {
  25. // idempotent: 이미 연동되어 있지 않아도 성공으로 처리
  26. return Result.Success(new Response(request.Provider, true));
  27. }
  28. // Google revoke 시도 — refresh_token 우선 (refresh_token revoke 는 access_token 도 함께 무효화)
  29. var revokeTarget = !string.IsNullOrEmpty(token.RefreshTokenEnc)
  30. ? token.RefreshTokenEnc
  31. : token.AccessTokenEnc;
  32. var revoked = await oauthService.RevokeTokenAsync(revokeTarget, ct);
  33. if (!revoked)
  34. {
  35. // 1회 재시도 (네트워크 일시 장애/Google 5xx 대비)
  36. try
  37. {
  38. await Task.Delay(RetryDelay, ct);
  39. revoked = await oauthService.RevokeTokenAsync(revokeTarget, ct);
  40. }
  41. catch (OperationCanceledException)
  42. {
  43. throw;
  44. }
  45. }
  46. // DB 삭제 — revoke 성공/실패 무관 (DB 삭제는 항상 진행)
  47. db.MemberOAuthToken.Remove(token);
  48. await db.SaveChangesAsync(ct);
  49. if (!revoked)
  50. {
  51. logger.LogWarning("[Sns.Disconnect] revoke 실패 — DB 만 삭제. memberID={MemberID}, provider={Provider}", request.MemberID, request.Provider);
  52. }
  53. else
  54. {
  55. logger.LogInformation("[Sns.Disconnect] 연동 해제 완료: memberID={MemberID}, provider={Provider}", request.MemberID, request.Provider);
  56. }
  57. return Result.Success(new Response(request.Provider, revoked));
  58. }
  59. }