Added: Validation for all fields of requests.

Refactor: mappers struct.
This commit is contained in:
KamilM1205 2025-06-30 22:12:52 +04:00
parent d047c17700
commit beb275d7cb
18 changed files with 182 additions and 89 deletions

View file

@ -41,6 +41,7 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
implementation("org.flywaydb:flyway-core") implementation("org.flywaydb:flyway-core")
implementation("org.flywaydb:flyway-database-postgresql:11.10.0") implementation("org.flywaydb:flyway-database-postgresql:11.10.0")
implementation("org.postgresql:postgresql") implementation("org.postgresql:postgresql")

View file

@ -1,20 +1,20 @@
package ru.team58.profileservice.controller; package ru.team58.profileservice.controller;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import ru.team58.profileservice.controller.exceptions.BadRequestException; import ru.team58.profileservice.controller.exceptions.BadRequestException;
import ru.team58.profileservice.controller.exceptions.IncorrectEmailException;
import ru.team58.profileservice.controller.exceptions.UnauthorizedException; import ru.team58.profileservice.controller.exceptions.UnauthorizedException;
import ru.team58.profileservice.controller.exceptions.UserNotFoundException; import ru.team58.profileservice.controller.exceptions.UserNotFoundException;
import ru.team58.profileservice.controller.schemes.CreateUserRequest; import ru.team58.profileservice.controller.schemes.CreateUserRequest;
import ru.team58.profileservice.controller.schemes.GetUserResponse; import ru.team58.profileservice.controller.schemes.GetUserResponse;
import ru.team58.profileservice.controller.schemes.UpdateUserRequest; import ru.team58.profileservice.controller.schemes.UpdateUserRequest;
import ru.team58.profileservice.controller.schemes.UserResponse; import ru.team58.profileservice.controller.schemes.UserResponse;
import ru.team58.profileservice.mapper.UserMapper; import ru.team58.profileservice.mapper.*;
import ru.team58.profileservice.service.UserDTO; import ru.team58.profileservice.service.UserDTO;
import ru.team58.profileservice.service.UserService; import ru.team58.profileservice.service.UserService;
import ru.team58.profileservice.service.exceptions.UserAlreadyExistsException; import ru.team58.profileservice.service.exceptions.UserAlreadyExistsException;
import ru.team58.profileservice.utils.EmailValidator;
import java.security.Principal; import java.security.Principal;
import java.util.UUID; import java.util.UUID;
@ -36,11 +36,11 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
return UserMapper.INSTANCE.toUserResponse(userService.getByUsername(principal.getName())); return UserResponseMapper.INSTANCE.toUserResponse(userService.getByUsername(principal.getName()));
} }
@PutMapping("/") @PutMapping(value = "/", consumes = {MediaType.APPLICATION_JSON_VALUE})
public void updateMe(Principal principal, @RequestBody UpdateUserRequest request) { public void updateMe(Principal principal, @Valid @RequestBody UpdateUserRequest request) {
if (principal == null) { if (principal == null) {
throw new UnauthorizedException(); throw new UnauthorizedException();
} }
@ -51,7 +51,7 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
userService.updateUser(UserMapper.INSTANCE.toUserDTO(request)); userService.updateUser(UpdateUserMapper.INSTANCE.toUserDTO(request));
} }
@DeleteMapping("/") @DeleteMapping("/")
@ -71,12 +71,9 @@ public class UserController {
userService.deleteUser(user.getId()); userService.deleteUser(user.getId());
} }
@PostMapping("/") @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
public void createMe(@RequestBody CreateUserRequest request) { public void createMe(@Valid @RequestBody CreateUserRequest request) {
UserDTO user = UserMapper.INSTANCE.toUserDTO(request); UserDTO user = CreateUserMapper.INSTANCE.toUserDTO(request);
if (!EmailValidator.validateEmail(user.getEmail()))
throw new IncorrectEmailException();
try { try {
userService.createUser(user); userService.createUser(user);
@ -97,7 +94,7 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
return UserMapper.INSTANCE.toGetUserResponse(user); return GetUserMapper.INSTANCE.toGetUserResponse(user);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View file

@ -2,10 +2,16 @@ package ru.team58.profileservice.controller.advice;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import ru.team58.profileservice.controller.exceptions.*; import ru.team58.profileservice.controller.exceptions.*;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice @ControllerAdvice
public class UserControllerExceptionHandler { public class UserControllerExceptionHandler {
@ExceptionHandler(UserNotFoundException.class) @ExceptionHandler(UserNotFoundException.class)
@ -38,9 +44,15 @@ public class UserControllerExceptionHandler {
return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED); return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED);
} }
@ExceptionHandler(IncorrectEmailException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorDetails> handleIncorrectEmailException(IncorrectEmailException ex) { public ResponseEntity<Map<String, String>> handleValidationExceptions(
ErrorDetails errorDetails = new ErrorDetails("Email is incorrect", HttpStatus.BAD_REQUEST.value()); MethodArgumentNotValidException ex) {
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
} }
} }

View file

@ -1,15 +1,10 @@
package ru.team58.profileservice.controller.schemes; package ru.team58.profileservice.controller.schemes;
import lombok.Builder; import lombok.*;
import lombok.Getter; import lombok.experimental.SuperBuilder;
import lombok.Setter;
@Getter @Getter
@Setter @Setter
@Builder @SuperBuilder
public class CreateUserRequest { @NoArgsConstructor
String username; public class CreateUserRequest extends UserScheme {}
String firstName;
String lastName;
String email;
}

View file

@ -1,18 +1,13 @@
package ru.team58.profileservice.controller.schemes; package ru.team58.profileservice.controller.schemes;
import lombok.Builder; import lombok.*;
import lombok.Getter; import lombok.experimental.SuperBuilder;
import lombok.Setter;
import java.util.UUID; import java.util.UUID;
@Getter @Getter
@Setter @Setter
@Builder @SuperBuilder
public class UpdateUserRequest { @NoArgsConstructor
UUID id; public class UpdateUserRequest extends UserScheme {
String username;
String firstName;
String lastName;
String email;
} }

View file

@ -0,0 +1,40 @@
package ru.team58.profileservice.controller.schemes;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.SuperBuilder;
import java.util.UUID;
@SuperBuilder
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserScheme {
private UUID id;
@NotBlank(message = "Username cannot be empty")
@Pattern(regexp = "^[a-z0-9.]+$", message = "Username can have only characters: a-z and 0-9")
@Size(min = 3, max = 14, message = "Username must be bigger than 3 and less than 14")
private String username;
@NotBlank(message = "First name cannot be empty")
@Pattern(regexp = "^[a-zA-Zа-яА-Я]*$", message = "First name can contain only letters")
@Size(min = 3, max = 255, message = "First name must be bigger than 3 and less than 255")
private String firstName;
@NotBlank(message = "Last name cannot be empty")
@Pattern(regexp = "^[a-zA-Zа-яА-Я]*$", message = "Last name can contain only letters")
@Size(min = 3, max = 255, message = "Last name must be bigger than 3 and less than 255")
private String lastName;
@Email(regexp = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}", message = """
Email can contain: a-z, 0-9, '.', '_', '%', '+', '-'.
And have format like: "example@domain.com".""")
@Size(min = 5, max = 255, message = "Email must be bigger than 5 and less than 255")
private String email;
}

View file

@ -0,0 +1,16 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.CreateUserRequest;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface CreateUserMapper {
CreateUserMapper INSTANCE = Mappers.getMapper(CreateUserMapper.class);
@Mapping(target = "id", ignore = true)
UserDTO toUserDTO(CreateUserRequest request);
}

View file

@ -0,0 +1,13 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.GetUserResponse;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface GetUserMapper {
GetUserMapper INSTANCE = Mappers.getMapper(GetUserMapper.class);
GetUserResponse toGetUserResponse(UserDTO dto);
}

View file

@ -0,0 +1,13 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.UpdateUserRequest;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface UpdateUserMapper {
UpdateUserMapper INSTANCE = Mappers.getMapper(UpdateUserMapper.class);
UserDTO toUserDTO(UpdateUserRequest request);
}

View file

@ -0,0 +1,13 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.persistence.entity.UserEntity;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface UserDTOMapper {
UserDTOMapper INSTANCE = Mappers.getMapper(UserDTOMapper.class);
UserDTO toUserDTO(UserEntity entity);
}

View file

@ -0,0 +1,13 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.persistence.entity.UserEntity;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface UserEntityMapper {
UserEntityMapper INSTANCE = Mappers.getMapper(UserEntityMapper.class);
UserEntity toUserEntity(UserDTO dto);
}

View file

@ -1,27 +0,0 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.CreateUserRequest;
import ru.team58.profileservice.controller.schemes.GetUserResponse;
import ru.team58.profileservice.controller.schemes.UpdateUserRequest;
import ru.team58.profileservice.controller.schemes.UserResponse;
import ru.team58.profileservice.persistence.entity.UserEntity;
import ru.team58.profileservice.service.UserDTO;
@Mapper(componentModel = "spring")
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO toUserDTO(UserEntity entity);
UserDTO toUserDTO(UpdateUserRequest request);
@Mapping(target = "id", ignore = true)
UserDTO toUserDTO(CreateUserRequest request);
UserEntity toUserEntity(UserDTO dto);
UserResponse toUserResponse(UserDTO dto);
GetUserResponse toGetUserResponse(UserDTO dto);
}

View file

@ -0,0 +1,13 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.UserResponse;
import ru.team58.profileservice.service.UserDTO;
@Mapper
public interface UserResponseMapper {
UserResponseMapper INSTANCE = Mappers.getMapper(UserResponseMapper.class);
UserResponse toUserResponse(UserDTO dto);
}

View file

@ -0,0 +1,15 @@
package ru.team58.profileservice.mapper;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.UserScheme;
import ru.team58.profileservice.service.UserDTO;
@Mapper(uses = {CreateUserMapper.class, UpdateUserMapper.class})
public interface UserSchemeMapper {
UserSchemeMapper INSTANCE = Mappers.getMapper(UserSchemeMapper.class);
@Mapping(target = "id", ignore = true)
UserDTO toUserDTO(UserScheme user);
}

View file

@ -1,6 +1,7 @@
package ru.team58.profileservice.persistence.entity; package ru.team58.profileservice.persistence.entity;
import jakarta.persistence.*; import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*; import lombok.*;
import java.util.UUID; import java.util.UUID;

View file

@ -1,12 +1,7 @@
package ru.team58.profileservice.service; package ru.team58.profileservice.service;
import jakarta.validation.Valid;
import lombok.*; import lombok.*;
import ru.team58.profileservice.persistence.entity.UserEntity;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@Getter @Getter

View file

@ -2,7 +2,8 @@ package ru.team58.profileservice.service;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import ru.team58.profileservice.mapper.UserMapper; import ru.team58.profileservice.mapper.UserDTOMapper;
import ru.team58.profileservice.mapper.UserEntityMapper;
import ru.team58.profileservice.persistence.entity.UserEntity; import ru.team58.profileservice.persistence.entity.UserEntity;
import ru.team58.profileservice.persistence.repo.UserRepository; import ru.team58.profileservice.persistence.repo.UserRepository;
import ru.team58.profileservice.service.exceptions.UserAlreadyExistsException; import ru.team58.profileservice.service.exceptions.UserAlreadyExistsException;
@ -18,13 +19,13 @@ public class UserServiceImpl implements UserService {
@Override @Override
public UserDTO getById(UUID id) { public UserDTO getById(UUID id) {
UserEntity user = userRepository.findById(id).orElseThrow(UserNotFoundException::new); UserEntity user = userRepository.findById(id).orElseThrow(UserNotFoundException::new);
return UserMapper.INSTANCE.toUserDTO(user); return UserDTOMapper.INSTANCE.toUserDTO(user);
} }
@Override @Override
public UserDTO getByUsername(String username) { public UserDTO getByUsername(String username) {
UserEntity user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new); UserEntity user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new);
return UserMapper.INSTANCE.toUserDTO(user); return UserDTOMapper.INSTANCE.toUserDTO(user);
} }
@Override @Override
@ -32,7 +33,7 @@ public class UserServiceImpl implements UserService {
userRepository.findByUsername(user.username).ifPresent(u -> { userRepository.findByUsername(user.username).ifPresent(u -> {
throw new UserAlreadyExistsException(); throw new UserAlreadyExistsException();
}); });
return userRepository.save(UserMapper.INSTANCE.toUserEntity(user)).getId(); return userRepository.save(UserEntityMapper.INSTANCE.toUserEntity(user)).getId();
} }
@Override @Override
@ -44,6 +45,6 @@ public class UserServiceImpl implements UserService {
@Override @Override
public void updateUser(UserDTO user) { public void updateUser(UserDTO user) {
userRepository.findById(user.getId()).orElseThrow(UserNotFoundException::new); userRepository.findById(user.getId()).orElseThrow(UserNotFoundException::new);
userRepository.save(UserMapper.INSTANCE.toUserEntity(user)); userRepository.save(UserEntityMapper.INSTANCE.toUserEntity(user));
} }
} }

View file

@ -1,13 +0,0 @@
package ru.team58.profileservice.utils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class EmailValidator {
public static boolean validateEmail(String email) {
String regexPattern = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@"
+ "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$";
return Pattern.compile(regexPattern).matcher(email).matches();
}
}