From 955b0af22d1a5b2331192eea0fdb6736d290496b Mon Sep 17 00:00:00 2001 From: Maks Date: Sat, 11 Apr 2026 20:37:46 +0300 Subject: [PATCH] Add message validations to dtos, refactor annotations in most classes, fix controllers create methods, fix logic in OrderServiceImpl --- .../controller/AuthenticationController.java | 7 ++- .../bookstore/controller/BookController.java | 25 ++++---- .../controller/CategoryController.java | 26 ++++---- .../bookstore/controller/OrderController.java | 23 +++---- .../controller/ShoppingCartController.java | 14 ++--- .../dto/book/CreateBookRequestDto.java | 21 ++++--- .../dto/cartitem/CartItemRequestDto.java | 6 +- .../cartitem/UpdateCartItemRequestDto.java | 2 +- .../category/CreateCategoryRequestDto.java | 2 +- .../bookstore/dto/order/OrderRequestDto.java | 2 +- .../order/UpdateOrderStatusRequestDto.java | 2 +- .../dto/user/UserLoginRequestDto.java | 10 ++-- .../dto/user/UserRegistrationRequestDto.java | 21 ++++--- .../CustomGlobalExceptionHandler.java | 60 +++++++++++++------ .../exception/EmptyCartException.java | 7 +++ .../com/origin/bookstore/model/CartItem.java | 2 +- .../com/origin/bookstore/model/Order.java | 2 +- .../com/origin/bookstore/model/OrderItem.java | 2 +- .../origin/bookstore/model/ShoppingCart.java | 2 +- .../java/com/origin/bookstore/model/User.java | 6 +- .../service/impl/OrderServiceImpl.java | 5 ++ .../AuthenticationControllerTest.java | 4 +- .../controller/BookControllerTest.java | 2 +- .../controller/OrderControllerTest.java | 2 +- .../users/add-admin-to-users-table.sql | 2 +- .../users/add-user-to-users-table.sql | 2 +- 26 files changed, 153 insertions(+), 106 deletions(-) create mode 100644 src/main/java/com/origin/bookstore/exception/EmptyCartException.java diff --git a/src/main/java/com/origin/bookstore/controller/AuthenticationController.java b/src/main/java/com/origin/bookstore/controller/AuthenticationController.java index 986e0ab..bc87d40 100644 --- a/src/main/java/com/origin/bookstore/controller/AuthenticationController.java +++ b/src/main/java/com/origin/bookstore/controller/AuthenticationController.java @@ -10,9 +10,11 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; @Tag(name = "User management", description = "Endpoints for managing user") @@ -24,14 +26,15 @@ public class AuthenticationController { private final AuthenticationService authenticationService; - @PostMapping("/registration") @Operation(summary = "Create new user", description = "Create a new user") + @PostMapping("/registration") + @ResponseStatus(HttpStatus.CREATED) public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto request) { return userService.save(request); } - @PostMapping("/login") @Operation(summary = "User login", description = "Endpoint for user login") + @PostMapping("/login") public UserLoginResponseDto login(@RequestBody @Valid UserLoginRequestDto request) { return authenticationService.authenticate(request); } diff --git a/src/main/java/com/origin/bookstore/controller/BookController.java b/src/main/java/com/origin/bookstore/controller/BookController.java index 54acd03..b372cca 100644 --- a/src/main/java/com/origin/bookstore/controller/BookController.java +++ b/src/main/java/com/origin/bookstore/controller/BookController.java @@ -29,46 +29,47 @@ public class BookController { private final BookService bookService; - @PreAuthorize("hasRole('USER')") - @GetMapping @Operation(summary = "Get all books", description = "Get a list of all available books") + @GetMapping + @PreAuthorize("hasRole('USER')") public Page getAll(Pageable pageable) { return bookService.findAll(pageable); } - @PreAuthorize("hasRole('USER')") - @GetMapping("/{id}") @Operation(summary = "Get book by id", description = "Get a book by id") + @GetMapping("/{id}") + @PreAuthorize("hasRole('USER')") public BookDto getBookById(@PathVariable Long id) { return bookService.findById(id); } - @PreAuthorize("hasRole('ADMIN')") - @PostMapping @Operation(summary = "Create book", description = "Create a new book") + @PostMapping + @PreAuthorize("hasRole('ADMIN')") + @ResponseStatus(HttpStatus.CREATED) public BookDto createBook(@RequestBody @Valid CreateBookRequestDto bookDto) { return bookService.save(bookDto); } + @Operation(summary = "Delete book by id", description = "Delete a book by id") + @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN')") @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{id}") - @Operation(summary = "Delete book by id", description = "Delete a book by id") public void deleteBookById(@PathVariable Long id) { bookService.deleteById(id); } - @PreAuthorize("hasRole('ADMIN')") - @PutMapping("/{id}") @Operation(summary = "Update book by id", description = "Update a book by id") + @PutMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") public BookDto updateBook(@PathVariable Long id, @RequestBody @Valid CreateBookRequestDto bookDto) { return bookService.updateBook(id, bookDto); } - @PreAuthorize("hasRole('USER')") - @GetMapping("/search") @Operation(summary = "Search books by parameters", description = "Search books by parameters") + @GetMapping("/search") + @PreAuthorize("hasRole('USER')") public Page searchBooks(BookSearchParameters searchParameters, Pageable pageable) { return bookService.search(searchParameters, pageable); } diff --git a/src/main/java/com/origin/bookstore/controller/CategoryController.java b/src/main/java/com/origin/bookstore/controller/CategoryController.java index ebec086..628b4f7 100644 --- a/src/main/java/com/origin/bookstore/controller/CategoryController.java +++ b/src/main/java/com/origin/bookstore/controller/CategoryController.java @@ -29,50 +29,50 @@ public class CategoryController { private final CategoryService categoryService; - @PreAuthorize("hasRole('ADMIN')") + @Operation(summary = "Create a category", description = "Create a category") @PostMapping + @PreAuthorize("hasRole('ADMIN')") @ResponseStatus(HttpStatus.CREATED) - @Operation(summary = "Create a category", description = "Create a category") public CategoryDto createCategory( @RequestBody @Valid CreateCategoryRequestDto createCategoryRequestDto) { return categoryService.save(createCategoryRequestDto); } - @PreAuthorize("hasRole('USER')") - @GetMapping @Operation(summary = "Get all categories", description = "Get all categories") + @GetMapping + @PreAuthorize("hasRole('USER')") public Page getAll(Pageable pageable) { return categoryService.findAll(pageable); } - @PreAuthorize("hasRole('USER')") - @GetMapping("/{id}") @Operation(summary = "Get category by id", description = "Get category by id") + @GetMapping("/{id}") + @PreAuthorize("hasRole('USER')") public CategoryDto getCategoryById(@PathVariable Long id) { return categoryService.getById(id); } - @PreAuthorize("hasRole('ADMIN')") - @PutMapping("/{id}") @Operation(summary = "Update category by id", description = "Update category by id") + @PutMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") public CategoryDto updateCategory(@PathVariable Long id, @RequestBody @Valid CreateCategoryRequestDto createCategoryRequestDto) { return categoryService.update(id, createCategoryRequestDto); } - @PreAuthorize("hasRole('ADMIN')") - @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{id}") @Operation(summary = "Delete category by id", description = "Delete category by id") + @DeleteMapping("/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + @PreAuthorize("hasRole('ADMIN')") public void deleteCategory(@PathVariable Long id) { categoryService.deleteById(id); } - @PreAuthorize("hasRole('USER')") - @GetMapping("/{id}/books") @Operation(summary = "Get books by category id", description = "Get books by category id") + @GetMapping("/{id}/books") + @PreAuthorize("hasRole('USER')") public Page getBooksByCategoryId(@PathVariable Long id, Pageable pageable) { return categoryService.getBooksByCategoryId(id, pageable); } diff --git a/src/main/java/com/origin/bookstore/controller/OrderController.java b/src/main/java/com/origin/bookstore/controller/OrderController.java index 961ac83..b52c7f4 100644 --- a/src/main/java/com/origin/bookstore/controller/OrderController.java +++ b/src/main/java/com/origin/bookstore/controller/OrderController.java @@ -13,6 +13,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; @@ -21,46 +22,47 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; -@Tag(name = "Orders managing", description = "Endpoints for managing orders") +@Tag(name = "Orders management", description = "Endpoints for managing orders") @RestController @RequiredArgsConstructor @RequestMapping("/orders") public class OrderController { private final OrderService orderService; - @PreAuthorize("hasRole('USER')") @Operation(summary = "Create order", description = "Create order") @PostMapping + @PreAuthorize("hasRole('USER')") + @ResponseStatus(HttpStatus.CREATED) public OrderResponseDto createOrder( @AuthenticationPrincipal User user, - @RequestBody @Valid OrderRequestDto orderRequestDto - ) { - return orderService.save(user, orderRequestDto); + @RequestBody @Valid OrderRequestDto requestDto) { + return orderService.save(user, requestDto); } - @PreAuthorize("hasRole('USER')") @Operation(summary = "Get all orders", description = "Get all orders") @GetMapping + @PreAuthorize("hasRole('USER')") public Page getAllOrders( @AuthenticationPrincipal User user, Pageable pageable) { return orderService.getAllOrders(user, pageable); } - @PreAuthorize("hasRole('USER')") @Operation(summary = "Get all order items in order", description = "Get all order items in order") @GetMapping("/{orderId}/items") + @PreAuthorize("hasRole('USER')") public List getAllOrderItemsInOrder( @AuthenticationPrincipal User user, @PathVariable Long orderId) { return orderService.getAllOrderItems(user, orderId); } - @PreAuthorize("hasRole('USER')") @Operation(summary = "Get order item by id", description = "Get order item by id") @GetMapping("/{orderId}/items/{id}") + @PreAuthorize("hasRole('USER')") public OrderItemResponseDto getOrderItemById( @AuthenticationPrincipal User user, @PathVariable Long orderId, @@ -68,13 +70,12 @@ public OrderItemResponseDto getOrderItemById( return orderService.getOrderItemById(user, orderId, itemId); } - @PreAuthorize("hasRole('ADMIN')") @Operation(summary = "Update order status by id", description = "Update order status by id") @PatchMapping("/{id}") + @PreAuthorize("hasRole('ADMIN')") public OrderResponseDto updateOrderStatusById( @PathVariable Long id, - @RequestBody - @Valid UpdateOrderStatusRequestDto updateOrderStatusRequestDto) { + @RequestBody @Valid UpdateOrderStatusRequestDto updateOrderStatusRequestDto) { return orderService.updateOrderStatus(id, updateOrderStatusRequestDto); } } diff --git a/src/main/java/com/origin/bookstore/controller/ShoppingCartController.java b/src/main/java/com/origin/bookstore/controller/ShoppingCartController.java index 0cb4778..b21960e 100644 --- a/src/main/java/com/origin/bookstore/controller/ShoppingCartController.java +++ b/src/main/java/com/origin/bookstore/controller/ShoppingCartController.java @@ -29,10 +29,10 @@ public class ShoppingCartController { private final ShoppingCartService shoppingCartService; + @Operation(summary = "Add book to cart", description = "Add book to shopping cart") @PostMapping @PreAuthorize("hasRole('USER')") @ResponseStatus(HttpStatus.CREATED) - @Operation(summary = "Add book to cart", description = "Add book to shopping cart") public ShoppingCartResponseDto addBookToCart( @RequestBody @Valid CartItemRequestDto cartItemRequestDto, @AuthenticationPrincipal User user) { @@ -40,19 +40,19 @@ public ShoppingCartResponseDto addBookToCart( cartItemRequestDto); } - @GetMapping - @PreAuthorize("hasRole('USER')") @Operation(summary = "Get items from shopping cart", description = "Get item list from shopping cart") + @GetMapping + @PreAuthorize("hasRole('USER')") public ShoppingCartResponseDto getCartItems( @AuthenticationPrincipal User user) { return shoppingCartService.getShoppingCartByUserId(user); } - @PutMapping("/items/{cartItemId}") - @PreAuthorize("hasRole('USER')") @Operation(summary = "Update the books quantity in shopping cart", description = "Update the books quantity in shopping cart") + @PutMapping("/items/{cartItemId}") + @PreAuthorize("hasRole('USER')") public ShoppingCartResponseDto updateBookQuantityInCart( @PathVariable Long cartItemId, @RequestBody @Valid UpdateCartItemRequestDto updateCartItemRequestDto, @@ -61,11 +61,11 @@ public ShoppingCartResponseDto updateBookQuantityInCart( cartItemId, updateCartItemRequestDto); } + @Operation(summary = "Delete a book from shopping cart", + description = "Delete a book from shopping cart") @DeleteMapping("/items/{cartItemId}") @ResponseStatus(HttpStatus.NO_CONTENT) @PreAuthorize("hasRole('USER')") - @Operation(summary = "Delete a book from shopping cart", - description = "Delete a book from shopping cart") public void deleteBookFromCart(@PathVariable Long cartItemId, @AuthenticationPrincipal User user) { shoppingCartService.deleteBookFromCart(user, cartItemId); diff --git a/src/main/java/com/origin/bookstore/dto/book/CreateBookRequestDto.java b/src/main/java/com/origin/bookstore/dto/book/CreateBookRequestDto.java index ce5f2e4..1f5c7e7 100644 --- a/src/main/java/com/origin/bookstore/dto/book/CreateBookRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/book/CreateBookRequestDto.java @@ -11,25 +11,28 @@ import lombok.Builder; import lombok.Getter; import lombok.Setter; +import org.hibernate.validator.constraints.URL; @Getter @Setter @Builder public class CreateBookRequestDto { - @NotBlank - @Size(min = 1, max = 100) + @NotBlank(message = "Title cannot be blank") + @Size(min = 1, max = 100, message = "Title must be between 1 and 100 characters") private String title; - @NotBlank - @Size(min = 1, max = 100) + @NotBlank(message = "Author cannot be blank") + @Size(min = 1, max = 10, message = "Author must be between 1 and 100 characters") private String author; - @NotBlank - @Pattern(regexp = "\\d{3}-\\d{10}") + @NotBlank(message = "ISBN cannot be blank") + @Pattern(regexp = "\\d{3}-\\d{10}", message = "Invalid ISBN format") private String isbn; - @NotNull - @DecimalMin(value = "0", inclusive = true) + @NotNull(message = "Price cannot be null") + @DecimalMin(value = "0", inclusive = true, message = "Price must be positive") private BigDecimal price; + @Size(max = 1000, message = "Description is too long") private String description; + @URL(message = "Invalid cover image URL format") private String coverImage; - @NotEmpty + @NotEmpty(message = "At least one category ID must be provided") private Set categoryIds; } diff --git a/src/main/java/com/origin/bookstore/dto/cartitem/CartItemRequestDto.java b/src/main/java/com/origin/bookstore/dto/cartitem/CartItemRequestDto.java index 7d306eb..4f02a0e 100644 --- a/src/main/java/com/origin/bookstore/dto/cartitem/CartItemRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/cartitem/CartItemRequestDto.java @@ -4,10 +4,10 @@ import jakarta.validation.constraints.Positive; public record CartItemRequestDto( - @NotNull - @Positive + @NotNull(message = "Book id cannot be null") + @Positive(message = "Book id must be positive") Long bookId, - @Positive + @Positive(message = "Book quantity must be positive") int quantity ) {} diff --git a/src/main/java/com/origin/bookstore/dto/cartitem/UpdateCartItemRequestDto.java b/src/main/java/com/origin/bookstore/dto/cartitem/UpdateCartItemRequestDto.java index daf3a2d..9a91c40 100644 --- a/src/main/java/com/origin/bookstore/dto/cartitem/UpdateCartItemRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/cartitem/UpdateCartItemRequestDto.java @@ -3,6 +3,6 @@ import jakarta.validation.constraints.Min; public record UpdateCartItemRequestDto( - @Min(1) + @Min(value = 1, message = "Quantity must be in minimum of 1") int quantity ) {} diff --git a/src/main/java/com/origin/bookstore/dto/category/CreateCategoryRequestDto.java b/src/main/java/com/origin/bookstore/dto/category/CreateCategoryRequestDto.java index 15725c0..6a31e10 100644 --- a/src/main/java/com/origin/bookstore/dto/category/CreateCategoryRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/category/CreateCategoryRequestDto.java @@ -4,7 +4,7 @@ import jakarta.validation.constraints.Size; public record CreateCategoryRequestDto( - @NotBlank + @NotBlank(message = "Category name cannot be null") @Size(min = 1, max = 100) String name, String description diff --git a/src/main/java/com/origin/bookstore/dto/order/OrderRequestDto.java b/src/main/java/com/origin/bookstore/dto/order/OrderRequestDto.java index 969bd97..e7b0772 100644 --- a/src/main/java/com/origin/bookstore/dto/order/OrderRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/order/OrderRequestDto.java @@ -3,6 +3,6 @@ import jakarta.validation.constraints.NotBlank; public record OrderRequestDto( - @NotBlank + @NotBlank(message = "Order shipping address cannot be null") String shippingAddress ) {} diff --git a/src/main/java/com/origin/bookstore/dto/order/UpdateOrderStatusRequestDto.java b/src/main/java/com/origin/bookstore/dto/order/UpdateOrderStatusRequestDto.java index cd4ceb7..b81e279 100644 --- a/src/main/java/com/origin/bookstore/dto/order/UpdateOrderStatusRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/order/UpdateOrderStatusRequestDto.java @@ -3,6 +3,6 @@ import jakarta.validation.constraints.NotEmpty; public record UpdateOrderStatusRequestDto( - @NotEmpty + @NotEmpty(message = "Order status cannot be null") String status ) {} diff --git a/src/main/java/com/origin/bookstore/dto/user/UserLoginRequestDto.java b/src/main/java/com/origin/bookstore/dto/user/UserLoginRequestDto.java index 5627510..6601bd4 100644 --- a/src/main/java/com/origin/bookstore/dto/user/UserLoginRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/user/UserLoginRequestDto.java @@ -5,11 +5,11 @@ import jakarta.validation.constraints.Size; public record UserLoginRequestDto( - @NotBlank - @Size(min = 5, max = 20) - @Email + @NotBlank(message = "Email cannot be null") + @Size(min = 5, max = 100, message = "Email must be between 5 and 20 characters") + @Email(message = "Incorrect email format") String email, - @NotBlank - @Size(min = 3, max = 20) + @NotBlank(message = "Password cannot be null") + @Size(min = 8, max = 30, message = "Password must be between 8 and 20 characters") String password ) { } diff --git a/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java b/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java index 695f5eb..8f9ee50 100644 --- a/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java +++ b/src/main/java/com/origin/bookstore/dto/user/UserRegistrationRequestDto.java @@ -1,6 +1,7 @@ package com.origin.bookstore.dto.user; import com.origin.bookstore.validation.FieldMatch; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.Builder; @@ -15,18 +16,22 @@ fieldToMatch = "repeatPassword" ) public class UserRegistrationRequestDto { - @NotBlank - @Size(min = 1, max = 100) + @NotBlank(message = "Email is required") + @Email(message = "Please provide a valid email address") + @Size(min = 5, max = 100, message = "Email must be between 5 and 100 characters") private String email; - @NotBlank - @Size(min = 1, max = 100) + @NotBlank(message = "Password is required") + @Size(min = 8, max = 30, message = "Password must be between 8 and 30 characters") private String password; - @NotBlank - @Size(min = 1, max = 100) + @NotBlank(message = "Repeat password is required") + @Size(min = 8, max = 30, message = "Repeat password must be between 8 and 30 characters") private String repeatPassword; - @NotBlank + @NotBlank(message = "First name is required") + @Size(max = 255, message = "First name must be less than 255 characters") private String firstName; - @NotBlank + @NotBlank(message = "Last name is required") + @Size(max = 255, message = "Last name must be less than 255 characters") private String lastName; + @Size(max = 500, message = "Shipping address must be less than 500 characters") private String shippingAddress; } diff --git a/src/main/java/com/origin/bookstore/exception/CustomGlobalExceptionHandler.java b/src/main/java/com/origin/bookstore/exception/CustomGlobalExceptionHandler.java index 140ce28..180dc12 100644 --- a/src/main/java/com/origin/bookstore/exception/CustomGlobalExceptionHandler.java +++ b/src/main/java/com/origin/bookstore/exception/CustomGlobalExceptionHandler.java @@ -1,5 +1,6 @@ package com.origin.bookstore.exception; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.Map; import org.springframework.http.HttpStatus; @@ -13,34 +14,55 @@ public class CustomGlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity> handleMethodArgumentNotValidException( - MethodArgumentNotValidException ex) { + public ResponseEntity> handleValidationErrors( + MethodArgumentNotValidException ex + ) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", HttpStatus.BAD_REQUEST.value()); + Map errors = new HashMap<>(); - ex.getBindingResult().getFieldErrors().forEach(error - -> errors.put(error.getField(), error.getDefaultMessage())); - return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST); + ex.getBindingResult().getFieldErrors().forEach(error -> + errors.put(error.getField(), error.getDefaultMessage()) + ); + body.put("errors", errors); + + return new ResponseEntity<>(body, HttpStatus.BAD_REQUEST); } - @ExceptionHandler(Exception.class) - public ResponseEntity handleException(Exception ex) { - return new ResponseEntity<>("An unexpected error occurred", - HttpStatus.INTERNAL_SERVER_ERROR); + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity handleEntityNotFound(EntityNotFoundException ex) { + return buildErrorResponse(HttpStatus.NOT_FOUND, ex.getMessage()); + } + + @ExceptionHandler(RegistrationException.class) + public ResponseEntity handleRegistration(RegistrationException ex) { + return buildErrorResponse(HttpStatus.CONFLICT, ex.getMessage()); + } + + @ExceptionHandler(EmptyCartException.class) + public ResponseEntity handleEmptyCart(EmptyCartException ex) { + return buildErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage()); } @ExceptionHandler(AccessDeniedException.class) - public ResponseEntity handleAccessDeniedException(AccessDeniedException ex) { - return new ResponseEntity<>("Access denied", - HttpStatus.FORBIDDEN); + public ResponseEntity handleAccessDenied(AccessDeniedException ex) { + return buildErrorResponse(HttpStatus.FORBIDDEN, + "You don't have permission to access this resource"); } - @ExceptionHandler(EntityNotFoundException.class) - public ResponseEntity handleEntityNotFoundException(EntityNotFoundException ex) { - return new ResponseEntity<>("Entity not found exception occurred", - HttpStatus.NOT_FOUND); + @ExceptionHandler(Exception.class) + public ResponseEntity handleAllErrors(Exception ex) { + return buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, + "An unexpected error occurred"); } - @ExceptionHandler(RegistrationException.class) - public ResponseEntity handleRegistrationException(RegistrationException ex) { - return new ResponseEntity<>(ex.getMessage(), HttpStatus.CONFLICT); + private ResponseEntity buildErrorResponse(HttpStatus status, String message) { + Map body = new HashMap<>(); + body.put("timestamp", LocalDateTime.now()); + body.put("status", status.value()); + body.put("error", status.getReasonPhrase()); + body.put("message", message); + return new ResponseEntity<>(body, status); } } diff --git a/src/main/java/com/origin/bookstore/exception/EmptyCartException.java b/src/main/java/com/origin/bookstore/exception/EmptyCartException.java new file mode 100644 index 0000000..96f7b99 --- /dev/null +++ b/src/main/java/com/origin/bookstore/exception/EmptyCartException.java @@ -0,0 +1,7 @@ +package com.origin.bookstore.exception; + +public class EmptyCartException extends RuntimeException { + public EmptyCartException(String message) { + super(message); + } +} diff --git a/src/main/java/com/origin/bookstore/model/CartItem.java b/src/main/java/com/origin/bookstore/model/CartItem.java index 8c5bc98..210910c 100644 --- a/src/main/java/com/origin/bookstore/model/CartItem.java +++ b/src/main/java/com/origin/bookstore/model/CartItem.java @@ -16,12 +16,12 @@ import lombok.Setter; @Entity -@Table(name = "cart_items") @Builder @Getter @Setter @NoArgsConstructor @AllArgsConstructor +@Table(name = "cart_items") public class CartItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/Order.java b/src/main/java/com/origin/bookstore/model/Order.java index 7f69c64..72d7a79 100644 --- a/src/main/java/com/origin/bookstore/model/Order.java +++ b/src/main/java/com/origin/bookstore/model/Order.java @@ -33,9 +33,9 @@ @Getter @Setter @Builder -@Table(name = "orders") @NoArgsConstructor @AllArgsConstructor +@Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/OrderItem.java b/src/main/java/com/origin/bookstore/model/OrderItem.java index 1b725cc..1e2150c 100644 --- a/src/main/java/com/origin/bookstore/model/OrderItem.java +++ b/src/main/java/com/origin/bookstore/model/OrderItem.java @@ -24,9 +24,9 @@ @Getter @Setter @Builder -@Table(name = "order_items") @NoArgsConstructor @AllArgsConstructor +@Table(name = "order_items") public class OrderItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/model/ShoppingCart.java b/src/main/java/com/origin/bookstore/model/ShoppingCart.java index 39f6997..057211f 100644 --- a/src/main/java/com/origin/bookstore/model/ShoppingCart.java +++ b/src/main/java/com/origin/bookstore/model/ShoppingCart.java @@ -28,9 +28,9 @@ @Getter @Builder @Setter -@Table(name = "shopping_carts") @NoArgsConstructor @AllArgsConstructor +@Table(name = "shopping_carts") public class ShoppingCart { @Id private Long id; diff --git a/src/main/java/com/origin/bookstore/model/User.java b/src/main/java/com/origin/bookstore/model/User.java index 93e312f..037d579 100644 --- a/src/main/java/com/origin/bookstore/model/User.java +++ b/src/main/java/com/origin/bookstore/model/User.java @@ -24,14 +24,14 @@ import org.springframework.security.core.userdetails.UserDetails; @Entity +@SQLDelete(sql = "UPDATE users SET is_deleted = true WHERE id = ?") +@SQLRestriction("is_deleted=false") @Getter @Setter @Builder -@SQLDelete(sql = "UPDATE users SET is_deleted = true WHERE id = ?") -@SQLRestriction("is_deleted=false") -@Table(name = "users") @NoArgsConstructor @AllArgsConstructor +@Table(name = "users") public class User implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) diff --git a/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java b/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java index 0a5d471..b545cda 100644 --- a/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java +++ b/src/main/java/com/origin/bookstore/service/impl/OrderServiceImpl.java @@ -4,6 +4,7 @@ import com.origin.bookstore.dto.order.OrderResponseDto; import com.origin.bookstore.dto.order.UpdateOrderStatusRequestDto; import com.origin.bookstore.dto.orderitem.OrderItemResponseDto; +import com.origin.bookstore.exception.EmptyCartException; import com.origin.bookstore.exception.EntityNotFoundException; import com.origin.bookstore.mapper.OrderItemMapper; import com.origin.bookstore.mapper.OrderMapper; @@ -46,6 +47,10 @@ public OrderResponseDto save(User user, OrderRequestDto orderRequestDto) { + user.getId()) ); + if (shoppingCart.getCartItems().isEmpty()) { + throw new EmptyCartException("Your cart is empty. Add some books first!"); + } + Order order = orderMapper.toEntity(orderRequestDto); order.setOrderDateTime(LocalDateTime.now()); order.setStatus(Order.Status.PENDING); diff --git a/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java b/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java index 3b5b7a5..de94c29 100644 --- a/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/origin/bookstore/controller/AuthenticationControllerTest.java @@ -40,7 +40,7 @@ void register_ValidRequest_ReturnsUserResponse() throws Exception { mockMvc.perform(post(REGISTRATION_PATH) .content(objectMapper.writeValueAsString(request)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath(EMAIL_JSON_PATH).value(request.getEmail()) ); } @@ -53,7 +53,7 @@ void register_ValidRequest_ReturnsUserResponse() throws Exception { Sql.ExecutionPhase.BEFORE_TEST_METHOD ) void login_ValidRequest_ReturnsToken() throws Exception { - UserLoginRequestDto request = new UserLoginRequestDto("rudycooper@gmail.com", "example"); + UserLoginRequestDto request = new UserLoginRequestDto("rudycooper@gmail.com", "testpassword"); mockMvc.perform(post(LOGIN_PATH) .content(objectMapper.writeValueAsString(request)) diff --git a/src/test/java/com/origin/bookstore/controller/BookControllerTest.java b/src/test/java/com/origin/bookstore/controller/BookControllerTest.java index adfd25c..b70e904 100644 --- a/src/test/java/com/origin/bookstore/controller/BookControllerTest.java +++ b/src/test/java/com/origin/bookstore/controller/BookControllerTest.java @@ -54,7 +54,7 @@ void createBook_Request_ReturnsBookDto() throws Exception { mockMvc.perform(post(API_BOOKS_PATH) .contentType(MediaType.APPLICATION_JSON) .content(json)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath(TITLE_JSON_PATH).value(requestDto.getTitle())) .andExpect(jsonPath(AUTHOR_JSON_PATH).value(requestDto.getAuthor())); } diff --git a/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java b/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java index edefcf1..4f321c7 100644 --- a/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java +++ b/src/test/java/com/origin/bookstore/controller/OrderControllerTest.java @@ -44,7 +44,7 @@ void createOrder_ValidRequest_ReturnsOrderResponseDto() throws Exception { mockMvc.perform(post(ORDERS_URL) .content(objectMapper.writeValueAsString(requestDto)) .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) + .andExpect(status().isCreated()) .andExpect(jsonPath($_ID).value(1L)) .andExpect(jsonPath($_STATUS).value("PENDING")) .andExpect(jsonPath($_TOTAL).value(260)); diff --git a/src/test/resources/database/users/add-admin-to-users-table.sql b/src/test/resources/database/users/add-admin-to-users-table.sql index c21e69e..30d7eb9 100644 --- a/src/test/resources/database/users/add-admin-to-users-table.sql +++ b/src/test/resources/database/users/add-admin-to-users-table.sql @@ -1,5 +1,5 @@ INSERT INTO users (id, email, password, first_name, last_name) -VALUES (3, 'admin@gmail.com', '9df52223ed4894c4c3b774550e00819375540a579843eea778417d78cebb9f4b', 'Admin', 'Admin'); +VALUES (3, 'admin@gmail.com', '$2a$08$xitEMEXybmfbqq9jb7RA7.vwQBM5M1E.05jo8EtFOqBZLou2c0Pli', 'Admin', 'Admin'); INSERT INTO users_roles (user_id, role_id) VALUES (3, 1) \ No newline at end of file diff --git a/src/test/resources/database/users/add-user-to-users-table.sql b/src/test/resources/database/users/add-user-to-users-table.sql index 55b7c85..747c892 100644 --- a/src/test/resources/database/users/add-user-to-users-table.sql +++ b/src/test/resources/database/users/add-user-to-users-table.sql @@ -1,5 +1,5 @@ INSERT INTO users (id, email, password, first_name, last_name) -VALUES (7, 'rudycooper@gmail.com', '$2a$08$n4TvfZDh6IH6QnJ9wQrAzO6gW3Yq6dt0QIltCOD4FO9pIX9YOP5C2', 'Rudy', 'Cooper'); +VALUES (7, 'rudycooper@gmail.com', '$2a$08$xitEMEXybmfbqq9jb7RA7.vwQBM5M1E.05jo8EtFOqBZLou2c0Pli', 'Rudy', 'Cooper'); INSERT INTO users_roles (user_id, role_id) VALUES (7, 2); \ No newline at end of file