From 3e2e72fef01e41e2c171d4768b685ab6ef3931b0 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 15:40:19 +0300 Subject: [PATCH 1/4] Add address management and user details retrieval functionality --- pom.xml | 2 +- .../auth/controller/UserController.java | 28 +++-- .../podzilla/auth/dto/CustomUserDetails.java | 3 + .../com/podzilla/auth/dto/SignupRequest.java | 3 + .../com/podzilla/auth/dto/UpdateRequest.java | 11 ++ .../podzilla/auth/dto/UserDetailsRequest.java | 12 ++ .../java/com/podzilla/auth/model/Address.java | 52 +++++++++ .../java/com/podzilla/auth/model/User.java | 10 ++ .../auth/repository/AddressRepository.java | 11 ++ .../auth/repository/UserRepository.java | 1 + .../podzilla/auth/service/AdminService.java | 1 + .../auth/service/AuthenticationService.java | 34 +++--- .../podzilla/auth/service/UserService.java | 109 ++++++++++++++++-- 13 files changed, 247 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/podzilla/auth/dto/UpdateRequest.java create mode 100644 src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java create mode 100644 src/main/java/com/podzilla/auth/model/Address.java create mode 100644 src/main/java/com/podzilla/auth/repository/AddressRepository.java diff --git a/pom.xml b/pom.xml index c06b865..1a1032e 100644 --- a/pom.xml +++ b/pom.xml @@ -43,7 +43,7 @@ com.github.Podzilla podzilla-utils-lib - v1.1.5 + v1.1.6 org.springframework.boot diff --git a/src/main/java/com/podzilla/auth/controller/UserController.java b/src/main/java/com/podzilla/auth/controller/UserController.java index eea7dc5..8c49507 100644 --- a/src/main/java/com/podzilla/auth/controller/UserController.java +++ b/src/main/java/com/podzilla/auth/controller/UserController.java @@ -1,19 +1,19 @@ package com.podzilla.auth.controller; +import com.podzilla.auth.dto.UpdateRequest; +import com.podzilla.auth.dto.UserDetailsRequest; import com.podzilla.auth.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestBody; -import java.util.UUID; - @RestController @RequestMapping("/user") public class UserController { @@ -27,14 +27,24 @@ public UserController(final UserService userService) { this.userService = userService; } - @PutMapping("/update/{userId}") + @PutMapping("/update") @Operation(summary = "Update user name", description = "Allows user to update their name.") @ApiResponse(responseCode = "200", description = "User profile updated successfully") - public void updateProfile(@PathVariable final UUID userId, - @Valid @RequestBody final String name) { - LOGGER.debug("Received updateProfile request for userId={}", userId); - userService.updateUserProfile(userId, name); + public void updateProfile(@Valid @RequestBody final UpdateRequest + updateRequest) { + LOGGER.debug("Received updateProfile request"); + userService.updateUserProfile(updateRequest); + } + + @GetMapping("/details") + @Operation(summary = "Get user details", + description = "Fetches the details of the current user.") + @ApiResponse(responseCode = "200", + description = "User details fetched successfully") + public UserDetailsRequest getUserDetails() { + LOGGER.debug("Received getUserDetails request"); + return userService.getUserDetails(); } } diff --git a/src/main/java/com/podzilla/auth/dto/CustomUserDetails.java b/src/main/java/com/podzilla/auth/dto/CustomUserDetails.java index 1337bd7..4803fba 100644 --- a/src/main/java/com/podzilla/auth/dto/CustomUserDetails.java +++ b/src/main/java/com/podzilla/auth/dto/CustomUserDetails.java @@ -11,6 +11,7 @@ import org.springframework.security.core.userdetails.UserDetails; import java.util.Set; +import java.util.UUID; @JsonIgnoreProperties(ignoreUnknown = true) @Builder @@ -21,6 +22,8 @@ public class CustomUserDetails implements UserDetails { private String username; + private UUID id; + @JsonIgnore private String password; diff --git a/src/main/java/com/podzilla/auth/dto/SignupRequest.java b/src/main/java/com/podzilla/auth/dto/SignupRequest.java index 5749166..7697899 100644 --- a/src/main/java/com/podzilla/auth/dto/SignupRequest.java +++ b/src/main/java/com/podzilla/auth/dto/SignupRequest.java @@ -1,5 +1,6 @@ package com.podzilla.auth.dto; +import com.podzilla.mq.events.DeliveryAddress; import lombok.Data; @Data @@ -7,4 +8,6 @@ public class SignupRequest { private String name; private String email; private String password; + private String mobileNumber; + private DeliveryAddress address; } diff --git a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java new file mode 100644 index 0000000..9258d09 --- /dev/null +++ b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java @@ -0,0 +1,11 @@ +package com.podzilla.auth.dto; + +import com.podzilla.mq.events.DeliveryAddress; +import lombok.Data; + +@Data +public class UpdateRequest { + private String name; + private DeliveryAddress address; + private String mobileNumber; +} diff --git a/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java b/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java new file mode 100644 index 0000000..23c4491 --- /dev/null +++ b/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java @@ -0,0 +1,12 @@ +package com.podzilla.auth.dto; + +import com.podzilla.mq.events.DeliveryAddress; +import lombok.Builder; + +@Builder +public class UserDetailsRequest { + private String email; + private String name; + private String mobileNumber; + private DeliveryAddress address; +} diff --git a/src/main/java/com/podzilla/auth/model/Address.java b/src/main/java/com/podzilla/auth/model/Address.java new file mode 100644 index 0000000..9cf314d --- /dev/null +++ b/src/main/java/com/podzilla/auth/model/Address.java @@ -0,0 +1,52 @@ +package com.podzilla.auth.model; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.Getter; + +import java.util.UUID; + +@Entity +@Table(name = "addresses") +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class Address { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @OneToOne(optional = false) + @JoinColumn(name = "user_id", nullable = false) + @JsonIgnore + private User user; + + @NotBlank(message = "Street is required") + private String street; + + @NotBlank(message = "City is required") + private String city; + + @NotBlank(message = "State is required") + private String state; + + @NotBlank(message = "Country is required") + private String country; + + @NotBlank(message = "Postal code is required") + private String postalCode; +} diff --git a/src/main/java/com/podzilla/auth/model/User.java b/src/main/java/com/podzilla/auth/model/User.java index 6b9b230..6daf2db 100644 --- a/src/main/java/com/podzilla/auth/model/User.java +++ b/src/main/java/com/podzilla/auth/model/User.java @@ -10,6 +10,7 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.ManyToMany; import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; import jakarta.persistence.Table; import jakarta.persistence.FetchType; @@ -19,6 +20,7 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -48,6 +50,14 @@ public class User { @NotBlank(message = "Password is required") private String password; + @NotBlank(message = "Mobile number is required") + @Column(unique = true) + private String mobileNumber; + + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, + orphanRemoval = true) + private Address address; + @Builder.Default @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "users_roles", diff --git a/src/main/java/com/podzilla/auth/repository/AddressRepository.java b/src/main/java/com/podzilla/auth/repository/AddressRepository.java new file mode 100644 index 0000000..7c5fa0e --- /dev/null +++ b/src/main/java/com/podzilla/auth/repository/AddressRepository.java @@ -0,0 +1,11 @@ +package com.podzilla.auth.repository; + +import com.podzilla.auth.model.Address; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; +import java.util.UUID; + +public interface AddressRepository extends JpaRepository { + Optional
findByUserId(UUID userId); +} diff --git a/src/main/java/com/podzilla/auth/repository/UserRepository.java b/src/main/java/com/podzilla/auth/repository/UserRepository.java index 683717a..53b057a 100644 --- a/src/main/java/com/podzilla/auth/repository/UserRepository.java +++ b/src/main/java/com/podzilla/auth/repository/UserRepository.java @@ -11,4 +11,5 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); Boolean existsByEmail(String email); + Boolean existsByMobileNumber(String mobileNumber); } diff --git a/src/main/java/com/podzilla/auth/service/AdminService.java b/src/main/java/com/podzilla/auth/service/AdminService.java index 3801478..3654556 100644 --- a/src/main/java/com/podzilla/auth/service/AdminService.java +++ b/src/main/java/com/podzilla/auth/service/AdminService.java @@ -71,6 +71,7 @@ public static UserDetails getUserDetails(final User user) { return CustomUserDetails.builder() .username(user.getEmail()) + .id(user.getId()) .password(user.getPassword()) .enabled(user.getEnabled()) .authorities(authorities) diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index f31fca6..1fc6aa8 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -1,5 +1,6 @@ package com.podzilla.auth.service; +import com.podzilla.auth.dto.CustomUserDetails; import com.podzilla.auth.dto.LoginRequest; import com.podzilla.auth.dto.SignupRequest; import com.podzilla.auth.exception.InvalidActionException; @@ -124,21 +125,26 @@ public String refreshToken(final HttpServletRequest request, public void addUserDetailsInHeader( final HttpServletResponse response) { + + CustomUserDetails userDetails = getCurrentUserDetails(); + String email = userDetails.getUsername(); + StringBuilder roles = new StringBuilder(); + userDetails.getAuthorities().forEach((authority) -> { + if (!roles.isEmpty()) { + roles.append(", "); + } + roles.append(authority.getAuthority()); + }); + setRoleAndEmailInHeader(response, email, roles.toString(), + userDetails.getId().toString()); + } + + public static CustomUserDetails getCurrentUserDetails() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - Object principal = authentication.getPrincipal(); - if (principal instanceof UserDetails) { - UserDetails userDetails = (UserDetails) principal; - String email = userDetails.getUsername(); - StringBuilder roles = new StringBuilder(); - userDetails.getAuthorities().forEach((authority) -> { - if (!roles.isEmpty()) { - roles.append(", "); - } - roles.append(authority.getAuthority()); - }); - setRoleAndEmailInHeader(response, email, roles.toString()); + if (principal instanceof CustomUserDetails) { + return (CustomUserDetails) principal; } else { throw new InvalidActionException( "User details not saved correctly."); @@ -148,9 +154,11 @@ public void addUserDetailsInHeader( private void setRoleAndEmailInHeader( final HttpServletResponse response, final String email, - final String roles) { + final String roles, + final String id) { response.setHeader("X-User-Email", email); response.setHeader("X-User-Roles", roles); + response.setHeader("X-User-Id", id); } private void checkNotNullValidationException(final String value, diff --git a/src/main/java/com/podzilla/auth/service/UserService.java b/src/main/java/com/podzilla/auth/service/UserService.java index 1b24454..95616eb 100644 --- a/src/main/java/com/podzilla/auth/service/UserService.java +++ b/src/main/java/com/podzilla/auth/service/UserService.java @@ -1,8 +1,15 @@ package com.podzilla.auth.service; +import com.podzilla.auth.dto.CustomUserDetails; +import com.podzilla.auth.dto.UpdateRequest; +import com.podzilla.auth.dto.UserDetailsRequest; import com.podzilla.auth.exception.NotFoundException; +import com.podzilla.auth.exception.ValidationException; +import com.podzilla.auth.model.Address; import com.podzilla.auth.model.User; +import com.podzilla.auth.repository.AddressRepository; import com.podzilla.auth.repository.UserRepository; +import com.podzilla.mq.events.DeliveryAddress; import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,21 +24,98 @@ public class UserService { LoggerFactory.getLogger(UserService.class); private final UserRepository userRepository; + private final AddressRepository addressRepository; - public UserService(final UserRepository userRepository) { + public UserService(final UserRepository userRepository, + final AddressRepository addressRepository) { this.userRepository = userRepository; + this.addressRepository = addressRepository; } @Transactional - public void updateUserProfile(final UUID userId, final String name) { - User user = getUserOrThrow(userId); - LOGGER.debug("Updating name for userId={}", userId); - user.setName(name); - userRepository.save(user); - LOGGER.debug("User profile updated successfully for userId={}", userId); + public void updateUserProfile(final UpdateRequest updateRequest) { + LOGGER.debug("Updating user profile"); + CustomUserDetails customUserDetails = + AuthenticationService.getCurrentUserDetails(); + if (updateRequest.getName() != null + && !updateRequest.getName().isBlank()) { + User user = getUserOrThrow(customUserDetails.getId()); + LOGGER.debug("Updating user with id={}", user.getId()); + user.setName(updateRequest.getName()); + userRepository.save(user); + } + if (updateRequest.getMobileNumber() != null + && !updateRequest.getMobileNumber().isBlank() + && mobileNumberIsUnique(updateRequest.getMobileNumber())) { + User user = getUserOrThrow(customUserDetails.getId()); + LOGGER.debug("Updating mobile number for user with id={}", + user.getId()); + user.setMobileNumber(updateRequest.getMobileNumber()); + userRepository.save(user); + } + if (updateRequest.getAddress() != null + && isValidAddress(updateRequest.getAddress())) { + Address address = getAddressOrThrow( + customUserDetails.getId()); + LOGGER.debug("Updating address for user with id={}", + address.getUser().getId()); + address.setStreet(updateRequest.getAddress().getStreet()); + address.setCity(updateRequest.getAddress().getCity()); + address.setState(updateRequest.getAddress().getState()); + address.setCountry(updateRequest.getAddress().getCountry()); + address.setPostalCode(updateRequest.getAddress() + .getPostalCode()); + addressRepository.save(address); + } } + public UserDetailsRequest getUserDetails() { + CustomUserDetails customUserDetails = + AuthenticationService.getCurrentUserDetails(); + LOGGER.debug("Fetching user details for user with id={}", + customUserDetails.getId()); + User user = getUserOrThrow(customUserDetails.getId()); + DeliveryAddress address = new DeliveryAddress(); + address.setStreet(user.getAddress().getStreet()); + address.setCity(user.getAddress().getCity()); + address.setState(user.getAddress().getState()); + address.setCountry(user.getAddress().getCountry()); + address.setPostalCode(user.getAddress().getPostalCode()); + return UserDetailsRequest.builder() + .name(user.getName()) + .email(user.getEmail()) + .mobileNumber(user.getMobileNumber()) + .address(address) + .build(); + } + + private boolean isValidAddress(final DeliveryAddress address) { + if (address.getStreet() == null || address.getStreet().isBlank()) { + throw new ValidationException("Street is required"); + } + if (address.getCity() == null || address.getCity().isBlank()) { + throw new ValidationException("City is required"); + } + if (address.getState() == null || address.getState().isBlank()) { + throw new ValidationException("State is required"); + } + if (address.getCountry() == null || address.getCountry().isBlank()) { + throw new ValidationException("Country is required"); + } + if (address.getPostalCode() == null + || address.getPostalCode().isBlank()) { + throw new ValidationException("Postal code is required"); + } + return true; + } + + private boolean mobileNumberIsUnique(final String mobileNumber) { + if (userRepository.existsByMobileNumber(mobileNumber)) { + throw new ValidationException("Mobile number already exists"); + } + return true; + } public User getUserOrThrow(final UUID userId) { LOGGER.debug("Fetching user with id={}", userId); @@ -42,4 +126,15 @@ public User getUserOrThrow(final UUID userId) { + userId + " does not exist."); }); } + + public Address getAddressOrThrow(final UUID userId) { + LOGGER.debug("Fetching address for user with id={}", userId); + return addressRepository.findByUserId(userId) + .orElseThrow(() -> { + LOGGER.warn("Address not found for user with id={}", + userId); + return new NotFoundException("Address for user with id " + + userId + " does not exist."); + }); + } } From 918e171a8acc6deb4c4a7e3d9ed366fd4e873c6f Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:27:27 +0300 Subject: [PATCH 2/4] Enhance user registration and login by adding mobile number and address validation --- .../controller/AuthenticationController.java | 5 +++-- .../com/podzilla/auth/dto/LoginRequest.java | 7 ++++++ .../com/podzilla/auth/dto/SignupRequest.java | 13 +++++++++++ .../com/podzilla/auth/dto/UpdateRequest.java | 8 +++++++ .../java/com/podzilla/auth/model/Address.java | 5 ----- .../java/com/podzilla/auth/model/User.java | 4 ---- .../auth/service/AuthenticationService.java | 22 +++++-------------- .../AuthenticationControllerTest.java | 16 ++++++++++++++ .../service/AuthenticationServiceTest.java | 18 --------------- 9 files changed, 53 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/podzilla/auth/controller/AuthenticationController.java b/src/main/java/com/podzilla/auth/controller/AuthenticationController.java index 0d1c85f..a4cd768 100644 --- a/src/main/java/com/podzilla/auth/controller/AuthenticationController.java +++ b/src/main/java/com/podzilla/auth/controller/AuthenticationController.java @@ -7,6 +7,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -43,7 +44,7 @@ public AuthenticationController( description = "User logged in successfully" ) public ResponseEntity login( - @RequestBody final LoginRequest loginRequest, + @Valid @RequestBody final LoginRequest loginRequest, final HttpServletResponse response) { String email = authenticationService.login(loginRequest, response); LOGGER.info("User {} logged in", email); @@ -62,7 +63,7 @@ public ResponseEntity login( description = "User registered successfully" ) public ResponseEntity registerUser( - @RequestBody final SignupRequest signupRequest) { + @Valid @RequestBody final SignupRequest signupRequest) { authenticationService.registerAccount(signupRequest); LOGGER.info("User {} registered", signupRequest.getEmail()); return new ResponseEntity<>("Account registered.", diff --git a/src/main/java/com/podzilla/auth/dto/LoginRequest.java b/src/main/java/com/podzilla/auth/dto/LoginRequest.java index dce141e..1030318 100644 --- a/src/main/java/com/podzilla/auth/dto/LoginRequest.java +++ b/src/main/java/com/podzilla/auth/dto/LoginRequest.java @@ -1,9 +1,16 @@ package com.podzilla.auth.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class LoginRequest { + + @Email + @NotBlank(message = "Email is required") private String email; + + @NotBlank(message = "Password is required") private String password; } diff --git a/src/main/java/com/podzilla/auth/dto/SignupRequest.java b/src/main/java/com/podzilla/auth/dto/SignupRequest.java index 7697899..380f4ff 100644 --- a/src/main/java/com/podzilla/auth/dto/SignupRequest.java +++ b/src/main/java/com/podzilla/auth/dto/SignupRequest.java @@ -1,13 +1,26 @@ package com.podzilla.auth.dto; import com.podzilla.mq.events.DeliveryAddress; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class SignupRequest { + @NotBlank(message = "Name is required") private String name; + + @Email + @NotBlank(message = "Email is required") private String email; + + @NotBlank(message = "Password is required") private String password; + + @NotBlank(message = "Mobile number is required") private String mobileNumber; + + @Valid private DeliveryAddress address; } diff --git a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java index 9258d09..0e4c109 100644 --- a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java +++ b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java @@ -1,11 +1,19 @@ package com.podzilla.auth.dto; import com.podzilla.mq.events.DeliveryAddress; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; import lombok.Data; @Data public class UpdateRequest { + + @NotBlank(message = "Name is required") private String name; + + @Valid private DeliveryAddress address; + + @NotBlank(message = "Mobile Number is required") private String mobileNumber; } diff --git a/src/main/java/com/podzilla/auth/model/Address.java b/src/main/java/com/podzilla/auth/model/Address.java index 9cf314d..d870709 100644 --- a/src/main/java/com/podzilla/auth/model/Address.java +++ b/src/main/java/com/podzilla/auth/model/Address.java @@ -35,18 +35,13 @@ public class Address { @JsonIgnore private User user; - @NotBlank(message = "Street is required") private String street; - @NotBlank(message = "City is required") private String city; - @NotBlank(message = "State is required") private String state; - @NotBlank(message = "Country is required") private String country; - @NotBlank(message = "Postal code is required") private String postalCode; } diff --git a/src/main/java/com/podzilla/auth/model/User.java b/src/main/java/com/podzilla/auth/model/User.java index 6daf2db..2a7356f 100644 --- a/src/main/java/com/podzilla/auth/model/User.java +++ b/src/main/java/com/podzilla/auth/model/User.java @@ -39,18 +39,14 @@ public class User { @GeneratedValue(strategy = GenerationType.UUID) private UUID id; - @NotBlank(message = "Name is required") private String name; - @NotBlank(message = "Email is required") @Email @Column(unique = true) private String email; - @NotBlank(message = "Password is required") private String password; - @NotBlank(message = "Mobile number is required") @Column(unique = true) private String mobileNumber; diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index 1fc6aa8..1f715fb 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -72,19 +72,15 @@ public String login(final LoginRequest loginRequest, public void registerAccount(final SignupRequest signupRequest) { checkUserLoggedIn("User cannot register while logged in."); - checkNotNullValidationException(signupRequest, - "Signup request cannot be null."); - checkNotNullValidationException(signupRequest.getEmail(), - "Email cannot be null."); - checkNotNullValidationException(signupRequest.getPassword(), - "Password cannot be null."); - checkNotNullValidationException(signupRequest.getName(), - "Name cannot be null."); - if (userRepository.existsByEmail(signupRequest.getEmail())) { throw new ValidationException("Email already in use."); } + if (userRepository.existsByMobileNumber( + signupRequest.getMobileNumber())) { + throw new ValidationException("Mobile number already in use."); + } + User account = User.builder() .name(signupRequest.getName()) @@ -92,6 +88,7 @@ public void registerAccount(final SignupRequest signupRequest) { .password( passwordEncoder.encode( signupRequest.getPassword())) + .mobileNumber(signupRequest.getMobileNumber()) .build(); Role role = roleRepository.findByErole(ERole.ROLE_USER).orElse(null); @@ -161,13 +158,6 @@ private void setRoleAndEmailInHeader( response.setHeader("X-User-Id", id); } - private void checkNotNullValidationException(final String value, - final String message) { - if (value == null || value.isEmpty()) { - throw new ValidationException(message); - } - } - private void checkNotNullValidationException(final Object value, final String message) { if (value == null) { diff --git a/src/test/java/com/podzilla/auth/controller/AuthenticationControllerTest.java b/src/test/java/com/podzilla/auth/controller/AuthenticationControllerTest.java index cc35333..31553e1 100644 --- a/src/test/java/com/podzilla/auth/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/podzilla/auth/controller/AuthenticationControllerTest.java @@ -3,12 +3,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.podzilla.auth.dto.LoginRequest; import com.podzilla.auth.dto.SignupRequest; +import com.podzilla.auth.model.Address; import com.podzilla.auth.model.ERole; import com.podzilla.auth.model.Role; import com.podzilla.auth.model.User; +import com.podzilla.auth.repository.AddressRepository; import com.podzilla.auth.repository.RoleRepository; import com.podzilla.auth.repository.UserRepository; import com.podzilla.auth.service.TokenService; // Assuming you have a JwtService +import com.podzilla.mq.events.DeliveryAddress; import jakarta.servlet.http.Cookie; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -66,11 +69,21 @@ void setUp() { userRole.setErole(ERole.ROLE_USER); roleRepository.save(userRole); + Address address = new Address(); + address.setStreet("123 Test St"); + address.setCity("Test City"); + address.setState("Test State"); + address.setCountry("Test Country"); + address.setPostalCode("12345"); + // Create a pre-existing user for login tests User user = new User(); user.setEmail(testUserEmail); user.setPassword(passwordEncoder.encode(testUserPassword)); user.setName("Test User"); // Assuming name is required or desired + user.setMobileNumber("1234567890"); + user.setAddress(address); + address.setUser(user); user.getRoles().add(userRole); userRepository.save(user); } @@ -87,6 +100,9 @@ void registerUser_shouldCreateNewUser_whenEmailIsNotTaken() throws Exception { signupRequest.setEmail("newuser@example.com"); signupRequest.setPassword("newpassword"); signupRequest.setName("New User"); + signupRequest.setMobileNumber("1234562137890"); + signupRequest.setAddress(new DeliveryAddress("456 New St", "New City", + "New State", "New Country", "54321")); mockMvc.perform(post("/auth/register") .contentType(MediaType.APPLICATION_JSON) diff --git a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java index 0a1dd7b..30d2b15 100644 --- a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java +++ b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java @@ -132,24 +132,6 @@ void registerAccount_shouldThrowValidationException_whenEmailExists() { verify(userRepository, never()).save(any(User.class)); } - @Test - void registerAccount_shouldThrowValidationException_whenPasswordIsEmpty() { - // Arrange - signupRequest.setPassword(""); // Empty password - - // Act & Assert - ValidationException exception = assertThrows(ValidationException.class, () -> { - authenticationService.registerAccount(signupRequest); - }); - - assertEquals("Validation error: Password cannot be null.", - exception.getMessage()); - verify(userRepository, never()).existsByEmail(anyString()); - verify(passwordEncoder, never()).encode(anyString()); - verify(roleRepository, never()).findByErole(any()); - verify(userRepository, never()).save(any(User.class)); - } - @Test void registerAccount_shouldHandleRoleNotFoundGracefully() { // Arrange - Simulate role not found in DB From 9e3546f5c6f51238ebe065b32b0f990ae3d9962c Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:28:21 +0300 Subject: [PATCH 3/4] Remove unused validation annotations from Address and User classes --- src/main/java/com/podzilla/auth/model/Address.java | 1 - src/main/java/com/podzilla/auth/model/User.java | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/main/java/com/podzilla/auth/model/Address.java b/src/main/java/com/podzilla/auth/model/Address.java index d870709..420f7b4 100644 --- a/src/main/java/com/podzilla/auth/model/Address.java +++ b/src/main/java/com/podzilla/auth/model/Address.java @@ -8,7 +8,6 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; diff --git a/src/main/java/com/podzilla/auth/model/User.java b/src/main/java/com/podzilla/auth/model/User.java index 2a7356f..d0b6ac5 100644 --- a/src/main/java/com/podzilla/auth/model/User.java +++ b/src/main/java/com/podzilla/auth/model/User.java @@ -19,8 +19,6 @@ import java.util.UUID; import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; From 572ef4cc8954338df2827119d75f4421310dd324 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:48:29 +0300 Subject: [PATCH 4/4] Add validation error handling in GlobalExceptionHandler and include validation starter dependency --- pom.xml | 4 +++ .../exception/GlobalExceptionHandler.java | 25 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/pom.xml b/pom.xml index 1a1032e..106ac42 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,10 @@ jakarta.validation-api 3.0.2 + + org.springframework.boot + spring-boot-starter-validation + org.mockito mockito-core diff --git a/src/main/java/com/podzilla/auth/exception/GlobalExceptionHandler.java b/src/main/java/com/podzilla/auth/exception/GlobalExceptionHandler.java index 19eb55d..bbef77c 100644 --- a/src/main/java/com/podzilla/auth/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/podzilla/auth/exception/GlobalExceptionHandler.java @@ -1,16 +1,41 @@ package com.podzilla.auth.exception; +import io.micrometer.common.lang.NonNull; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.AuthenticationException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; @RestControllerAdvice public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + @Override // Good practice to use @Override + protected ResponseEntity handleMethodArgumentNotValid( + final MethodArgumentNotValidException ex, + @NonNull final HttpHeaders headers, + @NonNull final HttpStatusCode status, + @NonNull final WebRequest request) { + + StringBuilder errorMessage = new StringBuilder("Validation failed: "); + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + errorMessage.append(error.getField()).append(": ") + .append(error.getDefaultMessage()).append("; "); + } + + ErrorResponse errorResponse = new ErrorResponse( + errorMessage.toString(), (HttpStatus) status); + + return new ResponseEntity<>(errorResponse, status); + + } @ExceptionHandler(AccessDeniedException.class) public ResponseEntity handleAccessDeniedException(