- Validation for all fields of requests.
- ErrorDTO
Refactor: mappers struct.
This commit is contained in:
KamilM1205 2025-07-23 14:59:50 +04:00
parent beb275d7cb
commit b88414c699
13 changed files with 87 additions and 56 deletions

View file

@ -24,6 +24,18 @@ public class UserController {
@Autowired @Autowired
UserService userService; UserService userService;
@Autowired
UserResponseMapper userResponseMapper;
@Autowired
GetUserMapper getUserMapper;
@Autowired
UpdateUserMapper updateUserMapper;
@Autowired
CreateUserMapper createUserMapper;
@GetMapping("/") @GetMapping("/")
public UserResponse getMe(Principal principal) { public UserResponse getMe(Principal principal) {
if (principal == null) { if (principal == null) {
@ -36,7 +48,7 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
return UserResponseMapper.INSTANCE.toUserResponse(userService.getByUsername(principal.getName())); return userResponseMapper.toUserResponse(userService.getByUsername(principal.getName()));
} }
@PutMapping(value = "/", consumes = {MediaType.APPLICATION_JSON_VALUE}) @PutMapping(value = "/", consumes = {MediaType.APPLICATION_JSON_VALUE})
@ -51,7 +63,7 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
userService.updateUser(UpdateUserMapper.INSTANCE.toUserDTO(request)); userService.updateUser(updateUserMapper.toUserDTO(request));
} }
@DeleteMapping("/") @DeleteMapping("/")
@ -73,7 +85,7 @@ public class UserController {
@PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE) @PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE)
public void createMe(@Valid @RequestBody CreateUserRequest request) { public void createMe(@Valid @RequestBody CreateUserRequest request) {
UserDTO user = CreateUserMapper.INSTANCE.toUserDTO(request); UserDTO user = createUserMapper.toUserDTO(request);
try { try {
userService.createUser(user); userService.createUser(user);
@ -94,7 +106,7 @@ public class UserController {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
return GetUserMapper.INSTANCE.toGetUserResponse(user); return getUserMapper.toGetUserResponse(user);
} }
@DeleteMapping("/{id}") @DeleteMapping("/{id}")

View file

@ -0,0 +1,16 @@
package ru.team58.profileservice.controller.advice;
import lombok.*;
import org.springframework.http.HttpStatus;
import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
public class ErrorDTO {
HttpStatus status;
List<String> errors;
}

View file

@ -6,53 +6,59 @@ import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException; 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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import ru.team58.profileservice.controller.exceptions.*; import ru.team58.profileservice.controller.exceptions.*;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
@ControllerAdvice @ControllerAdvice
public class UserControllerExceptionHandler { public class UserControllerExceptionHandler {
@ExceptionHandler(UserNotFoundException.class) @ExceptionHandler(UserNotFoundException.class)
public ResponseEntity<ErrorDetails> handleUserNotFoundException(UserNotFoundException ex) { public ResponseEntity<ErrorDTO> handleUserNotFoundException(UserNotFoundException ex) {
ErrorDetails errorDetails = new ErrorDetails("User not found", HttpStatus.FORBIDDEN.value()); var error = new ErrorDTO(HttpStatus.FORBIDDEN, List.of("User not found"));
return new ResponseEntity<>(errorDetails, HttpStatus.FORBIDDEN); return ResponseEntity.status(error.status).body(error);
} }
@ExceptionHandler(UserAlreadyExistsException.class) @ExceptionHandler(UserAlreadyExistsException.class)
public ResponseEntity<ErrorDetails> handleUserAlreadyExistsException(UserAlreadyExistsException ex) { public ResponseEntity<ErrorDTO> handleUserAlreadyExistsException(UserAlreadyExistsException ex) {
ErrorDetails errorDetails = new ErrorDetails("User already exists", HttpStatus.BAD_REQUEST.value()); var errorDetails = new ErrorDetails("User already exists", HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); var error = new ErrorDTO(HttpStatus.BAD_REQUEST, List.of("User already exists"));
return ResponseEntity.status(error.status).body(error);
} }
@ExceptionHandler(BadRequestException.class) @ExceptionHandler(BadRequestException.class)
public ResponseEntity<ErrorDetails> handleBadRequestException(BadRequestException ex) { public ResponseEntity<ErrorDTO> handleBadRequestException(BadRequestException ex) {
ErrorDetails errorDetails = new ErrorDetails("Bad Request", HttpStatus.BAD_REQUEST.value()); var error = new ErrorDTO(HttpStatus.BAD_REQUEST, List.of("Bad request"));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST); return ResponseEntity.status(error.status).body(error);
} }
@ExceptionHandler(ForbiddenException.class) @ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ErrorDetails> handleForbiddenException(ForbiddenException ex) { public ResponseEntity<ErrorDTO> handleForbiddenException(ForbiddenException ex) {
ErrorDetails errorDetails = new ErrorDetails("Forbidden", HttpStatus.FORBIDDEN.value()); var error = new ErrorDTO(HttpStatus.FORBIDDEN, List.of("Forbidden"));
return new ResponseEntity<>(errorDetails, HttpStatus.FORBIDDEN); return ResponseEntity.status(error.status).body(error);
} }
@ExceptionHandler(UnauthorizedException.class) @ExceptionHandler(UnauthorizedException.class)
public ResponseEntity<ErrorDetails> handleUnauthorizedException(UnauthorizedException ex) { public ResponseEntity<ErrorDTO> handleUnauthorizedException(UnauthorizedException ex) {
ErrorDetails errorDetails = new ErrorDetails("Unauthorized", HttpStatus.UNAUTHORIZED.value()); var error = new ErrorDTO(HttpStatus.UNAUTHORIZED, List.of("Unauthorized"));
return new ResponseEntity<>(errorDetails, HttpStatus.UNAUTHORIZED); return ResponseEntity.status(error.status).body(error);
} }
@ExceptionHandler(MethodArgumentNotValidException.class) @ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions( public ResponseEntity<ErrorDTO> handleValidationExceptions(
MethodArgumentNotValidException ex) { MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>(); var errors = new ArrayList<String>();
ex.getBindingResult().getAllErrors().forEach((error) -> { ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField(); var fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage(); var errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage); errors.add(String.format("%s: %s", fieldName, errorMessage));
}); });
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
var error = new ErrorDTO(HttpStatus.BAD_REQUEST, errors);
return ResponseEntity.status(error.status).body(error);
} }
} }

