From 3771ec4ba4dc8ea322ec451ebee7acc8fdd0458f Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Mon, 4 Sep 2023 18:36:27 +0400 Subject: [PATCH 1/6] Fixed repeat registration case with same user --- .../handler/GlobalExceptionHandler.java | 20 +++++--- .../onlinestore/user/api/UserService.java | 48 ++++++++++++++++++- .../onlinestore/user/entity/UserEntity.java | 4 +- .../UserAlreadyRegisteredException.java | 15 ++++++ .../handler/UserExceptionHandler.java | 26 ++++++++++ .../user/repository/UserRepository.java | 4 ++ ...7.2023.part4.create-user-details-table.sql | 1 + 7 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java create mode 100644 src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java diff --git a/src/main/java/com/zufar/onlinestore/common/exception/handler/GlobalExceptionHandler.java b/src/main/java/com/zufar/onlinestore/common/exception/handler/GlobalExceptionHandler.java index 9a457257..20cc755b 100644 --- a/src/main/java/com/zufar/onlinestore/common/exception/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/zufar/onlinestore/common/exception/handler/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.zufar.onlinestore.common.exception.handler; import com.zufar.onlinestore.common.response.ApiResponse; +import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import lombok.extern.slf4j.Slf4j; import org.apache.logging.log4j.util.Strings; import org.springframework.context.support.DefaultMessageSourceResolvable; @@ -40,12 +41,19 @@ protected ApiResponse buildResponse(Exception exception, HttpStatus httpSt } private List collectErrorMessages(Exception exception) { - return exception instanceof MethodArgumentNotValidException methodArgumentNotValidException ? - methodArgumentNotValidException - .getBindingResult() - .getAllErrors().stream() - .map(DefaultMessageSourceResolvable::getDefaultMessage) - .toList() : List.of(exception.getMessage()); + if (exception instanceof UserAlreadyRegisteredException userAlreadyRegisteredException) { + return userAlreadyRegisteredException.getErrors(); + } + + if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) { + return methodArgumentNotValidException + .getBindingResult() + .getAllErrors().stream() + .map(DefaultMessageSourceResolvable::getDefaultMessage) + .toList(); + } + + return List.of(exception.getMessage()); } private String buildErrorDescription(Exception exception) { diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index c10aac45..bac12f00 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -2,15 +2,17 @@ import com.zufar.onlinestore.user.converter.UserDtoConverter; import com.zufar.onlinestore.user.dto.UserDto; +import com.zufar.onlinestore.user.entity.Authority; import com.zufar.onlinestore.user.entity.UserEntity; +import com.zufar.onlinestore.user.entity.UserGrantedAuthority; +import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import com.zufar.onlinestore.user.exception.UserNotFoundException; import com.zufar.onlinestore.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.Optional; -import java.util.UUID; +import java.util.*; @Slf4j @Service @@ -23,7 +25,25 @@ public class UserService implements UserApi { @Override public UserDto saveUser(final UserDto userDto) { UserEntity userEntity = userDtoConverter.toEntity(userDto); + + List errors = new ArrayList<>(); + + if (!isEmailUnique(userEntity.getUserId(), userEntity.getEmail())) { + errors.add(String.format("User with email = %s is already registered", userEntity.getEmail())); + } + + if (!isUsernameUnique(userEntity.getUserId(), userEntity.getUsername())) { + errors.add(String.format("User with username = %s is already registered", userEntity.getUsername())); + } + + if (!errors.isEmpty()) { + + throw new UserAlreadyRegisteredException(errors); + + } + UserEntity userEntityWithId = userCrudRepository.save(userEntity); + return userDtoConverter.toDto(userEntityWithId); } @@ -36,4 +56,28 @@ public UserDto getUserById(final UUID userId) throws UserNotFoundException { } return userDtoConverter.toDto(userEntity.get()); } + + public boolean isEmailUnique(UUID id, String email) { + Optional userByEmail = userCrudRepository.findByEmail(email); + + if (userByEmail.isEmpty()) { + return true; + } + + return id == null || userByEmail.map(UserEntity::getUserId).filter(userId -> userId == id).isPresent(); + } + + public boolean isUsernameUnique(UUID id, String username) { + UserEntity userByUsername = userCrudRepository.findUserByUsername(username); + + if (userByUsername == null) { + return true; + } + + if (id == null) { + return false; + } else { + return userByUsername.getUserId() == id; + } + } } diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java index 26bfbd41..b920584c 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java @@ -44,10 +44,10 @@ public class UserEntity implements UserDetails { @Column(name = "last_name", nullable = false) private String lastName; - @Column(name = "user_name", nullable = false) + @Column(name = "user_name", nullable = false, unique = true) private String username; - @Column(name = "email", nullable = false) + @Column(name = "email", nullable = false, unique = true) private String email; @Column(name = "password", nullable = false) diff --git a/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java b/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java new file mode 100644 index 00000000..f7bc3b91 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java @@ -0,0 +1,15 @@ +package com.zufar.onlinestore.user.exception; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class UserAlreadyRegisteredException extends RuntimeException { + + private final List errors; + + public UserAlreadyRegisteredException(List errors) { + this.errors = errors; + } +} diff --git a/src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java b/src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java new file mode 100644 index 00000000..9d8673b6 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java @@ -0,0 +1,26 @@ +package com.zufar.onlinestore.user.exception.handler; + +import com.zufar.onlinestore.common.exception.handler.GlobalExceptionHandler; +import com.zufar.onlinestore.common.response.ApiResponse; +import com.zufar.onlinestore.payment.exception.PaymentNotFoundException; +import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +@RestControllerAdvice +public class UserExceptionHandler extends GlobalExceptionHandler { + + @ExceptionHandler(UserAlreadyRegisteredException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiResponse handleUserAlreadyRegisteredException(final UserAlreadyRegisteredException exception) { + ApiResponse apiResponse = buildResponse(exception, HttpStatus.BAD_REQUEST); + log.error("Handle user already registered exception: failed: messages: {}, description: {}.", + apiResponse.messages(), apiResponse.description()); + + return apiResponse; + } +} diff --git a/src/main/java/com/zufar/onlinestore/user/repository/UserRepository.java b/src/main/java/com/zufar/onlinestore/user/repository/UserRepository.java index 9e64c2a4..5a4ccfde 100644 --- a/src/main/java/com/zufar/onlinestore/user/repository/UserRepository.java +++ b/src/main/java/com/zufar/onlinestore/user/repository/UserRepository.java @@ -3,9 +3,13 @@ import com.zufar.onlinestore.user.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; import java.util.UUID; public interface UserRepository extends JpaRepository { UserEntity findUserByUsername(String username); + + Optional findByEmail(String email); + } diff --git a/src/main/resources/db/changelog/version-1.0/28.07.2023.part4.create-user-details-table.sql b/src/main/resources/db/changelog/version-1.0/28.07.2023.part4.create-user-details-table.sql index 4ce32170..ba0631d4 100644 --- a/src/main/resources/db/changelog/version-1.0/28.07.2023.part4.create-user-details-table.sql +++ b/src/main/resources/db/changelog/version-1.0/28.07.2023.part4.create-user-details-table.sql @@ -11,6 +11,7 @@ CREATE TABLE user_details account_non_locked BOOLEAN NOT NULL, credentials_non_expired BOOLEAN NOT NULL, enabled BOOLEAN NOT NULL, + UNIQUE (user_name, email), CONSTRAINT fk_address FOREIGN KEY (address_id) REFERENCES address (id) From 757e431cbc29fbf3795dd5d55f5dbd00472e2339 Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Mon, 4 Sep 2023 19:59:32 +0400 Subject: [PATCH 2/6] Fixed Hibernate issue with authorities saving --- .../user/api/AuthorityService.java | 19 +++++++++++ .../onlinestore/user/api/UserService.java | 1 + .../user/converter/UserDtoConverter.java | 32 ------------------- .../onlinestore/user/entity/UserEntity.java | 24 ++++++++++++-- .../user/entity/UserGrantedAuthority.java | 6 ++-- 5 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/zufar/onlinestore/user/api/AuthorityService.java diff --git a/src/main/java/com/zufar/onlinestore/user/api/AuthorityService.java b/src/main/java/com/zufar/onlinestore/user/api/AuthorityService.java new file mode 100644 index 00000000..bbc739ad --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/api/AuthorityService.java @@ -0,0 +1,19 @@ +package com.zufar.onlinestore.user.api; + +import com.zufar.onlinestore.user.entity.Authority; +import com.zufar.onlinestore.user.entity.UserEntity; +import com.zufar.onlinestore.user.entity.UserGrantedAuthority; +import org.springframework.stereotype.Service; + +@Service +public class AuthorityService { + + public void setDefaultAuthority(UserEntity savedUserEntity) { + UserGrantedAuthority defaultAuthority = UserGrantedAuthority + .builder() + .authority(Authority.USER) + .build(); + + savedUserEntity.addAuthority(defaultAuthority); + } +} diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index bac12f00..05aa74c5 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -21,6 +21,7 @@ public class UserService implements UserApi { private final UserRepository userCrudRepository; private final UserDtoConverter userDtoConverter; + private final AuthorityService authorityService; @Override public UserDto saveUser(final UserDto userDto) { diff --git a/src/main/java/com/zufar/onlinestore/user/converter/UserDtoConverter.java b/src/main/java/com/zufar/onlinestore/user/converter/UserDtoConverter.java index 6740d7d1..d66c51a4 100644 --- a/src/main/java/com/zufar/onlinestore/user/converter/UserDtoConverter.java +++ b/src/main/java/com/zufar/onlinestore/user/converter/UserDtoConverter.java @@ -1,16 +1,10 @@ package com.zufar.onlinestore.user.converter; import com.zufar.onlinestore.user.dto.UserDto; -import com.zufar.onlinestore.user.entity.Authority; import com.zufar.onlinestore.user.entity.UserEntity; -import com.zufar.onlinestore.user.entity.UserGrantedAuthority; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.Named; - -import java.util.Collections; -import java.util.Set; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = AddressDtoConverter.class) public interface UserDtoConverter { @@ -23,32 +17,6 @@ public interface UserDtoConverter { @Mapping(target = "credentialsNonExpired", constant = "true") @Mapping(target = "enabled", constant = "true") @Mapping(target = "address", source = "dto.address", qualifiedByName = "toAddress") - @Mapping(target = "authorities", source = "dto", qualifiedByName = "createAuthorities") UserEntity toEntity(final UserDto dto); - @Named("createAuthorities") - @Mapping(target = "address", source = "dto.address", qualifiedByName = "toAddress") - default Set createAuthorities(UserDto dto) { - UserEntity userEntity = UserEntity.builder() - .firstName(dto.firstName()) - .lastName(dto.lastName()) - .email(dto.email()) - .username(dto.username()) - .password(dto.password()) - .accountNonExpired(true) - .accountNonLocked(true) - .credentialsNonExpired(true) - .enabled(true) - .build(); - - Set authorities = Collections.singleton(UserGrantedAuthority - .builder() - .authority(Authority.USER) - .user(userEntity) - .build()); - - userEntity.setAuthorities(authorities); - - return authorities; - } } diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java index b920584c..2c1b7180 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java @@ -19,11 +19,10 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.security.core.userdetails.UserDetails; - +import java.util.Iterator; import java.util.Set; import java.util.UUID; - @Builder @Getter @Setter @@ -72,6 +71,27 @@ public class UserEntity implements UserDetails { @Column(name = "enabled", nullable = false) private boolean enabled; + public void addAuthority(UserGrantedAuthority authority) { + this.authorities.add(authority); + authority.setUser(this); + } + + public void removeAuthority(UserGrantedAuthority authority) { + authority.setUser(null); + this.authorities.remove(authority); + } + + public void removeAuthorities() { + Iterator iterator = this.authorities.iterator(); + + while (iterator.hasNext()){ + UserGrantedAuthority authority = iterator.next(); + + authority.setUser(null); + iterator.remove(); + } + } + @Override public boolean equals(Object o) { if (this == o) diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java b/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java index edffdee8..5f7f41b1 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java @@ -11,16 +11,14 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.springframework.security.core.GrantedAuthority; import java.util.UUID; @Builder @Getter +@Setter @AllArgsConstructor @NoArgsConstructor @Entity From c5d024ea39a35cd352847caf1eae5abc12b4e249 Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Mon, 4 Sep 2023 20:03:10 +0400 Subject: [PATCH 3/6] Fixed Hibernate issue with authorities saving part.2 --- .../java/com/zufar/onlinestore/user/api/UserService.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index 05aa74c5..09747d39 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -2,17 +2,17 @@ import com.zufar.onlinestore.user.converter.UserDtoConverter; import com.zufar.onlinestore.user.dto.UserDto; -import com.zufar.onlinestore.user.entity.Authority; import com.zufar.onlinestore.user.entity.UserEntity; -import com.zufar.onlinestore.user.entity.UserGrantedAuthority; import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import com.zufar.onlinestore.user.exception.UserNotFoundException; import com.zufar.onlinestore.user.repository.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; - -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; @Slf4j @Service @@ -44,6 +44,7 @@ public UserDto saveUser(final UserDto userDto) { } UserEntity userEntityWithId = userCrudRepository.save(userEntity); + authorityService.setDefaultAuthority(userEntityWithId); return userDtoConverter.toDto(userEntityWithId); } From 639b00ddc8c6a0c6d4710eeda3e100d4b0daba6c Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Sun, 10 Sep 2023 19:47:44 +0400 Subject: [PATCH 4/6] Fixed Hibernate issue with authorities saving part.2 --- .../onlinestore/user/api/UserService.java | 43 +------------- .../user/validation/UserDataValidator.java | 56 +++++++++++++++++++ 2 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index 09747d39..9a9dc401 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -6,6 +6,7 @@ import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import com.zufar.onlinestore.user.exception.UserNotFoundException; import com.zufar.onlinestore.user.repository.UserRepository; +import com.zufar.onlinestore.user.validation.UserDataValidator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -22,27 +23,12 @@ public class UserService implements UserApi { private final UserRepository userCrudRepository; private final UserDtoConverter userDtoConverter; private final AuthorityService authorityService; + private final UserDataValidator userDataValidator; @Override public UserDto saveUser(final UserDto userDto) { UserEntity userEntity = userDtoConverter.toEntity(userDto); - - List errors = new ArrayList<>(); - - if (!isEmailUnique(userEntity.getUserId(), userEntity.getEmail())) { - errors.add(String.format("User with email = %s is already registered", userEntity.getEmail())); - } - - if (!isUsernameUnique(userEntity.getUserId(), userEntity.getUsername())) { - errors.add(String.format("User with username = %s is already registered", userEntity.getUsername())); - } - - if (!errors.isEmpty()) { - - throw new UserAlreadyRegisteredException(errors); - - } - + userDataValidator.validate(userEntity); UserEntity userEntityWithId = userCrudRepository.save(userEntity); authorityService.setDefaultAuthority(userEntityWithId); @@ -59,27 +45,4 @@ public UserDto getUserById(final UUID userId) throws UserNotFoundException { return userDtoConverter.toDto(userEntity.get()); } - public boolean isEmailUnique(UUID id, String email) { - Optional userByEmail = userCrudRepository.findByEmail(email); - - if (userByEmail.isEmpty()) { - return true; - } - - return id == null || userByEmail.map(UserEntity::getUserId).filter(userId -> userId == id).isPresent(); - } - - public boolean isUsernameUnique(UUID id, String username) { - UserEntity userByUsername = userCrudRepository.findUserByUsername(username); - - if (userByUsername == null) { - return true; - } - - if (id == null) { - return false; - } else { - return userByUsername.getUserId() == id; - } - } } diff --git a/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java b/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java new file mode 100644 index 00000000..7cef0994 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java @@ -0,0 +1,56 @@ +package com.zufar.onlinestore.user.validation; + +import com.zufar.onlinestore.user.entity.UserEntity; +import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; +import com.zufar.onlinestore.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@RequiredArgsConstructor +@Component +public class UserDataValidator { + + private final List errors; + private final UserRepository userCrudRepository; + + public void validate(UserEntity user) { + if (!isEmailUnique(user.getUserId(), user.getEmail())) { + errors.add(String.format("User with email = %s is already registered", user.getEmail())); + } + + if (!isUsernameUnique(user.getUserId(), user.getUsername())) { + errors.add(String.format("User with username = %s is already registered", user.getUsername())); + } + + if (!errors.isEmpty()) { + + throw new UserAlreadyRegisteredException(errors); + + } + } + + public boolean isEmailUnique(UUID id, String email) { + Optional userByEmail = userCrudRepository.findByEmail(email); + + if (userByEmail.isEmpty()) { + return true; + } + + return userByEmail.map(UserEntity::getUserId).filter(userId -> userId == id).isPresent(); + } + + public boolean isUsernameUnique(UUID id, String username) { + UserEntity userByUsername = userCrudRepository.findUserByUsername(username); + + if (userByUsername == null) { + return true; + } + + return userByUsername.getUserId().equals(id); + } +} + From fd4fe55f7534a34f1d5440d9c7c7bb3126bbd061 Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Sat, 23 Sep 2023 18:03:02 +0400 Subject: [PATCH 5/6] Fixed Hibernate issue with authorities saving part.3 --- .../registration/UserRegistrationRequest.java | 2 ++ .../exception/SecurityExceptionHandler.java} | 7 +++-- .../onlinestore/user/api/UserService.java | 2 -- .../onlinestore/user/entity/UserEntity.java | 1 + .../user/validation/UniqueEmailValidator.java | 23 ++++++++++++++++ .../validation/annotation/UniqueEmail.java | 26 +++++++++++++++++++ src/main/resources/application.yaml | 2 +- 7 files changed, 56 insertions(+), 7 deletions(-) rename src/main/java/com/zufar/onlinestore/{user/exception/handler/UserExceptionHandler.java => security/exception/SecurityExceptionHandler.java} (84%) create mode 100644 src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java create mode 100644 src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java diff --git a/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java b/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java index 2f5c3410..8d393fcb 100644 --- a/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java +++ b/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java @@ -1,5 +1,6 @@ package com.zufar.onlinestore.security.dto.registration; +import com.zufar.onlinestore.user.validation.annotation.UniqueEmail; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -19,6 +20,7 @@ public record UserRegistrationRequest( @Size(max = 55, message = "Username length must be less than 55 characters") String username, + @UniqueEmail(message = "This email is alredy registered") @Email(message = "Email should be valid") @NotBlank(message = "Email is the mandatory attribute") String email, diff --git a/src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java b/src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java similarity index 84% rename from src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java rename to src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java index 9d8673b6..71a8972c 100644 --- a/src/main/java/com/zufar/onlinestore/user/exception/handler/UserExceptionHandler.java +++ b/src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java @@ -1,8 +1,7 @@ -package com.zufar.onlinestore.user.exception.handler; +package com.zufar.onlinestore.security.exception; import com.zufar.onlinestore.common.exception.handler.GlobalExceptionHandler; import com.zufar.onlinestore.common.response.ApiResponse; -import com.zufar.onlinestore.payment.exception.PaymentNotFoundException; import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -12,7 +11,7 @@ @Slf4j @RestControllerAdvice -public class UserExceptionHandler extends GlobalExceptionHandler { +public class SecurityExceptionHandler extends GlobalExceptionHandler { @ExceptionHandler(UserAlreadyRegisteredException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @@ -23,4 +22,4 @@ public ApiResponse handleUserAlreadyRegisteredException(final UserAlreadyR return apiResponse; } -} +} \ No newline at end of file diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index 9a9dc401..dbfc6b19 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -23,12 +23,10 @@ public class UserService implements UserApi { private final UserRepository userCrudRepository; private final UserDtoConverter userDtoConverter; private final AuthorityService authorityService; - private final UserDataValidator userDataValidator; @Override public UserDto saveUser(final UserDto userDto) { UserEntity userEntity = userDtoConverter.toEntity(userDto); - userDataValidator.validate(userEntity); UserEntity userEntityWithId = userCrudRepository.save(userEntity); authorityService.setDefaultAuthority(userEntityWithId); diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java index a18231e8..e6c3211e 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java @@ -11,6 +11,7 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; +import jakarta.validation.constraints.Email; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java b/src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java new file mode 100644 index 00000000..cc1c1ee1 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java @@ -0,0 +1,23 @@ +package com.zufar.onlinestore.user.validation; + +import com.zufar.onlinestore.user.repository.UserRepository; +import com.zufar.onlinestore.user.validation.annotation.UniqueEmail; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +@RequiredArgsConstructor +@Component +public class UniqueEmailValidator implements ConstraintValidator { + + private final UserRepository userCrudRepository; + + @Override + public boolean isValid(String email, ConstraintValidatorContext constraintValidatorContext) { + return userCrudRepository + .findByEmail(email) + .isPresent(); + } +} diff --git a/src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java b/src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java new file mode 100644 index 00000000..b8b1b290 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java @@ -0,0 +1,26 @@ +package com.zufar.onlinestore.user.validation.annotation; + +import com.zufar.onlinestore.user.validation.UniqueEmailValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ FIELD }) +@Retention(RUNTIME) +@Constraint(validatedBy = UniqueEmailValidator.class) +@Documented +public @interface UniqueEmail { + + String message() default ""; + + Class[] groups() default { }; + + Class[] payload() default { }; + +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index d1bdb3f7..4178d9c4 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -90,4 +90,4 @@ jwt: logging: pattern: file: online-store-%d{yyyy-MM-dd}.%i.log - config: classpath:logback-spring.xml \ No newline at end of file + config: classpath:logback-spring.xmlzz \ No newline at end of file From 89e67883f4c4f4671d55308be686cb2d16b0dcb9 Mon Sep 17 00:00:00 2001 From: Reyzis2021 Date: Sun, 24 Sep 2023 15:31:27 +0400 Subject: [PATCH 6/6] Fixed Hibernate issue with authorities saving part.4 --- .../validation/annotation/UniqueEmail.java | 4 +- .../validation/annotation/UniqueUsername.java | 24 ++++++++ .../validator}/UniqueEmailValidator.java | 7 +-- .../validator/UniqueUsernameValidator.java | 20 +++++++ .../registration/UserRegistrationRequest.java | 6 +- .../exception/SecurityExceptionHandler.java | 25 --------- .../onlinestore/user/api/UserService.java | 7 +-- .../onlinestore/user/entity/UserEntity.java | 16 ++---- .../user/entity/UserGrantedAuthority.java | 6 +- .../UserAlreadyRegisteredException.java | 15 ----- .../user/validation/UserDataValidator.java | 56 ------------------- src/main/resources/application.yaml | 2 +- 12 files changed, 66 insertions(+), 122 deletions(-) rename src/main/java/com/zufar/onlinestore/{user => common}/validation/annotation/UniqueEmail.java (80%) create mode 100644 src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueUsername.java rename src/main/java/com/zufar/onlinestore/{user/validation => common/validation/validator}/UniqueEmailValidator.java (79%) create mode 100644 src/main/java/com/zufar/onlinestore/common/validation/validator/UniqueUsernameValidator.java delete mode 100644 src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java delete mode 100644 src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java delete mode 100644 src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java diff --git a/src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java b/src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueEmail.java similarity index 80% rename from src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java rename to src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueEmail.java index b8b1b290..4be147c4 100644 --- a/src/main/java/com/zufar/onlinestore/user/validation/annotation/UniqueEmail.java +++ b/src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueEmail.java @@ -1,6 +1,6 @@ -package com.zufar.onlinestore.user.validation.annotation; +package com.zufar.onlinestore.common.validation.annotation; -import com.zufar.onlinestore.user.validation.UniqueEmailValidator; +import com.zufar.onlinestore.common.validation.validator.UniqueEmailValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; diff --git a/src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueUsername.java b/src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueUsername.java new file mode 100644 index 00000000..08d8faa7 --- /dev/null +++ b/src/main/java/com/zufar/onlinestore/common/validation/annotation/UniqueUsername.java @@ -0,0 +1,24 @@ +package com.zufar.onlinestore.common.validation.annotation; + +import com.zufar.onlinestore.common.validation.validator.UniqueUsernameValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target({ FIELD }) +@Retention(RUNTIME) +@Constraint(validatedBy = UniqueUsernameValidator.class) +@Documented +public @interface UniqueUsername { + + String message() default ""; + + Class[] groups() default { }; + + Class[] payload() default { }; +} diff --git a/src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java b/src/main/java/com/zufar/onlinestore/common/validation/validator/UniqueEmailValidator.java similarity index 79% rename from src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java rename to src/main/java/com/zufar/onlinestore/common/validation/validator/UniqueEmailValidator.java index cc1c1ee1..99c23009 100644 --- a/src/main/java/com/zufar/onlinestore/user/validation/UniqueEmailValidator.java +++ b/src/main/java/com/zufar/onlinestore/common/validation/validator/UniqueEmailValidator.java @@ -1,13 +1,12 @@ -package com.zufar.onlinestore.user.validation; +package com.zufar.onlinestore.common.validation.validator; import com.zufar.onlinestore.user.repository.UserRepository; -import com.zufar.onlinestore.user.validation.annotation.UniqueEmail; +import com.zufar.onlinestore.common.validation.annotation.UniqueEmail; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; - @RequiredArgsConstructor @Component public class UniqueEmailValidator implements ConstraintValidator { @@ -18,6 +17,6 @@ public class UniqueEmailValidator implements ConstraintValidator { + + private final UserRepository userCrudRepository; + + @Override + public boolean isValid(String username, ConstraintValidatorContext constraintValidatorContext) { + return userCrudRepository.findUserByUsername(username) == null; + } +} diff --git a/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java b/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java index 8d393fcb..aed6f6df 100644 --- a/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java +++ b/src/main/java/com/zufar/onlinestore/security/dto/registration/UserRegistrationRequest.java @@ -1,6 +1,7 @@ package com.zufar.onlinestore.security.dto.registration; -import com.zufar.onlinestore.user.validation.annotation.UniqueEmail; +import com.zufar.onlinestore.common.validation.annotation.UniqueEmail; +import com.zufar.onlinestore.common.validation.annotation.UniqueUsername; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -16,11 +17,12 @@ public record UserRegistrationRequest( @Size(max = 55, message = "LastName length must be less than 55 characters") String lastName, + @UniqueUsername(message = "User with this username is already registered") @NotBlank(message = "Username is the mandatory attribute") @Size(max = 55, message = "Username length must be less than 55 characters") String username, - @UniqueEmail(message = "This email is alredy registered") + @UniqueEmail(message = "User with this email is already registered") @Email(message = "Email should be valid") @NotBlank(message = "Email is the mandatory attribute") String email, diff --git a/src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java b/src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java deleted file mode 100644 index 71a8972c..00000000 --- a/src/main/java/com/zufar/onlinestore/security/exception/SecurityExceptionHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.zufar.onlinestore.security.exception; - -import com.zufar.onlinestore.common.exception.handler.GlobalExceptionHandler; -import com.zufar.onlinestore.common.response.ApiResponse; -import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseStatus; -import org.springframework.web.bind.annotation.RestControllerAdvice; - -@Slf4j -@RestControllerAdvice -public class SecurityExceptionHandler extends GlobalExceptionHandler { - - @ExceptionHandler(UserAlreadyRegisteredException.class) - @ResponseStatus(HttpStatus.BAD_REQUEST) - public ApiResponse handleUserAlreadyRegisteredException(final UserAlreadyRegisteredException exception) { - ApiResponse apiResponse = buildResponse(exception, HttpStatus.BAD_REQUEST); - log.error("Handle user already registered exception: failed: messages: {}, description: {}.", - apiResponse.messages(), apiResponse.description()); - - return apiResponse; - } -} \ No newline at end of file diff --git a/src/main/java/com/zufar/onlinestore/user/api/UserService.java b/src/main/java/com/zufar/onlinestore/user/api/UserService.java index dbfc6b19..6b624038 100644 --- a/src/main/java/com/zufar/onlinestore/user/api/UserService.java +++ b/src/main/java/com/zufar/onlinestore/user/api/UserService.java @@ -3,15 +3,12 @@ import com.zufar.onlinestore.user.converter.UserDtoConverter; import com.zufar.onlinestore.user.dto.UserDto; import com.zufar.onlinestore.user.entity.UserEntity; -import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; import com.zufar.onlinestore.user.exception.UserNotFoundException; import com.zufar.onlinestore.user.repository.UserRepository; -import com.zufar.onlinestore.user.validation.UserDataValidator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.List; + import java.util.Optional; import java.util.UUID; @@ -27,8 +24,8 @@ public class UserService implements UserApi { @Override public UserDto saveUser(final UserDto userDto) { UserEntity userEntity = userDtoConverter.toEntity(userDto); + authorityService.setDefaultAuthority(userEntity); UserEntity userEntityWithId = userCrudRepository.save(userEntity); - authorityService.setDefaultAuthority(userEntityWithId); return userDtoConverter.toDto(userEntityWithId); } diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java index e6c3211e..59af3e20 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserEntity.java @@ -11,7 +11,6 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Table; -import jakarta.validation.constraints.Email; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -20,11 +19,11 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.springframework.security.core.userdetails.UserDetails; +import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UUID; - @Builder @Getter @Setter @@ -61,8 +60,9 @@ public class UserEntity implements UserDetails { @JoinColumn(name = "address_id", referencedColumnName = "id") private transient Address address; + @Builder.Default @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER) - private Set authorities; + private Set authorities = new HashSet<>(); @Column(name = "account_non_expired", nullable = false) private boolean accountNonExpired; @@ -87,14 +87,8 @@ public void removeAuthority(UserGrantedAuthority authority) { } public void removeAuthorities() { - Iterator iterator = this.authorities.iterator(); - - while (iterator.hasNext()){ - UserGrantedAuthority authority = iterator.next(); - - authority.setUser(null); - iterator.remove(); - } + authorities.forEach(authority -> authority.setUser(null)); + authorities.clear(); } @Override diff --git a/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java b/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java index 5f7f41b1..4ffb14f8 100644 --- a/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java +++ b/src/main/java/com/zufar/onlinestore/user/entity/UserGrantedAuthority.java @@ -11,7 +11,11 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.Builder; import org.springframework.security.core.GrantedAuthority; import java.util.UUID; diff --git a/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java b/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java deleted file mode 100644 index f7bc3b91..00000000 --- a/src/main/java/com/zufar/onlinestore/user/exception/UserAlreadyRegisteredException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.zufar.onlinestore.user.exception; - -import lombok.Getter; - -import java.util.List; - -@Getter -public class UserAlreadyRegisteredException extends RuntimeException { - - private final List errors; - - public UserAlreadyRegisteredException(List errors) { - this.errors = errors; - } -} diff --git a/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java b/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java deleted file mode 100644 index 7cef0994..00000000 --- a/src/main/java/com/zufar/onlinestore/user/validation/UserDataValidator.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.zufar.onlinestore.user.validation; - -import com.zufar.onlinestore.user.entity.UserEntity; -import com.zufar.onlinestore.user.exception.UserAlreadyRegisteredException; -import com.zufar.onlinestore.user.repository.UserRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Optional; -import java.util.UUID; - -@RequiredArgsConstructor -@Component -public class UserDataValidator { - - private final List errors; - private final UserRepository userCrudRepository; - - public void validate(UserEntity user) { - if (!isEmailUnique(user.getUserId(), user.getEmail())) { - errors.add(String.format("User with email = %s is already registered", user.getEmail())); - } - - if (!isUsernameUnique(user.getUserId(), user.getUsername())) { - errors.add(String.format("User with username = %s is already registered", user.getUsername())); - } - - if (!errors.isEmpty()) { - - throw new UserAlreadyRegisteredException(errors); - - } - } - - public boolean isEmailUnique(UUID id, String email) { - Optional userByEmail = userCrudRepository.findByEmail(email); - - if (userByEmail.isEmpty()) { - return true; - } - - return userByEmail.map(UserEntity::getUserId).filter(userId -> userId == id).isPresent(); - } - - public boolean isUsernameUnique(UUID id, String username) { - UserEntity userByUsername = userCrudRepository.findUserByUsername(username); - - if (userByUsername == null) { - return true; - } - - return userByUsername.getUserId().equals(id); - } -} - diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 4178d9c4..d1bdb3f7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -90,4 +90,4 @@ jwt: logging: pattern: file: online-store-%d{yyyy-MM-dd}.%i.log - config: classpath:logback-spring.xmlzz \ No newline at end of file + config: classpath:logback-spring.xml \ No newline at end of file