From d0a3e56560d831e47a9e297eda8bd16414ebf7cc Mon Sep 17 00:00:00 2001 From: keshav sharma Date: Wed, 2 Apr 2025 01:10:42 +0530 Subject: [PATCH 1/3] error handler and user api fixes --- .../controllers/v1/UserControllerV1.java | 8 +++- .../shh_be/exceptions/ErrorDetails.java | 11 +++++- .../exceptions/GlobalExceptionHandler.java | 13 +------ .../shh_be/mapper/UserToUserDTOMapper.java | 6 +++ .../shh_be/service/v1/UserServiceV1.java | 38 ++++++++++++------- 5 files changed, 49 insertions(+), 27 deletions(-) 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..54e7fc0 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,11 @@ 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 lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; @RestController() @@ -16,13 +19,14 @@ public class UserControllerV1 { UserServiceV1 userService; @PostMapping() + @ResponseStatus(HttpStatus.CREATED) UserDAO createUser(@RequestBody CreateUserDTO user) { System.out.printf("Received create request for %s", user.getSecret()); return userService.addUser(user); } @GetMapping("/{userId}") - UserDAO getUserById(@PathVariable String userId) { + UserDAO getUserById(@PathVariable String userId) throws ErrorDetails { return userService.getUserById(userId); } @@ -32,7 +36,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/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..9b1f8e7 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,15 @@ 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.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; - @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); + public ResponseEntity globalExceptionHandling(ErrorDetails errorDetails, WebRequest request) { + return new ResponseEntity<>(errorDetails, errorDetails.getStatusCode()); } } \ 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..0f58f2a 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,16 @@ 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.http.HttpStatus; import org.springframework.stereotype.Service; +import java.util.Date; import java.util.List; import java.util.Optional; @@ -28,25 +31,34 @@ public class UserServiceV1 { public List findAllUsers() { return userRepo.findAll(); } - 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); From f1e816f6dcd733dcba6102b1f16bb1b6f4d2ee3f Mon Sep 17 00:00:00 2001 From: keshav sharma Date: Wed, 2 Apr 2025 02:36:48 +0530 Subject: [PATCH 2/3] added validation handler --- pom.xml | 5 ++++ .../controllers/v1/UserControllerV1.java | 3 +- .../vikasa/shh_be/dto/request/UserDTO.java | 11 ++++++- .../exceptions/GlobalExceptionHandler.java | 29 +++++++++++++++++-- 4 files changed, 44 insertions(+), 4 deletions(-) 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 54e7fc0..bd9864a 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 @@ -6,6 +6,7 @@ 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.http.HttpStatus; @@ -20,7 +21,7 @@ public class UserControllerV1 { @PostMapping() @ResponseStatus(HttpStatus.CREATED) - UserDAO createUser(@RequestBody CreateUserDTO user) { + UserDAO createUser(@Valid @RequestBody CreateUserDTO user) { System.out.printf("Received create request for %s", user.getSecret()); return userService.addUser(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..f439fb5 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,14 +1,23 @@ package com.vikasa.shh_be.dto.request; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.Setter; - +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.ValidationUtils.*; import java.math.BigDecimal; @Getter @Setter +@Validated public class UserDTO { + + + @NotNull(message = "missing required field 'sex'") + @Pattern(regexp = "^(male|female)$", message = "sex must be either male or female") private String sex; + private byte age; private String name; private String secret; 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 9b1f8e7..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,15 +1,40 @@ package com.vikasa.shh_be.exceptions; +import org.springframework.http.HttpStatus; 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.ExceptionHandler; import org.springframework.web.context.request.WebRequest; +import java.util.HashMap; +import java.util.Map; + @ControllerAdvice public class GlobalExceptionHandler { - @ExceptionHandler(Exception.class) - public ResponseEntity globalExceptionHandling(ErrorDetails errorDetails, WebRequest request) { + @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 From acb283c755bfe8c6aa246934bcf017e4cc8d4fb1 Mon Sep 17 00:00:00 2001 From: keshav sharma Date: Fri, 29 Aug 2025 00:24:17 +0530 Subject: [PATCH 3/3] Global Error Handler Added --- .../shh_be/controllers/v1/UserControllerV1.java | 6 ++++++ .../com/vikasa/shh_be/dto/request/UserDTO.java | 14 +++++++++----- .../vikasa/shh_be/service/v1/UserServiceV1.java | 7 +++++-- src/main/resources/application.properties | 2 ++ src/main/resources/locale_en.properties | 4 ++++ 5 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 src/main/resources/locale_en.properties 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 bd9864a..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 @@ -9,6 +9,8 @@ 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.*; @@ -26,6 +28,10 @@ UserDAO createUser(@Valid @RequestBody CreateUserDTO user) { return userService.addUser(user); } + @GetMapping() + Page getUsers(Pageable pageable) { + return userService.findAllUsers(pageable); + } @GetMapping("/{userId}") UserDAO getUserById(@PathVariable String userId) throws ErrorDetails { return userService.getUserById(userId); 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 f439fb5..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,26 +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 org.springframework.validation.ValidationUtils.*; import java.math.BigDecimal; @Getter @Setter @Validated public class UserDTO { - - - @NotNull(message = "missing required field 'sex'") - @Pattern(regexp = "^(male|female)$", message = "sex must be either male or female") + @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/service/v1/UserServiceV1.java b/src/main/java/com/vikasa/shh_be/service/v1/UserServiceV1.java index 0f58f2a..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 @@ -9,6 +9,8 @@ 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; @@ -28,8 +30,9 @@ 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) throws ErrorDetails { 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