Add email verification functionality, including backend support, email handling, and user interface integration.
This commit is contained in:
+140
@@ -0,0 +1,140 @@
|
||||
package com.alttd.altitudeweb.services.mail;
|
||||
|
||||
import com.alttd.altitudeweb.database.Databases;
|
||||
import com.alttd.altitudeweb.database.web_db.mail.EmailVerification;
|
||||
import com.alttd.altitudeweb.database.web_db.mail.EmailVerificationMapper;
|
||||
import com.alttd.altitudeweb.setup.Connection;
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MailVerificationService {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
|
||||
@Value("${spring.mail.username}")
|
||||
private String fromEmail;
|
||||
|
||||
public java.util.List<EmailVerification> listAll(UUID userUuid) {
|
||||
java.util.concurrent.CompletableFuture<java.util.List<EmailVerification>> future = new java.util.concurrent.CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DEFAULT)
|
||||
.runQuery(sql -> {
|
||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||
future.complete(mapper.findAllByUser(userUuid));
|
||||
});
|
||||
return future.join();
|
||||
}
|
||||
|
||||
public EmailVerification submitEmail(UUID userUuid, String email) {
|
||||
String code = generateCode();
|
||||
Instant now = Instant.now();
|
||||
|
||||
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DEFAULT)
|
||||
.runQuery(sql -> {
|
||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
||||
EmailVerification toPersist;
|
||||
if (existing == null) {
|
||||
toPersist = new EmailVerification(UUID.randomUUID(), userUuid, email, code, false, now, null, now);
|
||||
mapper.insert(toPersist);
|
||||
} else {
|
||||
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
||||
toPersist = new EmailVerification(existing.id(), userUuid, email, code, false, existing.createdAt(), null, now);
|
||||
}
|
||||
future.complete(toPersist);
|
||||
});
|
||||
EmailVerification saved = future.join();
|
||||
sendVerificationEmail(saved);
|
||||
return saved;
|
||||
}
|
||||
|
||||
public Optional<EmailVerification> verifyCode(UUID userUuid, String code) {
|
||||
CompletableFuture<Optional<EmailVerification>> future = new CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DEFAULT)
|
||||
.runQuery(sql -> {
|
||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||
EmailVerification found = mapper.findByUserAndCode(userUuid, code);
|
||||
if (found == null) {
|
||||
future.complete(Optional.empty());
|
||||
return;
|
||||
}
|
||||
mapper.markVerified(found.id(), Instant.now());
|
||||
future.complete(Optional.of(found));
|
||||
});
|
||||
return future.join();
|
||||
}
|
||||
|
||||
public EmailVerification resend(UUID userUuid, String email) {
|
||||
String code = generateCode();
|
||||
Instant now = Instant.now();
|
||||
CompletableFuture<EmailVerification> future = new CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DEFAULT)
|
||||
.runQuery(sql -> {
|
||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
||||
if (existing != null) {
|
||||
mapper.updateCodeAndLastSent(existing.id(), code, now);
|
||||
future.complete(new EmailVerification(existing.id(), userUuid, email, code, false, existing.createdAt(), null, now));
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
});
|
||||
EmailVerification updated = future.join();
|
||||
if (updated != null) {
|
||||
sendVerificationEmail(updated);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
public boolean delete(UUID userUuid, String email) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
Connection.getConnection(Databases.DEFAULT)
|
||||
.runQuery(sql -> {
|
||||
EmailVerificationMapper mapper = sql.getMapper(EmailVerificationMapper.class);
|
||||
EmailVerification existing = mapper.findByUserAndEmail(userUuid, email);
|
||||
if (existing != null) {
|
||||
mapper.deleteByUserAndEmail(userUuid, email);
|
||||
future.complete(true);
|
||||
} else {
|
||||
future.complete(false);
|
||||
}
|
||||
});
|
||||
return future.join();
|
||||
}
|
||||
|
||||
private void sendVerificationEmail(EmailVerification emailVerification) {
|
||||
try {
|
||||
MimeMessage message = mailSender.createMimeMessage();
|
||||
MimeMessageHelper helper = new MimeMessageHelper(message, true);
|
||||
helper.setFrom(fromEmail);
|
||||
helper.setTo(emailVerification.email());
|
||||
helper.setSubject("Your verification code");
|
||||
helper.setText("Your verification code is: " + emailVerification.verificationCode(), false);
|
||||
mailSender.send(message);
|
||||
} catch (MessagingException e) {
|
||||
log.error("Failed to send verification email to {}", emailVerification.email(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateCode() {
|
||||
// 6-digit numeric code
|
||||
Random random = new Random();
|
||||
int num = 100000 + random.nextInt(899999);
|
||||
return String.valueOf(num);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user