From 96eb11e5f78330d41f1aa8852754dea354b9b37c Mon Sep 17 00:00:00 2001 From: Alan Di Giovanni Date: Thu, 10 Jul 2025 18:33:49 -0300 Subject: [PATCH 1/2] feat(PC-125): Refactor with best practices --- .../AuthenticationControllerAdapter.java | 72 +++++++----------- .../out/{mail => email}/EmailService.java | 2 +- ...ionNotificationEmailRepositoryAdapter.java | 76 +++++++++++++++++++ ...ActiveDatabaseActiveRepositoryAdapter.java | 8 +- .../UpdateUserDatabaseRepositoryAdapter.java | 1 + .../adapter/out/mail/EmailServiceImpl.java | 49 ------------ .../port/in/ActivateUserCommand.java | 12 ++- .../out/SendConfirmationEmailRepository.java | 7 ++ .../port/out/UpdateUserActiveRepository.java | 1 - .../usecase/ActivateUserUseCase.java | 35 +++++---- .../usecase/CreateUserUseCase.java | 53 +++++-------- .../usecase/UpdateUserProfileUseCase.java | 33 ++------ .../com/cuoco/application/utils/JwtUtil.java | 28 ++++++- .../cuoco/shared/model/ErrorDescription.java | 1 + .../java/com/cuoco/shared/utils/JwtUtil.java | 1 + 15 files changed, 197 insertions(+), 182 deletions(-) rename src/main/java/com/cuoco/adapter/out/{mail => email}/EmailService.java (73%) create mode 100644 src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java delete mode 100644 src/main/java/com/cuoco/adapter/out/mail/EmailServiceImpl.java create mode 100644 src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java diff --git a/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java b/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java index df38590..eeed0ec 100644 --- a/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java +++ b/src/main/java/com/cuoco/adapter/in/controller/AuthenticationControllerAdapter.java @@ -7,7 +7,6 @@ import com.cuoco.adapter.in.controller.model.UserPreferencesResponse; import com.cuoco.adapter.in.controller.model.UserRequest; import com.cuoco.adapter.in.controller.model.UserResponse; -import com.cuoco.adapter.out.mail.EmailService; import com.cuoco.application.port.in.ActivateUserCommand; import com.cuoco.application.port.in.CreateUserCommand; import com.cuoco.application.port.in.SignInUserCommand; @@ -16,7 +15,6 @@ import com.cuoco.application.usecase.model.DietaryNeed; import com.cuoco.application.usecase.model.User; import com.cuoco.shared.GlobalExceptionHandler; -import com.cuoco.shared.utils.JwtUtil; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; @@ -24,39 +22,30 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.util.List; @Slf4j @RestController +@RequiredArgsConstructor @RequestMapping("/auth") + @Tag(name = "Authentication", description = "Operations related to authenticate users") public class AuthenticationControllerAdapter { private final SignInUserCommand signInUserCommand; private final CreateUserCommand createUserCommand; private final ActivateUserCommand activateUserCommand; - private final EmailService emailService; - private final JwtUtil jwtUtil; - - - public AuthenticationControllerAdapter( - SignInUserCommand signInUserCommand, - CreateUserCommand createUserCommand, - ActivateUserCommand activateUserCommand, - EmailService emailService, - JwtUtil jwtUtil - ) { - this.signInUserCommand = signInUserCommand; - this.createUserCommand = createUserCommand; - this.activateUserCommand = activateUserCommand; - this.emailService = emailService; - this.jwtUtil = jwtUtil; - } @PostMapping("/login") @Operation(summary = "POST for user authentication with email and password") @@ -104,14 +93,6 @@ public ResponseEntity login(@RequestBody AuthRequest request) { return ResponseEntity.ok(response); } - private AuthResponse buildAuthResponse(AuthenticatedUser authenticatedUser) { - return AuthResponse.builder() - .data(AuthDataResponse.builder() - .user(buildUserResponse(authenticatedUser.getUser(), authenticatedUser.getToken())) - .build()) - .build(); - } - @PostMapping("/register") @Operation(summary = "POST for user creation with basic data and preferences") @ApiResponses(value = { @@ -152,26 +133,21 @@ public ResponseEntity register(@RequestBody @Valid UserRequest req log.info("Executing POST register with email {}", request.getEmail()); User user = createUserCommand.execute(buildCreateCommand(request)); - - String confirmationLink = "http://localhost:8080/auth/confirm?token=" + generateConfirmationToken(user); - - emailService.sendConfirmationEmail(user.getEmail(), confirmationLink); - UserResponse userResponse = buildUserResponse(user, null); return ResponseEntity.status(HttpStatus.CREATED).body(userResponse); } @GetMapping("/confirm") - @Operation(summary = "GET para confirmar el email del usuario") + @Operation(summary = "GET for confirm user email") @ApiResponses(value = { @ApiResponse( responseCode = "200", - description = "Email confirmado exitosamente" + description = "Email confirmed successfully" ), @ApiResponse( responseCode = "400", - description = "Token inválido o expirado", + description = "Invalid token or expired", content = @Content( mediaType = "application/json", schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) @@ -179,18 +155,19 @@ public ResponseEntity register(@RequestBody @Valid UserRequest req ), @ApiResponse( responseCode = "404", - description = "Usuario no encontrado", + description = "User not found", content = @Content( mediaType = "application/json", schema = @Schema(implementation = GlobalExceptionHandler.ApiErrorResponse.class) ) ) }) - public ResponseEntity confirmEmail(@RequestParam String token) { - log.info("Ejecutando confirmación de email"); + public ResponseEntity confirmEmail(@RequestParam String token) { + log.info("Executing POST email confirmation for token {}", token); - String email = jwtUtil.extractEmail(token); - activateUserCommand.execute(email); + ActivateUserCommand.Command command = ActivateUserCommand.Command.builder().token(token).build(); + + activateUserCommand.execute(command); return ResponseEntity.ok().build(); } @@ -215,6 +192,14 @@ private CreateUserCommand.Command buildCreateCommand(UserRequest request) { ); } + private AuthResponse buildAuthResponse(AuthenticatedUser authenticatedUser) { + return AuthResponse.builder() + .data(AuthDataResponse.builder() + .user(buildUserResponse(authenticatedUser.getUser(), authenticatedUser.getToken())) + .build()) + .build(); + } + private UserResponse buildUserResponse(User user, String token) { return UserResponse.builder() .id(user.getId()) @@ -235,9 +220,4 @@ private List buildDietaryNeeds(List dietaryNeed private List buildAllergies(List allergies) { return allergies != null && !allergies.isEmpty() ? allergies.stream().map(ParametricResponse::fromDomain).toList() : null; } - - private String generateConfirmationToken(User user) { - return jwtUtil.generateToken(user); - } - } diff --git a/src/main/java/com/cuoco/adapter/out/mail/EmailService.java b/src/main/java/com/cuoco/adapter/out/email/EmailService.java similarity index 73% rename from src/main/java/com/cuoco/adapter/out/mail/EmailService.java rename to src/main/java/com/cuoco/adapter/out/email/EmailService.java index 3ff249e..754ec09 100644 --- a/src/main/java/com/cuoco/adapter/out/mail/EmailService.java +++ b/src/main/java/com/cuoco/adapter/out/email/EmailService.java @@ -1,4 +1,4 @@ -package com.cuoco.adapter.out.mail; +package com.cuoco.adapter.out.email; public interface EmailService { diff --git a/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java new file mode 100644 index 0000000..edfc661 --- /dev/null +++ b/src/main/java/com/cuoco/adapter/out/email/SendConfirmationNotificationEmailRepositoryAdapter.java @@ -0,0 +1,76 @@ +package com.cuoco.adapter.out.email; + +import com.cuoco.adapter.exception.NotAvailableException; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.shared.model.ErrorDescription; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class SendConfirmationNotificationEmailRepositoryAdapter implements SendConfirmationEmailRepository { + + private final HttpServletRequest request; + private final JavaMailSender mailSender; + + @Override + public void execute(User user, String token) { + try { + log.info("Executing send confirmation email for user with ID {}", user.getId()); + + String confirmationLink = buildConfirmationLink(token); + + MimeMessage message = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(message, true); + + helper.setFrom("latribudemicalle1480@gmail.com"); + helper.setTo(user.getEmail()); + helper.setSubject("Confirma tu cuenta en Cuoco"); + + String content = """ + + +

¡Bienvenido a Cuoco!

+

Por favor, confirma tu cuenta haciendo clic en el siguiente enlace:

+ Confirmar cuenta +

Si no creaste una cuenta en Cuoco, puedes ignorar este mensaje.

+ + + """.formatted(confirmationLink); + + helper.setText(content, true); + + mailSender.send(message); + + log.info("Successfully sended confirmation email to {} with link {}", user.getEmail(), confirmationLink); + } catch (MessagingException e) { + log.error("Error sending confirmation email to {}: {}", user.getEmail(), e.getMessage()); + throw new NotAvailableException(ErrorDescription.NOT_AVAILABLE.getValue()); + } + } + + private String buildConfirmationLink(String token) { + + String baseUrl = request.getRequestURL().toString() + .replace(request.getRequestURI(), ""); + + String contextPath = request.getContextPath(); + + String confirmationLink = baseUrl + .concat(contextPath) + .concat("/auth/confirm?token=") + .concat(token); + + log.info("Builded URL link from this base {} and this context {}", baseUrl, contextPath); + return confirmationLink; + } +} + diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserActiveDatabaseActiveRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserActiveDatabaseActiveRepositoryAdapter.java index 31524c2..1b3f4d6 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserActiveDatabaseActiveRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserActiveDatabaseActiveRepositoryAdapter.java @@ -5,19 +5,17 @@ import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.out.UpdateUserActiveRepository; import com.cuoco.application.usecase.model.User; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Repository; @Slf4j @Repository +@RequiredArgsConstructor public class UpdateUserActiveDatabaseActiveRepositoryAdapter implements UpdateUserActiveRepository { private final UpdateUserActiveHibernateRepositoryAdapter updateUserActiveHibernateRepositoryAdapter; - public UpdateUserActiveDatabaseActiveRepositoryAdapter(UpdateUserActiveHibernateRepositoryAdapter updateUserActiveHibernateRepositoryAdapter) { - this.updateUserActiveHibernateRepositoryAdapter = updateUserActiveHibernateRepositoryAdapter; - } - @Override public void execute(User user) { UserHibernateModel existingUser = updateUserActiveHibernateRepositoryAdapter.findById(user.getId()) @@ -27,6 +25,4 @@ public void execute(User user) { updateUserActiveHibernateRepositoryAdapter.save(existingUser); } - - } diff --git a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java index a9ed2bf..831c41f 100644 --- a/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java +++ b/src/main/java/com/cuoco/adapter/out/hibernate/UpdateUserDatabaseRepositoryAdapter.java @@ -64,6 +64,7 @@ private UserHibernateModel buildUserHibernateModel(User user) { .name(user.getName()) .email(user.getEmail()) .password(user.getPassword()) + .active(user.getActive()) .plan(PlanHibernateModel.fromDomain(user.getPlan())) .dietaryNeeds(user.getDietaryNeeds() != null ? user.getDietaryNeeds().stream().map(DietaryNeedHibernateModel::fromDomain).toList() : List.of()) .allergies(user.getAllergies() != null ? user.getAllergies().stream().map(AllergyHibernateModel::fromDomain).toList() : List.of()) diff --git a/src/main/java/com/cuoco/adapter/out/mail/EmailServiceImpl.java b/src/main/java/com/cuoco/adapter/out/mail/EmailServiceImpl.java deleted file mode 100644 index 3c98d86..0000000 --- a/src/main/java/com/cuoco/adapter/out/mail/EmailServiceImpl.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.cuoco.adapter.out.mail; - -import jakarta.mail.MessagingException; -import jakarta.mail.internet.MimeMessage; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.mail.javamail.JavaMailSender; -import org.springframework.mail.javamail.MimeMessageHelper; -import org.springframework.stereotype.Service; - -@Service -@Slf4j -@RequiredArgsConstructor -public class EmailServiceImpl implements EmailService { - - private final JavaMailSender mailSender; - - @Override - public void sendConfirmationEmail(String to, String confirmationLink) { - try { - MimeMessage message = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(message, true); - - helper.setFrom("latribudemicalle1480@gmail.com"); - helper.setTo(to); - helper.setSubject("Confirma tu cuenta en Cuoco"); - - String content = """ - - -

¡Bienvenido a Cuoco!

-

Por favor, confirma tu cuenta haciendo clic en el siguiente enlace:

- Confirmar cuenta -

Si no creaste una cuenta en Cuoco, puedes ignorar este mensaje.

- - - """.formatted(confirmationLink); - - helper.setText(content, true); - - mailSender.send(message); - log.info("Correo de confirmación enviado a: {}", to); - } catch (MessagingException e) { - log.error("Error al enviar correo de confirmación a {}: {}", to, e.getMessage()); - throw new RuntimeException("Error al enviar correo de confirmación", e); - } - } -} - diff --git a/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java b/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java index 332dbb5..af3e47e 100644 --- a/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java +++ b/src/main/java/com/cuoco/application/port/in/ActivateUserCommand.java @@ -1,6 +1,16 @@ package com.cuoco.application.port.in; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + public interface ActivateUserCommand { - void execute(String email); + void execute(Command command); + @Data + @Builder + @AllArgsConstructor + class Command { + private String token; + } } diff --git a/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java b/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java new file mode 100644 index 0000000..141d2bc --- /dev/null +++ b/src/main/java/com/cuoco/application/port/out/SendConfirmationEmailRepository.java @@ -0,0 +1,7 @@ +package com.cuoco.application.port.out; + +import com.cuoco.application.usecase.model.User; + +public interface SendConfirmationEmailRepository { + void execute(User user, String token); +} diff --git a/src/main/java/com/cuoco/application/port/out/UpdateUserActiveRepository.java b/src/main/java/com/cuoco/application/port/out/UpdateUserActiveRepository.java index 3fcf657..b1836d4 100644 --- a/src/main/java/com/cuoco/application/port/out/UpdateUserActiveRepository.java +++ b/src/main/java/com/cuoco/application/port/out/UpdateUserActiveRepository.java @@ -4,5 +4,4 @@ public interface UpdateUserActiveRepository { void execute(User user); - } diff --git a/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java index fdceadf..935c0ef 100644 --- a/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/ActivateUserUseCase.java @@ -1,29 +1,38 @@ package com.cuoco.application.usecase; -import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.in.ActivateUserCommand; -import com.cuoco.application.port.out.GetUserByEmailRepository; -import com.cuoco.application.port.out.UpdateUserActiveRepository; +import com.cuoco.application.port.out.GetUserByIdRepository; +import com.cuoco.application.port.out.UpdateUserRepository; +import com.cuoco.application.usecase.model.User; +import com.cuoco.application.utils.JwtUtil; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; -@Service +@Slf4j +@Component @RequiredArgsConstructor public class ActivateUserUseCase implements ActivateUserCommand { - private final GetUserByEmailRepository getUserByEmailRepository; - private final UpdateUserActiveRepository updateUserActiveRepository; + + private final GetUserByIdRepository getUserByIdRepository; + private final UpdateUserRepository updateUserRepository; + private final JwtUtil jwtUtil; @Override @Transactional - public void execute(String email) { - var user = getUserByEmailRepository.execute(email); - if (user == null) { - throw new BadRequestException("Usuario no encontrado"); - } + public void execute(Command command) { + log.info("Executing user activation with token {}", command.getToken()); + + Long id = Long.valueOf(jwtUtil.extractId(command.getToken())); + + User user = getUserByIdRepository.execute(id); user.setActive(true); - updateUserActiveRepository.execute(user); + + updateUserRepository.execute(user); + + log.info("Successfully activated user with ID {} and email {}", user.getId(), user.getEmail()); } } diff --git a/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java index e8d4f13..86de879 100644 --- a/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/CreateUserUseCase.java @@ -3,12 +3,13 @@ import com.cuoco.application.exception.BadRequestException; import com.cuoco.application.port.in.CreateUserCommand; import com.cuoco.application.port.out.CreateUserRepository; +import com.cuoco.application.port.out.ExistsUserByEmailRepository; import com.cuoco.application.port.out.GetAllergiesByIdRepository; import com.cuoco.application.port.out.GetCookLevelByIdRepository; import com.cuoco.application.port.out.GetDietByIdRepository; import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; import com.cuoco.application.port.out.GetPlanByIdRepository; -import com.cuoco.application.port.out.ExistsUserByEmailRepository; +import com.cuoco.application.port.out.SendConfirmationEmailRepository; import com.cuoco.application.usecase.model.Allergy; import com.cuoco.application.usecase.model.CookLevel; import com.cuoco.application.usecase.model.Diet; @@ -16,8 +17,11 @@ import com.cuoco.application.usecase.model.Plan; import com.cuoco.application.usecase.model.User; import com.cuoco.application.usecase.model.UserPreferences; +import com.cuoco.application.utils.JwtUtil; import com.cuoco.shared.model.ErrorDescription; +import com.cuoco.shared.utils.PlanConstants; import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -27,6 +31,7 @@ @Slf4j @Component +@RequiredArgsConstructor public class CreateUserUseCase implements CreateUserCommand { private final PasswordEncoder passwordEncoder; @@ -37,26 +42,8 @@ public class CreateUserUseCase implements CreateUserCommand { private final GetCookLevelByIdRepository getCookLevelByIdRepository; private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; private final GetAllergiesByIdRepository getAllergiesByIdRepository; - - public CreateUserUseCase( - PasswordEncoder passwordEncoder, - CreateUserRepository createUserRepository, - ExistsUserByEmailRepository existsUserByEmailRepository, - GetPlanByIdRepository getPlanByIdRepository, - GetDietByIdRepository getDietByIdRepository, - GetCookLevelByIdRepository getCookLevelByIdRepository, - GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository, - GetAllergiesByIdRepository getAllergiesByIdRepository - ) { - this.passwordEncoder = passwordEncoder; - this.createUserRepository = createUserRepository; - this.existsUserByEmailRepository = existsUserByEmailRepository; - this.getPlanByIdRepository = getPlanByIdRepository; - this.getDietByIdRepository = getDietByIdRepository; - this.getCookLevelByIdRepository = getCookLevelByIdRepository; - this.getDietaryNeedsByIdRepository = getDietaryNeedsByIdRepository; - this.getAllergiesByIdRepository = getAllergiesByIdRepository; - } + private final SendConfirmationEmailRepository sendConfirmationEmailRepository; + private final JwtUtil jwtUtil; @Transactional public User execute(Command command) { @@ -70,9 +57,9 @@ public User execute(Command command) { List existingNeeds = getDietaryNeeds(command); List existingAlergies = getAllergies(command); - Plan plan = getPlan(command.getPlanId()); - CookLevel cookLevel = getCookLevel(command.getCookLevelId()); - Diet diet = getDiet(command.getDietId()); + Plan plan = getPlanByIdRepository.execute(PlanConstants.FREE.getValue()); + CookLevel cookLevel = getCookLevelByIdRepository.execute(command.getCookLevelId()); + Diet diet = getDietByIdRepository.execute(command.getDietId()); UserPreferences preferencesToSave = buildUserPreferences(cookLevel, diet); User userToSave = buildUser(command, preferencesToSave, plan, existingNeeds, existingAlergies); @@ -81,19 +68,9 @@ public User execute(Command command) { userCreated.setPassword(null); - return userCreated; - } + sendConfirmationEmail(userCreated); - private Plan getPlan(Integer planId) { - return getPlanByIdRepository.execute(planId); - } - - private Diet getDiet(Integer dietId) { - return getDietByIdRepository.execute(dietId); - } - - private CookLevel getCookLevel(Integer cookLevelId) { - return getCookLevelByIdRepository.execute(cookLevelId); + return userCreated; } private List getDietaryNeeds(Command command) { @@ -155,4 +132,8 @@ private UserPreferences buildUserPreferences(CookLevel cookLevel, Diet diet) { .build(); } + private void sendConfirmationEmail(User user) { + String token = jwtUtil.generateActivationToken(user); + sendConfirmationEmailRepository.execute(user, token); + } } diff --git a/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java b/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java index e7c5e91..1ef561f 100644 --- a/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/UpdateUserProfileUseCase.java @@ -6,7 +6,6 @@ import com.cuoco.application.port.out.GetCookLevelByIdRepository; import com.cuoco.application.port.out.GetDietByIdRepository; import com.cuoco.application.port.out.GetDietaryNeedsByIdRepository; -import com.cuoco.application.port.out.GetPlanByIdRepository; import com.cuoco.application.port.out.GetUserByIdRepository; import com.cuoco.application.port.out.UpdateUserRepository; import com.cuoco.application.usecase.domainservice.UserDomainService; @@ -14,13 +13,11 @@ import com.cuoco.application.usecase.model.CookLevel; import com.cuoco.application.usecase.model.Diet; import com.cuoco.application.usecase.model.DietaryNeed; -import com.cuoco.application.usecase.model.Plan; import com.cuoco.application.usecase.model.User; import com.cuoco.application.usecase.model.UserPreferences; import com.cuoco.shared.model.ErrorDescription; -import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import java.util.Collections; @@ -28,45 +25,25 @@ @Slf4j @Component +@RequiredArgsConstructor public class UpdateUserProfileUseCase implements UpdateUserProfileCommand { private final UserDomainService userDomainService; private final GetUserByIdRepository getUserByIdRepository; private final UpdateUserRepository updateUserRepository; - private final GetPlanByIdRepository getPlanByIdRepository; private final GetDietByIdRepository getDietByIdRepository; private final GetCookLevelByIdRepository getCookLevelByIdRepository; private final GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository; private final GetAllergiesByIdRepository getAllergiesByIdRepository; - public UpdateUserProfileUseCase( - UserDomainService userDomainService, - GetUserByIdRepository getUserByIdRepository, - UpdateUserRepository updateUserRepository, - GetPlanByIdRepository getPlanByIdRepository, - GetDietByIdRepository getDietByIdRepository, - GetCookLevelByIdRepository getCookLevelByIdRepository, - GetDietaryNeedsByIdRepository getDietaryNeedsByIdRepository, - GetAllergiesByIdRepository getAllergiesByIdRepository - ) { - this.userDomainService = userDomainService; - this.getUserByIdRepository = getUserByIdRepository; - this.updateUserRepository = updateUserRepository; - this.getPlanByIdRepository = getPlanByIdRepository; - this.getDietByIdRepository = getDietByIdRepository; - this.getCookLevelByIdRepository = getCookLevelByIdRepository; - this.getDietaryNeedsByIdRepository = getDietaryNeedsByIdRepository; - this.getAllergiesByIdRepository = getAllergiesByIdRepository; - } - @Override public User execute(Command command) { User user = userDomainService.getCurrentUser(); log.info("Executing update user use case with ID {}", user.getId()); User existingUser = getUserByIdRepository.execute(user.getId()); - User userToUpdate = buildUpdateUser(existingUser, command); + User updatedUser = updateUserRepository.execute(userToUpdate); log.info("User with ID {} updated successfully", user.getId()); @@ -76,14 +53,14 @@ public User execute(Command command) { private User buildUpdateUser(User existingUser, Command command) { String updatedName = command.getName() != null ? command.getName() : existingUser.getName(); - Plan updatedPlan = command.getPlanId() != null ? getPlanByIdRepository.execute(command.getPlanId()) : existingUser.getPlan(); return User.builder() .id(existingUser.getId()) .name(updatedName) .email(existingUser.getEmail()) .password(existingUser.getPassword()) - .plan(updatedPlan) + .active(existingUser.getActive()) + .plan(existingUser.getPlan()) .preferences(buildUserPreferences(existingUser.getPreferences(), command)) .dietaryNeeds(getUpdatedDietaryNeeds(command, existingUser.getDietaryNeeds())) .allergies(getUpdatedAllergies(command, existingUser.getAllergies())) diff --git a/src/main/java/com/cuoco/application/utils/JwtUtil.java b/src/main/java/com/cuoco/application/utils/JwtUtil.java index 82fe5a9..c33b297 100644 --- a/src/main/java/com/cuoco/application/utils/JwtUtil.java +++ b/src/main/java/com/cuoco/application/utils/JwtUtil.java @@ -22,6 +22,7 @@ public class JwtUtil { public String generateToken(User user) { return Jwts.builder() + .setId(user.getId().toString()) .setSubject(user.getEmail()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas @@ -29,6 +30,31 @@ public String generateToken(User user) { .compact(); } + public String generateActivationToken(User user) { + return Jwts.builder() + .setId(user.getId().toString()) + .setSubject(user.getEmail()) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 2)) // 2 horas + .signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()), SignatureAlgorithm.HS256) + .compact(); + } + + public String extractId(String token) { + try { + return Jwts.parserBuilder() + .setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes())) + .build() + .parseClaimsJws(token) + .getBody() + .getId(); + + } catch (MalformedJwtException e) { + log.warn("Extract ID: Invalid JWT token: {}", e.getMessage()); + throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); + } + } + public String extractEmail(String token) { try { return Jwts.parserBuilder() @@ -39,7 +65,7 @@ public String extractEmail(String token) { .getSubject(); } catch (MalformedJwtException e) { - log.warn("Invalid JWT token: {}", e.getMessage()); + log.warn("Extract email: Invalid JWT token: {}", e.getMessage()); throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } } diff --git a/src/main/java/com/cuoco/shared/model/ErrorDescription.java b/src/main/java/com/cuoco/shared/model/ErrorDescription.java index 684d9d5..36d42e0 100644 --- a/src/main/java/com/cuoco/shared/model/ErrorDescription.java +++ b/src/main/java/com/cuoco/shared/model/ErrorDescription.java @@ -24,6 +24,7 @@ public enum ErrorDescription { USER_NOT_EXISTS("El usuario ingresado no existe"), USER_DUPLICATED("El usuario ya existe"), NO_AUTH_TOKEN("El token no esta presente"), + INVALID_TOKEN("El token ingresado no es válido"), INVALID_CREDENTIALS("Las credenciales no son válidas"), EXPIRED_CREDENTIALS("El token ha expirado"), diff --git a/src/main/java/com/cuoco/shared/utils/JwtUtil.java b/src/main/java/com/cuoco/shared/utils/JwtUtil.java index 8060ff5..b49da99 100644 --- a/src/main/java/com/cuoco/shared/utils/JwtUtil.java +++ b/src/main/java/com/cuoco/shared/utils/JwtUtil.java @@ -17,6 +17,7 @@ public class JwtUtil { public String generateToken(User user) { return Jwts.builder() + .setId(user.getId().toString()) .setSubject(user.getEmail()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 horas From 4c3870650fff8c8eaedca4cd627cd80b40e2465d Mon Sep 17 00:00:00 2001 From: Alan Di Giovanni Date: Thu, 10 Jul 2025 19:27:32 -0300 Subject: [PATCH 2/2] feat(PC-125): Added user active conditions --- .../cuoco/application/usecase/AuthenticateUserUseCase.java | 6 ++++++ .../com/cuoco/application/usecase/SignInUserUseCase.java | 7 ++++++- src/main/java/com/cuoco/shared/model/ErrorDescription.java | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java b/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java index fdfea4e..ef88212 100644 --- a/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/AuthenticateUserUseCase.java @@ -1,5 +1,6 @@ package com.cuoco.application.usecase; +import com.cuoco.application.exception.ForbiddenException; import com.cuoco.application.exception.UnauthorizedException; import com.cuoco.application.port.in.AuthenticateUserCommand; import com.cuoco.application.port.out.GetUserByEmailRepository; @@ -59,6 +60,11 @@ public AuthenticatedUser execute(Command command) { throw new UnauthorizedException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } + if (user.getActive() != null && !user.getActive()) { + log.info("User with email {} is not activated yet", email); + throw new ForbiddenException(ErrorDescription.USER_NOT_ACTIVATED.getValue()); + } + log.info("User authenticated with email {}", email); return buildAuthenticatedUser(user); } diff --git a/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java b/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java index 4e8ea0f..6615a01 100644 --- a/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java +++ b/src/main/java/com/cuoco/application/usecase/SignInUserUseCase.java @@ -5,8 +5,8 @@ import com.cuoco.application.port.out.GetUserByEmailRepository; import com.cuoco.application.usecase.model.AuthenticatedUser; import com.cuoco.application.usecase.model.User; -import com.cuoco.shared.model.ErrorDescription; import com.cuoco.application.utils.JwtUtil; +import com.cuoco.shared.model.ErrorDescription; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -41,6 +41,11 @@ public AuthenticatedUser execute(Command command) { throw new ForbiddenException(ErrorDescription.INVALID_CREDENTIALS.getValue()); } + if(user.getActive() != null && !user.getActive()) { + log.info("User with email {} is not activated yet", user.getEmail()); + throw new ForbiddenException(ErrorDescription.USER_NOT_ACTIVATED.getValue()); + } + user.setPassword(null); return buildAuthenticatedUser(user); diff --git a/src/main/java/com/cuoco/shared/model/ErrorDescription.java b/src/main/java/com/cuoco/shared/model/ErrorDescription.java index 36d42e0..e14def3 100644 --- a/src/main/java/com/cuoco/shared/model/ErrorDescription.java +++ b/src/main/java/com/cuoco/shared/model/ErrorDescription.java @@ -23,6 +23,7 @@ public enum ErrorDescription { USER_NOT_EXISTS("El usuario ingresado no existe"), USER_DUPLICATED("El usuario ya existe"), + USER_NOT_ACTIVATED("El usuario no esta activo"), NO_AUTH_TOKEN("El token no esta presente"), INVALID_TOKEN("El token ingresado no es válido"), INVALID_CREDENTIALS("Las credenciales no son válidas"),