From 5e64056ceb79f609510da277395242ea71866328 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sat, 10 May 2025 00:41:41 +0300 Subject: [PATCH 01/27] Add Dockerfile and spring-boot-starter-actuator dependency with actuator endpoints --- Dockerfile | 9 +++++++++ pom.xml | 5 +++++ .../java/com/podzilla/auth/security/SecurityConfig.java | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e407c6c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +# Use the specified base image +FROM openjdk:25-ea-4-jdk-oraclelinux9 + +WORKDIR /app + +COPY target/auth-0.0.1-SNAPSHOT.jar /app/auth-0.0.1-SNAPSHOT.jar + +# Define the command to run your application +CMD [ "java", "-jar", "/app/auth-0.0.1-SNAPSHOT.jar" ] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 84202a5..1b564ac 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,11 @@ org.springframework.boot spring-boot-starter-data-jpa + + org.springframework.boot + spring-boot-starter-actuator + 3.4.5 + org.springframework.boot spring-boot-starter-data-redis diff --git a/src/main/java/com/podzilla/auth/security/SecurityConfig.java b/src/main/java/com/podzilla/auth/security/SecurityConfig.java index 8da1d06..3418366 100644 --- a/src/main/java/com/podzilla/auth/security/SecurityConfig.java +++ b/src/main/java/com/podzilla/auth/security/SecurityConfig.java @@ -51,6 +51,8 @@ SecurityFilterChain securityFilterChain(final HttpSecurity http) .requestMatchers("/swagger-ui/**", "/v3/api-docs/**") .permitAll() + .requestMatchers("/actuator/**") + .permitAll() .anyRequest().authenticated() ) .sessionManagement(s -> s From cbafcb7b49d6e1c416764515eeb001cc44cc0710 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sat, 10 May 2025 11:06:44 +0300 Subject: [PATCH 02/27] Add JitPack repository and mq-utils-lib dependency --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 1b564ac..477a44a 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,12 @@ 23 + + + jitpack.io + https://jitpack.io + + org.springframework.boot @@ -39,6 +45,11 @@ spring-boot-starter-actuator 3.4.5 + + com.github.Podzilla + mq-utils-lib + v1.1.0 + org.springframework.boot spring-boot-starter-data-redis From 54892fdc851117abf0484677b9e23a3dd92d9135 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sun, 11 May 2025 10:17:29 +0300 Subject: [PATCH 03/27] Refactor authentication to add user details in response headers --- .../controller/AuthenticationController.java | 8 +++---- .../auth/service/AuthenticationService.java | 22 +++++++++++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/podzilla/auth/controller/AuthenticationController.java b/src/main/java/com/podzilla/auth/controller/AuthenticationController.java index 1c45c26..0d1c85f 100644 --- a/src/main/java/com/podzilla/auth/controller/AuthenticationController.java +++ b/src/main/java/com/podzilla/auth/controller/AuthenticationController.java @@ -12,7 +12,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -113,9 +112,8 @@ public ResponseEntity refreshToken( responseCode = "200", description = "User details fetched successfully" ) - public UserDetails getCurrentUser() { - UserDetails userDetails = authenticationService.getCurrentUserDetails(); - LOGGER.info("Fetched details for user {}", userDetails.getUsername()); - return userDetails; + public void addUserDetailsInHeader(final HttpServletResponse response) { + authenticationService.addUserDetailsInHeader(response); + LOGGER.info("Fetching current user details and adding to header"); } } diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index 038a35c..f31fca6 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -122,19 +122,37 @@ public String refreshToken(final HttpServletRequest request, } } - public UserDetails getCurrentUserDetails() { + public void addUserDetailsInHeader( + final HttpServletResponse response) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { - return (UserDetails) principal; + 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()); } else { throw new InvalidActionException( "User details not saved correctly."); } } + private void setRoleAndEmailInHeader( + final HttpServletResponse response, + final String email, + final String roles) { + response.setHeader("X-User-Email", email); + response.setHeader("X-User-Roles", roles); + } + private void checkNotNullValidationException(final String value, final String message) { if (value == null || value.isEmpty()) { From 0308c35844a984474693300d708f38f78277b1b1 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sun, 11 May 2025 13:40:48 +0300 Subject: [PATCH 04/27] Update component scanning and dependency version for podzilla-utils-lib --- pom.xml | 9 ++------- src/main/java/com/podzilla/auth/AuthApplication.java | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 477a44a..c06b865 100644 --- a/pom.xml +++ b/pom.xml @@ -40,15 +40,10 @@ org.springframework.boot spring-boot-starter-data-jpa - - org.springframework.boot - spring-boot-starter-actuator - 3.4.5 - com.github.Podzilla - mq-utils-lib - v1.1.0 + podzilla-utils-lib + v1.1.5 org.springframework.boot diff --git a/src/main/java/com/podzilla/auth/AuthApplication.java b/src/main/java/com/podzilla/auth/AuthApplication.java index 920bdd3..1cca17a 100644 --- a/src/main/java/com/podzilla/auth/AuthApplication.java +++ b/src/main/java/com/podzilla/auth/AuthApplication.java @@ -3,9 +3,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @EnableCaching +@ComponentScan(basePackages = { "com.podzilla" }) public class AuthApplication { public static void main(final String[] args) { From 3e2e72fef01e41e2c171d4768b685ab6ef3931b0 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 15:40:19 +0300 Subject: [PATCH 05/27] 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 06/27] 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 07/27] 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 08/27] 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( From f00e543cd1de250dbac15e0adcac07245a09e961 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 18:34:02 +0300 Subject: [PATCH 09/27] Remove validation annotations from UpdateRequest and adjust UserController to accept raw request body --- .../java/com/podzilla/auth/controller/UserController.java | 3 +-- src/main/java/com/podzilla/auth/dto/UpdateRequest.java | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/com/podzilla/auth/controller/UserController.java b/src/main/java/com/podzilla/auth/controller/UserController.java index 8c49507..d642efc 100644 --- a/src/main/java/com/podzilla/auth/controller/UserController.java +++ b/src/main/java/com/podzilla/auth/controller/UserController.java @@ -5,7 +5,6 @@ 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.RequestMapping; @@ -32,7 +31,7 @@ public UserController(final UserService userService) { description = "Allows user to update their name.") @ApiResponse(responseCode = "200", description = "User profile updated successfully") - public void updateProfile(@Valid @RequestBody final UpdateRequest + public void updateProfile(@RequestBody final UpdateRequest updateRequest) { LOGGER.debug("Received updateProfile request"); userService.updateUserProfile(updateRequest); diff --git a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java index 0e4c109..9258d09 100644 --- a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java +++ b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java @@ -1,19 +1,11 @@ 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; } From 8791fb7a82ba54df1f1946ed432ea347391ce1da Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sat, 10 May 2025 00:41:41 +0300 Subject: [PATCH 10/27] Add Dockerfile and spring-boot-starter-actuator dependency with actuator endpoints --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index c06b865..620638c 100644 --- a/pom.xml +++ b/pom.xml @@ -45,6 +45,11 @@ podzilla-utils-lib v1.1.5 + + org.springframework.boot + spring-boot-starter-actuator + 3.4.5 + org.springframework.boot spring-boot-starter-data-redis From 28666106e59484aea6c446610a7e3b52e5c854e7 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sat, 10 May 2025 11:06:44 +0300 Subject: [PATCH 11/27] Add JitPack repository and mq-utils-lib dependency --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index 620638c..9b604cf 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,11 @@ spring-boot-starter-actuator 3.4.5 + + com.github.Podzilla + mq-utils-lib + v1.1.0 + org.springframework.boot spring-boot-starter-data-redis From 5e708c33801936bd9831cb560bdf3922e1f3d555 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Sun, 11 May 2025 13:40:48 +0300 Subject: [PATCH 12/27] Update component scanning and dependency version for podzilla-utils-lib # Conflicts: # pom.xml --- pom.xml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9b604cf..cf08335 100644 --- a/pom.xml +++ b/pom.xml @@ -40,11 +40,6 @@ org.springframework.boot spring-boot-starter-data-jpa - - com.github.Podzilla - podzilla-utils-lib - v1.1.5 - org.springframework.boot spring-boot-starter-actuator @@ -52,8 +47,8 @@ com.github.Podzilla - mq-utils-lib - v1.1.0 + podzilla-utils-lib + v1.1.5 org.springframework.boot From 33fa149ff263a71da1d235d57c2ca8b129163ad5 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 15:40:19 +0300 Subject: [PATCH 13/27] 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 cf08335..a6b5d52 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,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 566e1067b6d4b5040e17f9e9ca4ec7581213d9d2 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:27:27 +0300 Subject: [PATCH 14/27] 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 05b2e6b674195c3c6eb106f92b45f9cd7cf8c89b Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:28:21 +0300 Subject: [PATCH 15/27] 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 7fe5c1e49c627f1d8579aeb4aea91b962ade9836 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 17:48:29 +0300 Subject: [PATCH 16/27] 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 a6b5d52..3e4f30a 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,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( From d6c1a55afd62c344d645e382dbbc659fed6f5c6e Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 18:34:02 +0300 Subject: [PATCH 17/27] Remove validation annotations from UpdateRequest and adjust UserController to accept raw request body --- .../java/com/podzilla/auth/controller/UserController.java | 3 +-- src/main/java/com/podzilla/auth/dto/UpdateRequest.java | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/main/java/com/podzilla/auth/controller/UserController.java b/src/main/java/com/podzilla/auth/controller/UserController.java index 8c49507..d642efc 100644 --- a/src/main/java/com/podzilla/auth/controller/UserController.java +++ b/src/main/java/com/podzilla/auth/controller/UserController.java @@ -5,7 +5,6 @@ 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.RequestMapping; @@ -32,7 +31,7 @@ public UserController(final UserService userService) { description = "Allows user to update their name.") @ApiResponse(responseCode = "200", description = "User profile updated successfully") - public void updateProfile(@Valid @RequestBody final UpdateRequest + public void updateProfile(@RequestBody final UpdateRequest updateRequest) { LOGGER.debug("Received updateProfile request"); userService.updateUserProfile(updateRequest); diff --git a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java index 0e4c109..9258d09 100644 --- a/src/main/java/com/podzilla/auth/dto/UpdateRequest.java +++ b/src/main/java/com/podzilla/auth/dto/UpdateRequest.java @@ -1,19 +1,11 @@ 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; } From 6d47d4da733f08ea51dad4697338d3bb611a6bb2 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 18:58:02 +0300 Subject: [PATCH 18/27] Remove user unit tests --- .../auth/service/UserServiceTest.java | 81 ------------------- 1 file changed, 81 deletions(-) delete mode 100644 src/test/java/com/podzilla/auth/service/UserServiceTest.java diff --git a/src/test/java/com/podzilla/auth/service/UserServiceTest.java b/src/test/java/com/podzilla/auth/service/UserServiceTest.java deleted file mode 100644 index e6e5ba2..0000000 --- a/src/test/java/com/podzilla/auth/service/UserServiceTest.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.podzilla.auth.service; - -import com.podzilla.auth.exception.NotFoundException; -import com.podzilla.auth.model.User; -import com.podzilla.auth.repository.UserRepository; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - - -import java.util.Optional; -import java.util.UUID; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; - -@ExtendWith(MockitoExtension.class) -class UserServiceTest { - - @Mock - private UserRepository userRepository; - - @InjectMocks - private UserService userService; - - private UUID userId; - private User existingUser; - - @BeforeEach - void setUp() { - userId = UUID.randomUUID(); - existingUser = User.builder() - .id(userId) - .name("Old Name") - .email("old@example.com") - .build(); - } - - @Test - void updateUserProfile_shouldUpdateName_whenUserExists() { - // Arrange - String newName = "New Name"; - when(userRepository.findById(userId)).thenReturn(Optional.of(existingUser)); - - // Act - userService.updateUserProfile(userId, newName); - - // Assert - verify(userRepository).findById(userId); - - - ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); - verify(userRepository).save(userCaptor.capture()); - User savedUser = userCaptor.getValue(); - - assertEquals(newName, savedUser.getName()); - assertEquals(userId, savedUser.getId()); - } - - @Test - void updateUserProfile_shouldThrowNotFoundException_whenUserDoesNotExist() { - // Arrange - when(userRepository.findById(userId)).thenReturn(Optional.empty()); - - // Act & Assert - NotFoundException exception = assertThrows(NotFoundException.class, () -> { - userService.updateUserProfile(userId, "New Name"); - }); - - assertEquals("Not Found: User with id " + userId + " does not exist.", exception.getMessage()); - verify(userRepository).findById(userId); - verify(userRepository, never()).save(any(User.class)); - } - - -} \ No newline at end of file From 14fc45f0845874a43beafd5446fea15601617e16 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 19:04:31 +0300 Subject: [PATCH 19/27] Fix formatting issue by adding a newline at the end of AuthenticationService.java --- .../java/com/podzilla/auth/service/AuthenticationService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index 9e4d170..1f715fb 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -122,6 +122,7 @@ public String refreshToken(final HttpServletRequest request, public void addUserDetailsInHeader( final HttpServletResponse response) { + CustomUserDetails userDetails = getCurrentUserDetails(); String email = userDetails.getUsername(); StringBuilder roles = new StringBuilder(); @@ -177,4 +178,4 @@ private void checkUserLoggedIn(final String message) { throw new InvalidActionException(message); } } -} \ No newline at end of file +} From 730a35442510e2adaa5059eea63d1ec722b84e5b Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 19:11:22 +0300 Subject: [PATCH 20/27] Add DatabaseSeeder to initialize default roles in the database --- .../podzilla/auth/seeder/DatabaseSeeder.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java diff --git a/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java new file mode 100644 index 0000000..10f5af1 --- /dev/null +++ b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java @@ -0,0 +1,22 @@ +package com.podzilla.auth.seeder; + +import com.podzilla.auth.model.ERole; +import com.podzilla.auth.model.Role; +import com.podzilla.auth.repository.RoleRepository; +import org.springframework.boot.CommandLineRunner; + + +public class DatabaseSeeder implements CommandLineRunner { + + private final RoleRepository roleRepository; + + public DatabaseSeeder(final RoleRepository roleRepository) { + this.roleRepository = roleRepository; + } + + @Override + public void run(final String... args) throws Exception { + roleRepository.save(new Role(ERole.ROLE_USER)); + roleRepository.save(new Role(ERole.ROLE_ADMIN)); + } +} From a764e4d8702e05a1cf12aff058cfdda32fb6b8b9 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 19:18:41 +0300 Subject: [PATCH 21/27] Add @Component annotation to DatabaseSeeder for Spring context management --- src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java index 10f5af1..b0bdd9c 100644 --- a/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java +++ b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java @@ -4,8 +4,9 @@ import com.podzilla.auth.model.Role; import com.podzilla.auth.repository.RoleRepository; import org.springframework.boot.CommandLineRunner; +import org.springframework.stereotype.Component; - +@Component public class DatabaseSeeder implements CommandLineRunner { private final RoleRepository roleRepository; From fbc8ddca333f03ed77480e456319a966bb51ff36 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 20:05:21 +0300 Subject: [PATCH 22/27] Add address handling in user signup process --- .../podzilla/auth/service/AuthenticationService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index 1f715fb..f929823 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -5,6 +5,7 @@ import com.podzilla.auth.dto.SignupRequest; import com.podzilla.auth.exception.InvalidActionException; import com.podzilla.auth.exception.ValidationException; +import com.podzilla.auth.model.Address; import com.podzilla.auth.model.ERole; import com.podzilla.auth.model.Role; import com.podzilla.auth.model.User; @@ -81,6 +82,14 @@ public void registerAccount(final SignupRequest signupRequest) { throw new ValidationException("Mobile number already in use."); } + Address address = Address.builder() + .street(signupRequest.getAddress().getStreet()) + .city(signupRequest.getAddress().getCity()) + .state(signupRequest.getAddress().getState()) + .country(signupRequest.getAddress().getCountry()) + .postalCode(signupRequest.getAddress().getPostalCode()) + .build(); + User account = User.builder() .name(signupRequest.getName()) @@ -89,7 +98,10 @@ public void registerAccount(final SignupRequest signupRequest) { passwordEncoder.encode( signupRequest.getPassword())) .mobileNumber(signupRequest.getMobileNumber()) + .address(address) .build(); + address.setUser(account); + Role role = roleRepository.findByErole(ERole.ROLE_USER).orElse(null); checkNotNullValidationException(role, "Role_USER not found."); From ef89fa94a51c06f4355424833d70b0d1ab957306 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 20:16:53 +0300 Subject: [PATCH 23/27] Add delivery address handling to signup request in AuthenticationServiceTest --- .../auth/service/AuthenticationServiceTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java index 30d2b15..9f0c90d 100644 --- a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java +++ b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java @@ -4,11 +4,13 @@ import com.podzilla.auth.dto.LoginRequest; import com.podzilla.auth.dto.SignupRequest; import com.podzilla.auth.exception.ValidationException; +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.RoleRepository; import com.podzilla.auth.repository.UserRepository; +import com.podzilla.mq.events.DeliveryAddress; import jakarta.servlet.http.HttpServletRequest; // Added import import jakarta.servlet.http.HttpServletResponse; import org.junit.jupiter.api.BeforeEach; @@ -68,6 +70,14 @@ void setUp() { signupRequest.setName("Test User"); signupRequest.setEmail("test@example.com"); signupRequest.setPassword("password123"); + signupRequest.setMobileNumber("1234567890"); + DeliveryAddress deliveryAddress = new DeliveryAddress(); + deliveryAddress.setStreet("123 Test St"); + deliveryAddress.setCity("Test City"); + deliveryAddress.setState("Test State"); + deliveryAddress.setCountry("Test Country"); + deliveryAddress.setPostalCode("12345"); + signupRequest.setAddress(deliveryAddress); loginRequest = new LoginRequest(); loginRequest.setEmail("test@example.com"); From 90c746c058937fe36e330f48ca80466ccd9241d2 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 20:49:01 +0300 Subject: [PATCH 24/27] Enhance UserDetailsRequest with Lombok annotations for improved code simplicity --- src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java b/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java index 23c4491..6f75b6f 100644 --- a/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java +++ b/src/main/java/com/podzilla/auth/dto/UserDetailsRequest.java @@ -1,9 +1,15 @@ package com.podzilla.auth.dto; import com.podzilla.mq.events.DeliveryAddress; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; @Builder +@NoArgsConstructor +@AllArgsConstructor +@Data public class UserDetailsRequest { private String email; private String name; From 50c8cf554e0b719ccae29d943bd36024500b1197 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 22:12:58 +0300 Subject: [PATCH 25/27] Add AddCourierRequest DTO, implement courier registration in AdminService and add event publisher in registration courier/customer. --- .../auth/controller/AdminController.java | 22 ++++++++++- .../podzilla/auth/dto/AddCourierRequest.java | 21 +++++++++++ .../java/com/podzilla/auth/model/ERole.java | 3 +- .../podzilla/auth/seeder/DatabaseSeeder.java | 1 + .../podzilla/auth/service/AdminService.java | 37 ++++++++++++++++++- .../auth/service/AuthenticationService.java | 12 +++++- 6 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/podzilla/auth/dto/AddCourierRequest.java diff --git a/src/main/java/com/podzilla/auth/controller/AdminController.java b/src/main/java/com/podzilla/auth/controller/AdminController.java index 0976764..fbefc3e 100644 --- a/src/main/java/com/podzilla/auth/controller/AdminController.java +++ b/src/main/java/com/podzilla/auth/controller/AdminController.java @@ -1,5 +1,6 @@ package com.podzilla.auth.controller; +import com.podzilla.auth.dto.AddCourierRequest; import com.podzilla.auth.model.User; import com.podzilla.auth.service.AdminService; import io.swagger.v3.oas.annotations.Operation; @@ -8,12 +9,15 @@ 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.GetMapping; import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; +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.PostMapping; + import java.util.List; import java.util.UUID; @@ -71,4 +75,18 @@ public void deleteUser( LOGGER.debug("Admin requested to delete user with userId={}", userId); adminService.deleteUser(userId); } + + @PostMapping("/courier") + @Operation(summary = "Add a new courier", + description = "Allows an admin to add a new courier to the system.") + @ApiResponse(responseCode = "200", + description = "Courier added successfully") + public void addCourier( + @Parameter(description = "Courier details") + @RequestBody final AddCourierRequest addCourierRequest) { + + LOGGER.debug("Admin requested to add a new courier with details={}", + addCourierRequest); + adminService.addCourier(addCourierRequest); + } } diff --git a/src/main/java/com/podzilla/auth/dto/AddCourierRequest.java b/src/main/java/com/podzilla/auth/dto/AddCourierRequest.java new file mode 100644 index 0000000..0336544 --- /dev/null +++ b/src/main/java/com/podzilla/auth/dto/AddCourierRequest.java @@ -0,0 +1,21 @@ +package com.podzilla.auth.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class AddCourierRequest { + @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; +} diff --git a/src/main/java/com/podzilla/auth/model/ERole.java b/src/main/java/com/podzilla/auth/model/ERole.java index e877932..40b0494 100644 --- a/src/main/java/com/podzilla/auth/model/ERole.java +++ b/src/main/java/com/podzilla/auth/model/ERole.java @@ -2,5 +2,6 @@ public enum ERole { ROLE_USER, - ROLE_ADMIN + ROLE_ADMIN, + ROLE_COURIER } diff --git a/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java index b0bdd9c..cbb6d65 100644 --- a/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java +++ b/src/main/java/com/podzilla/auth/seeder/DatabaseSeeder.java @@ -19,5 +19,6 @@ public DatabaseSeeder(final RoleRepository roleRepository) { public void run(final String... args) throws Exception { roleRepository.save(new Role(ERole.ROLE_USER)); roleRepository.save(new Role(ERole.ROLE_ADMIN)); + roleRepository.save(new Role(ERole.ROLE_COURIER)); } } diff --git a/src/main/java/com/podzilla/auth/service/AdminService.java b/src/main/java/com/podzilla/auth/service/AdminService.java index 3654556..8b20acc 100644 --- a/src/main/java/com/podzilla/auth/service/AdminService.java +++ b/src/main/java/com/podzilla/auth/service/AdminService.java @@ -1,13 +1,21 @@ package com.podzilla.auth.service; +import com.podzilla.auth.dto.AddCourierRequest; import com.podzilla.auth.dto.CustomGrantedAuthority; import com.podzilla.auth.dto.CustomUserDetails; +import com.podzilla.auth.model.ERole; import com.podzilla.auth.model.User; +import com.podzilla.auth.repository.RoleRepository; import com.podzilla.auth.repository.UserRepository; +import com.podzilla.mq.EventPublisher; +import com.podzilla.mq.EventsConstants; +import com.podzilla.mq.events.CourierRegisteredEvent; +import com.podzilla.mq.events.CustomerRegisteredEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,13 +33,22 @@ public class AdminService { private final UserRepository userRepository; private final UserService userService; private final CacheService cacheService; + private final PasswordEncoder passwordEncoder; + private final RoleRepository roleRepository; + private final EventPublisher eventPublisher; public AdminService(final UserRepository userRepository, final UserService userService, - final CacheService cacheService) { + final CacheService cacheService, + final PasswordEncoder passwordEncoder, + final RoleRepository roleRepository, + final EventPublisher eventPublisher) { this.userRepository = userRepository; this.userService = userService; this.cacheService = cacheService; + this.passwordEncoder = passwordEncoder; + this.roleRepository = roleRepository; + this.eventPublisher = eventPublisher; } public List getUsers() { @@ -61,6 +78,24 @@ public void deleteUser(final UUID userId) { LOGGER.debug("User deleted successfully with userId={}", userId); } + @Transactional + public void addCourier(final AddCourierRequest request) { + User user = new User(); + user.setName(request.getName()); + user.setEmail(request.getEmail()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setMobileNumber(request.getMobileNumber()); + user.setEnabled(true); + user.setRoles(Set.of(roleRepository.findByErole(ERole.ROLE_COURIER) + .orElseThrow(() -> new RuntimeException("Role not found")))); + + userRepository.save(user); + + eventPublisher.publishEvent(EventsConstants.COURIER_REGISTERED, + new CourierRegisteredEvent(user.getId().toString(), + user.getName(), user.getMobileNumber())); + } + public static UserDetails getUserDetails(final User user) { Set authorities = user .getRoles() diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index f929823..82e47b1 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -11,6 +11,9 @@ import com.podzilla.auth.model.User; import com.podzilla.auth.repository.RoleRepository; import com.podzilla.auth.repository.UserRepository; +import com.podzilla.mq.EventPublisher; +import com.podzilla.mq.EventsConstants; +import com.podzilla.mq.events.CustomerRegisteredEvent; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.access.AccessDeniedException; @@ -33,18 +36,21 @@ public class AuthenticationService { private final UserRepository userRepository; private final TokenService tokenService; private final RoleRepository roleRepository; + private final EventPublisher eventPublisher; public AuthenticationService( final AuthenticationManager authenticationManager, final PasswordEncoder passwordEncoder, final UserRepository userRepository, final TokenService tokenService, - final RoleRepository roleRepository) { + final RoleRepository roleRepository, + final EventPublisher eventPublisher) { this.authenticationManager = authenticationManager; this.passwordEncoder = passwordEncoder; this.userRepository = userRepository; this.tokenService = tokenService; this.roleRepository = roleRepository; + this.eventPublisher = eventPublisher; } public String login(final LoginRequest loginRequest, @@ -108,6 +114,10 @@ public void registerAccount(final SignupRequest signupRequest) { account.setRoles(Collections.singleton(role)); userRepository.save(account); + + eventPublisher.publishEvent(EventsConstants.CUSTOMER_REGISTERED, + new CustomerRegisteredEvent(account.getId().toString(), + account.getName())); } public void logoutUser( From e24ebd0c48b352ce0f37b0b16d0c653dcc4607b6 Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Wed, 14 May 2025 22:13:27 +0300 Subject: [PATCH 26/27] Remove unused CustomerRegisteredEvent import from AdminService --- src/main/java/com/podzilla/auth/service/AdminService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/podzilla/auth/service/AdminService.java b/src/main/java/com/podzilla/auth/service/AdminService.java index 8b20acc..a642552 100644 --- a/src/main/java/com/podzilla/auth/service/AdminService.java +++ b/src/main/java/com/podzilla/auth/service/AdminService.java @@ -10,7 +10,6 @@ import com.podzilla.mq.EventPublisher; import com.podzilla.mq.EventsConstants; import com.podzilla.mq.events.CourierRegisteredEvent; -import com.podzilla.mq.events.CustomerRegisteredEvent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.GrantedAuthority; From d272a67190a25e064ac40975dc785bfa5b968cbb Mon Sep 17 00:00:00 2001 From: Nour Eldien Ayman Date: Thu, 15 May 2025 12:49:34 +0300 Subject: [PATCH 27/27] Fix account registration to return saved user and mock event publishing in tests --- .../com/podzilla/auth/service/AuthenticationService.java | 2 +- .../podzilla/auth/service/AuthenticationServiceTest.java | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/podzilla/auth/service/AuthenticationService.java b/src/main/java/com/podzilla/auth/service/AuthenticationService.java index 82e47b1..f48c5a9 100644 --- a/src/main/java/com/podzilla/auth/service/AuthenticationService.java +++ b/src/main/java/com/podzilla/auth/service/AuthenticationService.java @@ -113,7 +113,7 @@ public void registerAccount(final SignupRequest signupRequest) { checkNotNullValidationException(role, "Role_USER not found."); account.setRoles(Collections.singleton(role)); - userRepository.save(account); + account = userRepository.save(account); eventPublisher.publishEvent(EventsConstants.CUSTOMER_REGISTERED, new CustomerRegisteredEvent(account.getId().toString(), diff --git a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java index 9f0c90d..b111cf2 100644 --- a/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java +++ b/src/test/java/com/podzilla/auth/service/AuthenticationServiceTest.java @@ -10,6 +10,10 @@ import com.podzilla.auth.model.User; import com.podzilla.auth.repository.RoleRepository; import com.podzilla.auth.repository.UserRepository; +import com.podzilla.mq.EventPublisher; +import com.podzilla.mq.EventsConstants; +import com.podzilla.mq.events.BaseEvent; +import com.podzilla.mq.events.CustomerRegisteredEvent; import com.podzilla.mq.events.DeliveryAddress; import jakarta.servlet.http.HttpServletRequest; // Added import import jakarta.servlet.http.HttpServletResponse; @@ -55,6 +59,8 @@ class AuthenticationServiceTest { private HttpServletResponse httpServletResponse; @Mock // Added mock for HttpServletRequest private HttpServletRequest httpServletRequest; + @Mock + private EventPublisher eventPublisher; @InjectMocks private AuthenticationService authenticationService; @@ -104,6 +110,9 @@ void registerAccount_shouldSaveUser_whenEmailNotExistsAndPasswordNotEmpty() { when(passwordEncoder.encode(signupRequest.getPassword())).thenReturn("encodedPassword"); when(roleRepository.findByErole(ERole.ROLE_USER)).thenReturn(Optional.of(userRole)); when(userRepository.save(any(User.class))).thenReturn(user); // Return the saved user + // mock publish event in event publisher void method + doNothing().when(eventPublisher).publishEvent(any(EventsConstants.EventMetadata.class), + any(CustomerRegisteredEvent.class)); // Act authenticationService.registerAccount(signupRequest);