diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/controller/AuthenticationController.java b/application/src/main/java/net/furizon/backend/feature/authentication/controller/AuthenticationController.java index 265fe5ed..c2f5c506 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/controller/AuthenticationController.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/controller/AuthenticationController.java @@ -1,6 +1,7 @@ package net.furizon.backend.feature.authentication.controller; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import net.furizon.backend.feature.authentication.dto.LoginRequest; import net.furizon.backend.feature.authentication.dto.LoginResponse; @@ -30,7 +31,7 @@ public class AuthenticationController { @PostMapping("/login") public LoginResponse loginUser( - @RequestBody final LoginRequest loginRequest, + @Valid @RequestBody final LoginRequest loginRequest, @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) @Nullable String userAgent, HttpServletRequest httpServletRequest ) { @@ -60,7 +61,7 @@ public LogoutUserResponse logoutUser( @PostMapping("/register") public RegisterUserResponse registerUser( - @RequestBody final RegisterUserRequest registerUserRequest + @Valid @RequestBody final RegisterUserRequest registerUserRequest ) { executor.execute( RegisterUserUseCase.class, diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/dto/LoginRequest.java b/application/src/main/java/net/furizon/backend/feature/authentication/dto/LoginRequest.java index 148ed64f..fddd01fb 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/dto/LoginRequest.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/dto/LoginRequest.java @@ -1,13 +1,22 @@ package net.furizon.backend.feature.authentication.dto; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Data; -import org.jetbrains.annotations.NotNull; + +import static net.furizon.backend.feature.authentication.Const.EMAIL_REGEX; @Data public class LoginRequest { @NotNull + @NotEmpty + @Email(regexp = EMAIL_REGEX) private final String email; @NotNull + @NotEmpty + @Size(min = 6) private final String password; } diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/dto/PersonalUserInformation.java b/application/src/main/java/net/furizon/backend/feature/authentication/dto/PersonalUserInformation.java new file mode 100644 index 00000000..7713bc0c --- /dev/null +++ b/application/src/main/java/net/furizon/backend/feature/authentication/dto/PersonalUserInformation.java @@ -0,0 +1,55 @@ +package net.furizon.backend.feature.authentication.dto; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PastOrPresent; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDate; + +@Data +public class PersonalUserInformation { + @NotNull + @NotEmpty + @Size(min = 2) + private final String firstName; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String lastName; + + @NotNull + @PastOrPresent + private final LocalDate birthday; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String birthCity; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String birthProvince; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String birthCountry; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String currentAddress; + + @NotNull + @NotEmpty + @Size(min = 2) + private final String phoneNumber; + + @Nullable + private final String socialSecurityNumber; +} diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/dto/RegisterUserRequest.java b/application/src/main/java/net/furizon/backend/feature/authentication/dto/RegisterUserRequest.java index 80a098b5..657c6a95 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/dto/RegisterUserRequest.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/dto/RegisterUserRequest.java @@ -1,17 +1,32 @@ package net.furizon.backend.feature.authentication.dto; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import lombok.Data; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; + +import static net.furizon.backend.feature.authentication.Const.EMAIL_REGEX; @Data public class RegisterUserRequest { @NotNull + @NotEmpty + @Email(regexp = EMAIL_REGEX) private final String email; @NotNull + @NotEmpty + @Size(min = 6) private final String password; - @Nullable - private final String fursuitName; + @NotNull + @Pattern(regexp = "^[\\p{L}\\p{N}\\p{M}_\\- ]{3,20}$") + private final String fursonaName; + + @NotNull + @Valid + private final PersonalUserInformation personalUserInformation; } diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/usecase/RegisterUserUseCase.java b/application/src/main/java/net/furizon/backend/feature/authentication/usecase/RegisterUserUseCase.java index 7203a41b..2222bf48 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/usecase/RegisterUserUseCase.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/usecase/RegisterUserUseCase.java @@ -4,6 +4,7 @@ import net.furizon.backend.feature.authentication.action.createAuthentication.CreateAuthenticationAction; import net.furizon.backend.feature.authentication.dto.RegisterUserRequest; import net.furizon.backend.feature.authentication.validation.RegisterUserValidation; +import net.furizon.backend.feature.membership.action.addMembershipInfo.AddMembershipInfoAction; import net.furizon.backend.feature.user.User; import net.furizon.backend.feature.user.action.createUser.CreateUserAction; import net.furizon.backend.infrastructure.usecase.UseCase; @@ -20,16 +21,22 @@ public class RegisterUserUseCase implements UseCase { private final CreateAuthenticationAction createAuthenticationAction; + private final AddMembershipInfoAction addMembershipInfoAction; + @Transactional @Override public @NotNull User executor(@NotNull RegisterUserRequest input) { validation.validate(input); - final var user = createUserAction.invoke(input.getFursuitName()); + final var user = createUserAction.invoke(input.getFursonaName()); createAuthenticationAction.invoke( user.getId(), input.getEmail(), input.getPassword() ); + addMembershipInfoAction.invoke( + user.getId(), + input.getPersonalUserInformation() + ); return user; } diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/validation/AuthenticationException.java b/application/src/main/java/net/furizon/backend/feature/authentication/validation/AuthenticationException.java deleted file mode 100644 index ec140928..00000000 --- a/application/src/main/java/net/furizon/backend/feature/authentication/validation/AuthenticationException.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.furizon.backend.feature.authentication.validation; - -import net.furizon.backend.feature.authentication.AuthenticationErrorCode; -import net.furizon.backend.infrastructure.web.exception.ApiException; -import org.jetbrains.annotations.NotNull; - -import static net.furizon.backend.feature.authentication.Const.EMAIL_PATTERN; - -public class AuthenticationException { - public static void validateEmailOrThrow(@NotNull String email) { - if (!EMAIL_PATTERN.matcher(email).matches()) { - throw new ApiException( - "Invalid email", - AuthenticationErrorCode.EMAIL_INVALID.name() - ); - } - } -} diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/validation/CreateLoginSessionValidation.java b/application/src/main/java/net/furizon/backend/feature/authentication/validation/CreateLoginSessionValidation.java index 2fe17ce5..3e6a25f0 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/validation/CreateLoginSessionValidation.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/validation/CreateLoginSessionValidation.java @@ -20,8 +20,6 @@ public class CreateLoginSessionValidation { private final PasswordEncoder passwordEncoder; public long validateAndGetUserId(@NotNull LoginUserUseCase.Input input) { - AuthenticationException.validateEmailOrThrow(input.email()); - final var authentication = authenticationFinder.findByEmail(input.email()); if (authentication == null) { throw new ApiException( diff --git a/application/src/main/java/net/furizon/backend/feature/authentication/validation/RegisterUserValidation.java b/application/src/main/java/net/furizon/backend/feature/authentication/validation/RegisterUserValidation.java index 4e3ec788..d77a5399 100644 --- a/application/src/main/java/net/furizon/backend/feature/authentication/validation/RegisterUserValidation.java +++ b/application/src/main/java/net/furizon/backend/feature/authentication/validation/RegisterUserValidation.java @@ -14,8 +14,6 @@ public class RegisterUserValidation { private final AuthenticationFinder authenticationFinder; public void validate(@NotNull RegisterUserRequest input) { - AuthenticationException.validateEmailOrThrow(input.getEmail()); - final var authentication = authenticationFinder.findByEmail(input.getEmail()); if (authentication != null) { throw new ApiException( diff --git a/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/AddMembershipInfoAction.java b/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/AddMembershipInfoAction.java new file mode 100644 index 00000000..d5487d50 --- /dev/null +++ b/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/AddMembershipInfoAction.java @@ -0,0 +1,8 @@ +package net.furizon.backend.feature.membership.action.addMembershipInfo; + +import net.furizon.backend.feature.authentication.dto.PersonalUserInformation; +import org.jetbrains.annotations.NotNull; + +public interface AddMembershipInfoAction { + void invoke(long userId, @NotNull final PersonalUserInformation personalUserInformation); +} diff --git a/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/JooqAddMembershipInfoAction.java b/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/JooqAddMembershipInfoAction.java new file mode 100644 index 00000000..25d1eda5 --- /dev/null +++ b/application/src/main/java/net/furizon/backend/feature/membership/action/addMembershipInfo/JooqAddMembershipInfoAction.java @@ -0,0 +1,44 @@ +package net.furizon.backend.feature.membership.action.addMembershipInfo; + +import lombok.RequiredArgsConstructor; +import net.furizon.backend.feature.authentication.dto.PersonalUserInformation; +import net.furizon.jooq.infrastructure.command.SqlCommand; +import org.jetbrains.annotations.NotNull; +import org.jooq.util.postgres.PostgresDSL; +import org.springframework.stereotype.Component; + +import static net.furizon.jooq.generated.Tables.MEMBERSHIP_INFO; + +@Component +@RequiredArgsConstructor +public class JooqAddMembershipInfoAction implements AddMembershipInfoAction { + private final SqlCommand sqlCommand; + + @Override + public void invoke(long userId, @NotNull PersonalUserInformation personalUserInformation) { + sqlCommand.execute( + PostgresDSL + .insertInto( + MEMBERSHIP_INFO, + MEMBERSHIP_INFO.INFO_FIRST_NAME, + MEMBERSHIP_INFO.INFO_LAST_NAME, + MEMBERSHIP_INFO.INFO_BIRTH_CITY, + MEMBERSHIP_INFO.INFO_BIRTH_REGION, + MEMBERSHIP_INFO.INFO_BIRTH_COUNTRY, + MEMBERSHIP_INFO.INFO_ADDRESS, + MEMBERSHIP_INFO.INFO_PHONE, + MEMBERSHIP_INFO.USER_ID + ) + .values( + personalUserInformation.getFirstName(), + personalUserInformation.getLastName(), + personalUserInformation.getBirthCity(), + personalUserInformation.getBirthProvince(), + personalUserInformation.getBirthCountry(), + personalUserInformation.getCurrentAddress(), + personalUserInformation.getPhoneNumber(), + userId + ) + ); + } +} diff --git a/application/src/main/java/net/furizon/backend/feature/user/action/createUser/CreateUserAction.java b/application/src/main/java/net/furizon/backend/feature/user/action/createUser/CreateUserAction.java index 7ac2da6f..3a310bb5 100644 --- a/application/src/main/java/net/furizon/backend/feature/user/action/createUser/CreateUserAction.java +++ b/application/src/main/java/net/furizon/backend/feature/user/action/createUser/CreateUserAction.java @@ -2,9 +2,8 @@ import net.furizon.backend.feature.user.User; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public interface CreateUserAction { @NotNull - User invoke(@Nullable String fursonaName); + User invoke(@NotNull String fursonaName); } diff --git a/application/src/main/java/net/furizon/backend/feature/user/action/createUser/JooqCreateUserAction.java b/application/src/main/java/net/furizon/backend/feature/user/action/createUser/JooqCreateUserAction.java index 2390b2a0..aa97dd00 100644 --- a/application/src/main/java/net/furizon/backend/feature/user/action/createUser/JooqCreateUserAction.java +++ b/application/src/main/java/net/furizon/backend/feature/user/action/createUser/JooqCreateUserAction.java @@ -5,7 +5,6 @@ import net.furizon.backend.feature.user.mapper.JooqUserMapper; import net.furizon.jooq.infrastructure.command.SqlCommand; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jooq.util.postgres.PostgresDSL; import org.springframework.stereotype.Component; @@ -19,7 +18,7 @@ public class JooqCreateUserAction implements CreateUserAction { private final SqlCommand sqlCommand; @Override - public @NotNull User invoke(@Nullable String fursonaName) { + public @NotNull User invoke(@NotNull String fursonaName) { return sqlCommand.executeResult( PostgresDSL.insertInto( USERS, diff --git a/application/src/main/java/net/furizon/backend/infrastructure/web/ApiCommonErrorCode.java b/application/src/main/java/net/furizon/backend/infrastructure/web/ApiCommonErrorCode.java index b1903305..6e0e004e 100644 --- a/application/src/main/java/net/furizon/backend/infrastructure/web/ApiCommonErrorCode.java +++ b/application/src/main/java/net/furizon/backend/infrastructure/web/ApiCommonErrorCode.java @@ -4,6 +4,7 @@ public enum ApiCommonErrorCode { UNKNOWN, UNAUTHENTICATED, SESSION_NOT_FOUND, + INVALID_INPUT, ; @Override diff --git a/application/src/main/java/net/furizon/backend/infrastructure/web/CommonControllerAdvice.java b/application/src/main/java/net/furizon/backend/infrastructure/web/CommonControllerAdvice.java index 01c1c251..f43cf45d 100644 --- a/application/src/main/java/net/furizon/backend/infrastructure/web/CommonControllerAdvice.java +++ b/application/src/main/java/net/furizon/backend/infrastructure/web/CommonControllerAdvice.java @@ -7,6 +7,8 @@ import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.FieldError; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -35,28 +37,11 @@ ResponseEntity handleMethodArgumentNotValidException( @NotNull MethodArgumentNotValidException ex, @NotNull HttpServletRequest request ) { - //.forEach( - //(error) -> { - //if (error instanceof FieldError fieldError) { - //String fieldName = fieldError.getField(); - //String errorMessage = fieldError.getDefaultMessage(); - //errors.put(fieldName, errorMessage); - //} else { - //generalErrors.add(error.getDefaultMessage()); - //} - //} - //); final var errors = ex .getBindingResult() .getAllErrors() .stream() - .map( - (err) -> - new ApiError( - err.getDefaultMessage(), - "" - ) - ) + .map(this::matchObjectError) .toList(); return ResponseEntity .status(HttpStatus.UNPROCESSABLE_ENTITY) @@ -67,4 +52,21 @@ ResponseEntity handleMethodArgumentNotValidException( .build() ); } + + @NotNull + private ApiError matchObjectError(@NotNull ObjectError error) { + if (error instanceof FieldError fieldError) { + return new ApiError( + "Field '%s' %s; (value '%s' is invalid)".formatted( + fieldError.getField(), + error.getDefaultMessage(), + fieldError.getRejectedValue() + ), + ApiCommonErrorCode.INVALID_INPUT.toString() + ); + } + + final var message = error.getDefaultMessage(); + return new ApiError(message != null ? message : "Unknown error", ApiCommonErrorCode.UNKNOWN.toString()); + } }