using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Domain.Entities.EmailVerification.ValueObject; using SharedKernel.Results; using Microsoft.EntityFrameworkCore; namespace Application.Features.Api.MyPage.VerifyEmail; internal sealed class Handler(IAppDbContext db) : IQueryHandler { public async Task Handle(Query request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Token)) { return Result.Failure(Error.Problem("MyPage.TokenRequired", "인증 토큰은 필수입니다.")); } var verifyToken = await db.EmailVerifyToken.FirstOrDefaultAsync(t => t.Token == request.Token && t.Type == VerificationType.ChangedEmail, ct); if (verifyToken is null) { return Result.Failure(Error.NotFound("MyPage.TokenNotFound", "유효하지 않은 인증 토큰입니다.")); } if (verifyToken.IsVerified) { return Result.Failure(Error.Problem("MyPage.AlreadyVerified", "이미 인증된 토큰입니다.")); } if (verifyToken.Expiration < DateTime.UtcNow) { return Result.Failure(Error.Problem("MyPage.TokenExpired", "인증 토큰이 만료되었습니다.")); } // AdditionalData.Email = 기존 이메일, verifyToken.Email = 새 이메일 var oldEmail = verifyToken.AdditionalData?.Email; if (string.IsNullOrWhiteSpace(oldEmail)) { return Result.Failure(Error.Problem("MyPage.InvalidToken", "인증 토큰 데이터가 올바르지 않습니다.")); } var member = await db.Member.FirstOrDefaultAsync(m => m.Email == oldEmail, ct); if (member is null) { return Result.Failure(Error.NotFound("MyPage.MemberNotFound", "회원 정보를 찾을 수 없습니다.")); } // 새 이메일이 다른 회원에게 사용 중인지 확인 var newEmail = verifyToken.Email; var emailExists = await db.Member.AnyAsync(m => m.Email == newEmail && m.ID != member.ID, ct); if (emailExists) { return Result.Failure(Error.Conflict("MyPage.EmailExists", "이미 사용 중인 이메일입니다.")); } verifyToken.MarkVerified(); member.SetEmail(newEmail); member.MarkEmailVerified(); await db.SaveChangesAsync(ct); return Result.Success(); } }