View file

@ -32,6 +32,7 @@ public class UserScheme {
@Size(min = 3, max = 255, message = "Last name must be bigger than 3 and less than 255") @Size(min = 3, max = 255, message = "Last name must be bigger than 3 and less than 255")
private String lastName; private String lastName;
@NotBlank
@Email(regexp = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}", message = """ @Email(regexp = "[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,3}", message = """
Email can contain: a-z, 0-9, '.', '_', '%', '+', '-'. Email can contain: a-z, 0-9, '.', '_', '%', '+', '-'.
And have format like: "example@domain.com".""") And have format like: "example@domain.com".""")

View file

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

View file

@ -5,9 +5,7 @@ import org.mapstruct.factory.Mappers;
import ru.team58.profileservice.controller.schemes.GetUserResponse; import ru.team58.profileservice.controller.schemes.GetUserResponse;
import ru.team58.profileservice.service.UserDTO; import ru.team58.profileservice.service.UserDTO;
@Mapper @Mapper(componentModel = "spring")
public interface GetUserMapper { public interface GetUserMapper {
GetUserMapper INSTANCE = Mappers.getMapper(GetUserMapper.class);
GetUserResponse toGetUserResponse(UserDTO dto); GetUserResponse toGetUserResponse(UserDTO dto);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -16,16 +16,21 @@ public class UserServiceImpl implements UserService {
@Autowired @Autowired
UserRepository userRepository; UserRepository userRepository;
@Autowired
UserDTOMapper userDTOMapper;
@Autowired
UserEntityMapper userEntityMapper;
@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 UserDTOMapper.INSTANCE.toUserDTO(user); return userDTOMapper.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 UserDTOMapper.INSTANCE.toUserDTO(user); return userDTOMapper.toUserDTO(user);
} }
@Override @Override
@ -33,7 +38,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(UserEntityMapper.INSTANCE.toUserEntity(user)).getId(); return userRepository.save(userEntityMapper.toUserEntity(user)).getId();
} }
@Override @Override
@ -45,6 +50,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(UserEntityMapper.INSTANCE.toUserEntity(user)); userRepository.save(userEntityMapper.toUserEntity(user));
} }
} }

View file

@ -0,0 +1,12 @@
package ru.team58.profileservice;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class ProfileServiceTest {
@Test
public void contextLoads() {
}
}