diff --git a/pom.xml b/pom.xml index c6a0810..ad60616 100644 --- a/pom.xml +++ b/pom.xml @@ -30,6 +30,11 @@ 17 + + org.springframework.boot + spring-boot-starter-validation + 3.4.4 + org.projectlombok lombok diff --git a/src/main/java/com/vikasa/shh_be/controllers/v1/UserControllerV1.java b/src/main/java/com/vikasa/shh_be/controllers/v1/UserControllerV1.java index ba8dcdc..2f2a6fb 100644 --- a/src/main/java/com/vikasa/shh_be/controllers/v1/UserControllerV1.java +++ b/src/main/java/com/vikasa/shh_be/controllers/v1/UserControllerV1.java @@ -4,8 +4,14 @@ import com.vikasa.shh_be.dto.request.DeleteUserRequestBody; import com.vikasa.shh_be.dto.request.UpdateUserDTO; import com.vikasa.shh_be.dto.response.UserDAO; +import com.vikasa.shh_be.exceptions.ErrorDetails; import com.vikasa.shh_be.service.v1.UserServiceV1; +import jakarta.validation.Valid; +import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController() @@ -16,13 +22,18 @@ public class UserControllerV1 { UserServiceV1 userService; @PostMapping() - UserDAO createUser(@RequestBody CreateUserDTO user) { + @ResponseStatus(HttpStatus.CREATED) + UserDAO createUser(@Valid @RequestBody CreateUserDTO user) { System.out.printf("Received create request for %s", user.getSecret()); return userService.addUser(user); } + @GetMapping() + Page getUsers(Pageable pageable) { + return userService.findAllUsers(pageable); + } @GetMapping("/{userId}") - UserDAO getUserById(@PathVariable String userId) { + UserDAO getUserById(@PathVariable String userId) throws ErrorDetails { return userService.getUserById(userId); } @@ -32,7 +43,7 @@ void deleteUser(@RequestBody DeleteUserRequestBody user) { } @PatchMapping("/{userId}") - void updateUser(@PathVariable("userId") String userId, @RequestBody UpdateUserDTO user) { + void updateUser(@PathVariable("userId") String userId, @RequestBody UpdateUserDTO user) throws ErrorDetails { userService.updateUser(userId, user); } } diff --git a/src/main/java/com/vikasa/shh_be/dto/request/UserDTO.java b/src/main/java/com/vikasa/shh_be/dto/request/UserDTO.java index e5acc78..886e266 100644 --- a/src/main/java/com/vikasa/shh_be/dto/request/UserDTO.java +++ b/src/main/java/com/vikasa/shh_be/dto/request/UserDTO.java @@ -1,17 +1,30 @@ package com.vikasa.shh_be.dto.request; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.Setter; - +import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; @Getter @Setter +@Validated public class UserDTO { + @NotNull(message = "{validation.user.invalid_sex}") + @Pattern(regexp = "^(male|female)$", message = "{validation.user.invalid_sex}") private String sex; + + @Min(value = 18) private byte age; + + @NotNull(message = "{validation.user.invalid_name}") private String name; + + @NotNull(message = "{validation.user.invalid_secret}") private String secret; + private BigDecimal latitude; private BigDecimal longitude; } diff --git a/src/main/java/com/vikasa/shh_be/exceptions/ErrorDetails.java b/src/main/java/com/vikasa/shh_be/exceptions/ErrorDetails.java index 0ca8b20..81b0448 100644 --- a/src/main/java/com/vikasa/shh_be/exceptions/ErrorDetails.java +++ b/src/main/java/com/vikasa/shh_be/exceptions/ErrorDetails.java @@ -1,14 +1,23 @@ package com.vikasa.shh_be.exceptions; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import java.util.Date; +@Builder +@EqualsAndHashCode(callSuper = true) @Data @AllArgsConstructor -public class ErrorDetails { +public class ErrorDetails extends Exception { private Date timestamp; private String message; private String details; + + @Builder.Default + private HttpStatusCode statusCode = HttpStatus.INTERNAL_SERVER_ERROR; } \ No newline at end of file diff --git a/src/main/java/com/vikasa/shh_be/exceptions/GlobalExceptionHandler.java b/src/main/java/com/vikasa/shh_be/exceptions/GlobalExceptionHandler.java index 3edcc5e..828d677 100644 --- a/src/main/java/com/vikasa/shh_be/exceptions/GlobalExceptionHandler.java +++ b/src/main/java/com/vikasa/shh_be/exceptions/GlobalExceptionHandler.java @@ -1,24 +1,40 @@ package com.vikasa.shh_be.exceptions; -import org.jetbrains.annotations.NotNull; import org.springframework.http.HttpStatus; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ProblemDetail; import org.springframework.http.ResponseEntity; -import org.springframework.web.ErrorResponse; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; 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.context.request.WebRequest; -import java.util.Date; +import java.util.HashMap; +import java.util.Map; @ControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(Exception.class) - public ResponseEntity globalExceptionHandling(Exception exception, WebRequest request) { - return new ResponseEntity<>(new ErrorDetails(new Date(), exception.getMessage(), request.getDescription(false)), HttpStatus.INTERNAL_SERVER_ERROR); + @ExceptionHandler(ErrorDetails.class) + public ResponseEntity handleErrorDetailsException(ErrorDetails errorDetails, WebRequest request) { + return new ResponseEntity<>(errorDetails, errorDetails.getStatusCode()); } + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) { + Map errorResponse = new HashMap<>(); + + errorResponse.put("timestamp", System.currentTimeMillis()); + + errorResponse.put("message", "Validation failed for the request"); + + Map fieldErrors = new HashMap<>(); + + for (FieldError error : ex.getBindingResult().getFieldErrors()) { + fieldErrors.put(error.getField(), error.getDefaultMessage()); + } + + errorResponse.put("fieldErrors", fieldErrors); + + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + } \ No newline at end of file diff --git a/src/main/java/com/vikasa/shh_be/mapper/UserToUserDTOMapper.java b/src/main/java/com/vikasa/shh_be/mapper/UserToUserDTOMapper.java index ca6bee7..c7506e0 100644 --- a/src/main/java/com/vikasa/shh_be/mapper/UserToUserDTOMapper.java +++ b/src/main/java/com/vikasa/shh_be/mapper/UserToUserDTOMapper.java @@ -1,9 +1,15 @@ package com.vikasa.shh_be.mapper; import com.vikasa.shh_be.dto.response.UserDAO; +import com.vikasa.shh_be.exceptions.ErrorDetails; import com.vikasa.shh_be.model.User; +import jakarta.annotation.Nullable; +import lombok.SneakyThrows; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import java.util.Date; +import java.util.Optional; import java.util.function.Function; @Service diff --git a/src/main/java/com/vikasa/shh_be/service/v1/UserServiceV1.java b/src/main/java/com/vikasa/shh_be/service/v1/UserServiceV1.java index cca7f32..17b1173 100644 --- a/src/main/java/com/vikasa/shh_be/service/v1/UserServiceV1.java +++ b/src/main/java/com/vikasa/shh_be/service/v1/UserServiceV1.java @@ -3,13 +3,18 @@ import com.vikasa.shh_be.dto.request.CreateUserDTO; import com.vikasa.shh_be.dto.request.UpdateUserDTO; import com.vikasa.shh_be.dto.response.UserDAO; +import com.vikasa.shh_be.exceptions.ErrorDetails; import com.vikasa.shh_be.mapper.CreateUserDTOToUserMapper; import com.vikasa.shh_be.mapper.UserToUserDTOMapper; import com.vikasa.shh_be.model.User; import com.vikasa.shh_be.repository.v1.UserRepositoryV1; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import java.util.Date; import java.util.List; import java.util.Optional; @@ -25,28 +30,38 @@ public class UserServiceV1 { @Autowired UserToUserDTOMapper userToUserDTOMapper; - public List findAllUsers() { - return userRepo.findAll(); + public Page findAllUsers(Pageable pageable) { + Page page = userRepo.findAll(pageable); + return page.map(userToUserDTOMapper); } - public UserDAO getUserById(String id) { + + public UserDAO getUserById(String id) throws ErrorDetails { Optional obj = userRepo.findById(id); - return userToUserDTOMapper.apply(obj.orElse(null)); + if(obj.isEmpty()) { + throw new ErrorDetails(new Date(), "Failed to update User, as not found", "User with given ID cant be updated", HttpStatus.NOT_FOUND); + } + return userToUserDTOMapper.apply(obj.get()); } + public UserDAO addUser(CreateUserDTO usr) { User obj = userRepo.save(createUserToUserMapper.apply(usr)); return userToUserDTOMapper.apply(obj); } - public UserDAO updateUser(String id, UpdateUserDTO usr) { - User obj = userRepo.findById(id).orElse(null); - if(obj == null) - return null; - obj.setName(usr.getName()); - obj.setSecret(usr.getSecret()); - obj.setAge(usr.getAge()); - obj.setLatitude(usr.getLatitude()); - obj.setLongitude(usr.getLongitude()); - userRepo.saveAndFlush(obj); - return userToUserDTOMapper.apply(obj); + + public UserDAO updateUser(String id, UpdateUserDTO usr) throws ErrorDetails { + Optional obj = userRepo.findById(id); + if(obj.isEmpty()) { + throw new ErrorDetails(new Date(), "Failed to update User, as not found", "User with given ID cant be updated", HttpStatus.BAD_REQUEST); + } + + User user = obj.get(); + user.setName(usr.getName()); + user.setSecret(usr.getSecret()); + user.setAge(usr.getAge()); + user.setLatitude(usr.getLatitude()); + user.setLongitude(usr.getLongitude()); + userRepo.saveAndFlush(user); + return userToUserDTOMapper.apply(user); } public void deleteUsers(List ids) { List users = userRepo.findAllById(ids); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 67db49a..2ee497d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -6,3 +6,5 @@ spring.datasource.url=${SPRING_DATASOURCE_URL} spring.datasource.username=${SPRING_DATASOURCE_USERNAME} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD} spring.jpa.properties.hibernate.jdbc.time_zone=UTC +spring.messages.basename=locale_en +spring.messages.encoding=UTF-8 diff --git a/src/main/resources/locale_en.properties b/src/main/resources/locale_en.properties new file mode 100644 index 0000000..d020131 --- /dev/null +++ b/src/main/resources/locale_en.properties @@ -0,0 +1,4 @@ +validation.user.invalid_sex=Sex must be either male or female +validation.user.invalid_name=missing property name +validation.user.invalid_age=user must be atleast 18 year old +validation.user.invalid_secret=missing secret or provided value is invalid. Ensure it adheres to the required password validation. \ No newline at end of file