using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Application.Abstractions.YouTube; using Domain.Entities.Members; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using SharedKernel.Results; namespace Application.Features.Api.MyPage.Sns.Connect; internal sealed class Handler( IAppDbContext db, IGoogleOAuthService oauthService, ILogger logger ) : ICommandHandler> { private const string Provider = "Google"; public async Task> Handle(Command request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Code)) { return Result.Failure(Error.Problem("Sns.CodeRequired", "Authorization code 가 필요합니다.")); } if (string.IsNullOrWhiteSpace(request.RedirectUri)) { return Result.Failure(Error.Problem("Sns.RedirectUriRequired", "Redirect URI 가 필요합니다.")); } 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) ) { return Result.Failure(Error.Problem("Sns.OAuthConfigMissing", "Google OAuth 설정이 없습니다.")); } var tokens = await oauthService.ExchangeCodeAsync( request.Code, request.RedirectUri, config.External.GoogleClientId, config.External.GoogleClientSecretEnc, ct ); if (tokens is null) { return Result.Failure(Error.Problem("Sns.OAuthExchangeFailed", "Google 인증 코드 교환에 실패했습니다.")); } var existing = await db.MemberOAuthToken.FirstOrDefaultAsync(t => t.MemberID == request.MemberID && t.Provider == Provider, ct); var scopesStr = string.Join(" ", tokens.Scopes); if (existing is null) { var token = MemberOAuthToken.Create( request.MemberID, Provider, tokens.AccessToken, tokens.RefreshToken, tokens.ExpiresAt, scopesStr ); db.MemberOAuthToken.Add(token); } else { existing.UpdateTokens(tokens.AccessToken, tokens.RefreshToken, tokens.ExpiresAt, scopesStr); } await db.SaveChangesAsync(ct); logger.LogInformation("[Sns.Connect] Google 연동 완료: memberID={MemberID}", request.MemberID); return Result.Success(new Response(Provider, DateTime.UtcNow, tokens.ExpiresAt)); } }