From ad9d221983b0f0203a420853ef90197becd612d0 Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:44:00 +0200 Subject: [PATCH 1/6] refactor: move verification email orchestration into service --- .../controller/RegistrationController.java | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java b/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java index 6b757e0..7c6ccb8 100644 --- a/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java +++ b/src/main/java/com/empress/usermanagementapi/controller/RegistrationController.java @@ -3,14 +3,12 @@ import com.empress.usermanagementapi.entity.Role; import com.empress.usermanagementapi.entity.User; import com.empress.usermanagementapi.model.RegistrationRequest; -import com.empress.usermanagementapi.service.EmailService; import com.empress.usermanagementapi.service.EmailVerificationService; import com.empress.usermanagementapi.service.UserService; import com.empress.usermanagementapi.util.LoggingUtil; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; @@ -37,17 +35,11 @@ public class RegistrationController { private final UserService userService; private final EmailVerificationService emailVerificationService; - private final EmailService emailService; - - @Value("${app.base-url}") - private String baseUrl; public RegistrationController(UserService userService, - EmailVerificationService emailVerificationService, - EmailService emailService) { + EmailVerificationService emailVerificationService) { this.userService = userService; this.emailVerificationService = emailVerificationService; - this.emailService = emailService; } /** @@ -123,22 +115,7 @@ public String registerSubmit( created.getId(), created.getUsername()); - // create verification token - String token = emailVerificationService.createTokenForUser(created); - log.debug("Email verification token created - userId: {}", created.getId()); - - String verifyLink = baseUrl + "/verify-email?token=" + token; - - try { - emailService.sendVerificationEmail(created.getEmail(), verifyLink); - log.info("Verification email sent - userId: {}", created.getId()); - } catch (Exception e) { - // for now just log; account is created even if email fails - log.error("Failed to send verification email - userId: {}, error: {}", - created.getId(), - e.getMessage()); - System.err.println("Failed to send verification email: " + e.getMessage()); - } + emailVerificationService.createTokenAndSendVerificationEmail(created); } else { log.warn("User creation returned null or invalid user"); } From 189d557da8a4d2ece789f569a735c4a9b610f088 Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:44:24 +0200 Subject: [PATCH 2/6] refactor: move verification email orchestration into service --- .../service/EmailVerificationService.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java index ef8cb33..751a06a 100644 --- a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java +++ b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java @@ -7,6 +7,7 @@ import com.empress.usermanagementapi.util.LoggingUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.time.LocalDateTime; @@ -20,11 +21,17 @@ public class EmailVerificationService { private final EmailVerificationTokenRepository tokenRepo; private final UserRepository userRepo; + private final EmailService emailService; + + @Value("${app.base-url}") + private String baseUrl; public EmailVerificationService(EmailVerificationTokenRepository tokenRepo, - UserRepository userRepo) { + UserRepository userRepo, + EmailService emailService) { this.tokenRepo = tokenRepo; this.userRepo = userRepo; + this.emailService = emailService; } // create or refresh a token for this user and return the token string @@ -65,6 +72,30 @@ public String createTokenForUser(User user) { return newTokenValue; } + /** + * Creates a verification token and sends the verification email. + * + * The account is already created at this point, so email delivery failures are logged + * without rolling back registration. This preserves the existing registration behavior + * while keeping email orchestration inside the service layer. + */ + public void createTokenAndSendVerificationEmail(User user) { + String token = createTokenForUser(user); + log.debug("Email verification token created - userId: {}", user.getId()); + + String verifyLink = baseUrl + "/verify-email?token=" + token; + + try { + emailService.sendVerificationEmail(user.getEmail(), verifyLink); + log.info("Verification email sent - userId: {}", user.getId()); + } catch (Exception e) { + log.error("Failed to send verification email - userId: {}, error: {}", + user.getId(), + e.getMessage()); + System.err.println("Failed to send verification email: " + e.getMessage()); + } + } + /** * @return null on success, or an error message on failure */ From 48f71e9abfc5425a5b03751f72c403265b63778a Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:45:01 +0200 Subject: [PATCH 3/6] refactor: move password reset email orchestration into service --- .../controller/ForgotPasswordController.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java b/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java index 5e078ca..9a52a56 100644 --- a/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java +++ b/src/main/java/com/empress/usermanagementapi/controller/ForgotPasswordController.java @@ -1,13 +1,10 @@ package com.empress.usermanagementapi.controller; -import com.empress.usermanagementapi.entity.PasswordResetToken; import com.empress.usermanagementapi.entity.User; import com.empress.usermanagementapi.service.UserService; -import com.empress.usermanagementapi.service.EmailService; import com.empress.usermanagementapi.service.PasswordResetService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; @@ -20,17 +17,11 @@ public class ForgotPasswordController { private static final Logger log = LoggerFactory.getLogger(ForgotPasswordController.class); private final UserService userService; - private final EmailService emailService; private final PasswordResetService passwordResetService; - @Value("${app.base-url}") - private String baseUrl; - public ForgotPasswordController(UserService userService, - EmailService emailService, PasswordResetService passwordResetService) { this.userService = userService; - this.emailService = emailService; this.passwordResetService = passwordResetService; } @@ -64,15 +55,8 @@ public String handleForm(@RequestParam String username, return "forgot-password"; } - // User exists, create token + send email - PasswordResetToken tokenEntity = - passwordResetService.createPasswordResetTokenForEmail(trimmedEmail); - String token = tokenEntity.getToken(); - - String resetLink = baseUrl + "/reset-password?token=" + token; - try { - emailService.sendPasswordResetEmail(trimmedEmail, resetLink); + passwordResetService.createTokenAndSendResetEmail(trimmedEmail); } catch (Exception e) { log.error("Failed to send password reset email to: {}", trimmedEmail, e); model.addAttribute("error", "Failed to send password reset email. Please try again later."); From a261da7f6d3480694f3cf09c9a6b792a775767db Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 21:45:29 +0200 Subject: [PATCH 4/6] refactor: move password reset email orchestration into service --- .../service/PasswordResetService.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java index e58beb8..346829b 100644 --- a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java +++ b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java @@ -7,6 +7,7 @@ import com.empress.usermanagementapi.util.LoggingUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -22,13 +23,19 @@ public class PasswordResetService { private final PasswordResetTokenRepository tokenRepo; private final UserRepository userRepo; private final PasswordEncoder passwordEncoder; + private final EmailService emailService; + + @Value("${app.base-url}") + private String baseUrl; public PasswordResetService(PasswordResetTokenRepository tokenRepo, UserRepository userRepo, - PasswordEncoder passwordEncoder) { + PasswordEncoder passwordEncoder, + EmailService emailService) { this.tokenRepo = tokenRepo; this.userRepo = userRepo; this.passwordEncoder = passwordEncoder; + this.emailService = emailService; } public PasswordResetToken createPasswordResetTokenForEmail(String email) { @@ -70,6 +77,18 @@ public PasswordResetToken createPasswordResetTokenForEmail(String email) { return saved; } + /** + * Creates a password reset token and sends the reset email. + * + * Callers only need to validate who is allowed to request a reset; this service owns + * the token creation and email-delivery workflow. + */ + public void createTokenAndSendResetEmail(String email) { + PasswordResetToken tokenEntity = createPasswordResetTokenForEmail(email); + String resetLink = baseUrl + "/reset-password?token=" + tokenEntity.getToken(); + emailService.sendPasswordResetEmail(email, resetLink); + } + public String validatePasswordResetToken(String token) { String cleanToken = token == null ? null : token.trim(); From 70a9e62f45654766d21eecc1941a5c84488beab6 Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:03:03 +0200 Subject: [PATCH 5/6] refactor: remove duplicate stderr logging --- .../usermanagementapi/service/EmailVerificationService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java index 751a06a..662e4dc 100644 --- a/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java +++ b/src/main/java/com/empress/usermanagementapi/service/EmailVerificationService.java @@ -92,7 +92,6 @@ public void createTokenAndSendVerificationEmail(User user) { log.error("Failed to send verification email - userId: {}, error: {}", user.getId(), e.getMessage()); - System.err.println("Failed to send verification email: " + e.getMessage()); } } From 8d448f62a55fa8ea319026449977d534a6268450 Mon Sep 17 00:00:00 2001 From: enlorik <99548776+enlorik@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:03:36 +0200 Subject: [PATCH 6/6] docs: clarify password reset email failure behavior --- .../usermanagementapi/service/PasswordResetService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java index 346829b..e3234f3 100644 --- a/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java +++ b/src/main/java/com/empress/usermanagementapi/service/PasswordResetService.java @@ -81,7 +81,8 @@ public PasswordResetToken createPasswordResetTokenForEmail(String email) { * Creates a password reset token and sends the reset email. * * Callers only need to validate who is allowed to request a reset; this service owns - * the token creation and email-delivery workflow. + * the token creation and email-delivery workflow. Email delivery failures are allowed + * to propagate so the controller can show the user that the reset email was not sent. */ public void createTokenAndSendResetEmail(String email) { PasswordResetToken tokenEntity = createPasswordResetTokenForEmail(email);