diff --git a/Book-Store.pdf b/Book-Store.pdf
new file mode 100644
index 0000000..b86a7da
Binary files /dev/null and b/Book-Store.pdf differ
diff --git a/Book-Store.png b/Book-Store.png
new file mode 100644
index 0000000..28b4105
Binary files /dev/null and b/Book-Store.png differ
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..dff39e4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# π― Project Overview
+This application provides functionalities for managing users, books, categories, orders, and shopping carts. The system ensures secure access with role-based authorization (USER and ADMIN). The app is designed for book sales, allowing users to:
+
+Create accounts and log in.
+Purchase books conveniently and quickly from home.
+Track the status of orders and know when they arrive at the post office.
+This is my first large-scale application, and I faced numerous challenges during its development. Each part was difficult but immensely educational, and I gained a lot of knowledge that will be invaluable for my future projects.
+
+# π Technologies Used
+- Spring Boot
+- Spring Security
+- Spring Data JPA
+- JWT (JSON Web Token)
+- Swagger
+- Liquibase
+- MapStruct
+- Lombok
+- Hibernate Validator
+- MySQL
+
+# π API Endpoints
+1. AuthenticationController
+- ```POST /auth/registration``` β Register a new user.
+- ```POST /auth/login``` β User authentication (login).
+2. BookController
+- ```GET /books``` β Retrieve all books with pagination (ADMIN access only).
+- ```GET /books/{id}``` β Retrieve a book by its ID.
+- ```POST /books``` β Create a new book (ADMIN access only).
+- ```PUT /books/{id}``` β Update a book by its ID (ADMIN access only).
+- ```DELETE /books/{id}``` β Delete a book by its ID (ADMIN access only).
+- ```GET /books/search``` β Search books by parameters.
+3. CategoryController
+- ```POST /categories``` β Create a new category (ADMIN access only).
+- ```GET /categories``` β Retrieve all categories.
+- ```GET /categories/{id}``` β Retrieve a category by its ID.
+- ```PUT /categories/{id}``` β Update a category by its ID (ADMIN access only).
+- ```DELETE /categories/{id}``` β Delete a category by its ID (ADMIN access only).
+- ```GET /categories/{id}/books``` β Retrieve all books in a category by its ID.
+4. OrderController
+- ```POST /orders``` β Create a new order (USER access only).
+- ```GET /orders``` β Retrieve all orders of the logged-in user (USER access only).
+- ```PATCH /orders/{id}``` β Update the status of an order (ADMIN access only).
+- ```GET /orders/{orderId}/items``` β Retrieve all items from a specific order (USER access only).
+- ```GET /orders/{orderId}/items/{itemId}``` β Retrieve a specific item from an order (USER access only).
+5. ShoppingCartController
+- ```GET /cart``` β Retrieve the current user's shopping cart.
+- ```POST /cart``` β Add a book to the shopping cart.
+- ```PUT /cart/items/{cartItemId}``` β Update the quantity of items in the cart.
+- ```DELETE /cart/items/{cartItemId}``` β Remove an item from the cart.
+
+# Models and relations
+
+
+
+# π Getting Started
+I have always loved books, especially those that help understand the meaning of human existence and provide fuel for thought. Inspired by this passion, I decided to create my own application, where users can purchase books from the comfort of their homes.
+
+# πΉVideo Overview of Program Functionality
+You can also find a video of the program at this link: https://www.loom.com/share/0c519d24efc04b64acf9a3e9096b40cb?sid=e3750a8b-c044-4b13-b382-535497228e21
+
+# Contacts
+- Email: greqit.work@gmail.com
+- [LinkedIn](https://www.linkedin.com/in/ivan-prystaia-7099a22b1/)
diff --git a/docker-compose.yml b/docker-compose.yml
index ae895a6..18673a6 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,11 @@
version: "3.8"
services:
+ redis:
+ image: redis:7-alpine
+ restart: unless-stopped
+ ports:
+ - "6379:6379"
+
mysqldb:
platform: linux/amd64
image: mysql
@@ -18,6 +24,8 @@ services:
depends_on:
mysqldb:
condition: service_healthy
+ redis:
+ condition: service_started
restart: on-failure
image: book-store
build: .
@@ -33,7 +41,10 @@ services:
"spring.jpa.properties.hibernate.dialect" : "$SPRING_JPA_PROPERTIES_HIBERNATE_DIALECT",
"spring.datasource.driver-class-name" : "$SPRING_DATASOURSE_DRIVER_CLASS_NAME",
"spring.jpa.hibernate.ddl-auto" : "$SPRING_JPA_HIBERNATE_DDL_AUTO",
- "jwt.expiration": "$JWT_EXPIRATION_MS",
- "jwt.secret": "$JWT_SECRET"
+ "jwt.access.expiration": "$JWT_ACCESS_EXPIRATION_MS",
+ "jwt.refresh.expiration": "$JWT_REFRESH_EXPIRATION_MS",
+ "jwt.secret": "$JWT_SECRET",
+ "spring.data.redis.host": "redis",
+ "spring.data.redis.port": 6379
}'
JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
diff --git a/pom.xml b/pom.xml
index d17cb27..8582478 100644
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
3.3.1
- mare.academy
+ mate.academy
Spring-Boot-web
0.0.1-SNAPSHOT
Spring-Boot-web
@@ -39,6 +39,10 @@
8.0.1.Final
3.3.2
0.11.5
+ 3.1.1
+
+ https://raw.githubusercontent.com/mate-academy/style-guides/master/java/checkstyle.xml
+
@@ -60,6 +64,17 @@
1.6.14
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.1.0
+
+
+
+ io.swagger.core.v3
+ swagger-annotations
+ 2.2.25
+
io.jsonwebtoken
@@ -84,6 +99,11 @@
spring-boot-starter-security
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
org.hibernate.validator
hibernate-validator
@@ -121,11 +141,10 @@
test
-
- mysql
- mysql-connector-java
- 8.0.33
+ com.mysql
+ mysql-connector-j
+ 9.1.0
@@ -224,6 +243,27 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ ${maven.checkstyle.plugin.version}
+
+
+ compile
+
+ check
+
+
+
+
+ **/src/main/java/**
+ **/target/generated-sources/**
+ ${maven.checkstyle.plugin.configLocation}
+ true
+ true
+ false
+
+
diff --git a/src/main/java/mate/academy/springbootwebgreqit/config/SecurityConfig.java b/src/main/java/mate/academy/springbootwebgreqit/config/SecurityConfig.java
index cf61a2c..fbac8df 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/config/SecurityConfig.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/config/SecurityConfig.java
@@ -1,6 +1,7 @@
package mate.academy.springbootwebgreqit.config;
import lombok.RequiredArgsConstructor;
+import mate.academy.springbootwebgreqit.security.BookRateLimitFilter;
import mate.academy.springbootwebgreqit.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -22,6 +23,7 @@
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
+ private final BookRateLimitFilter bookRateLimitFilter;
@Bean
public PasswordEncoder getPasswordEncoder() {
@@ -46,6 +48,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter,
UsernamePasswordAuthenticationFilter.class)
+ .addFilterAfter(bookRateLimitFilter, JwtAuthenticationFilter.class)
.userDetailsService(userDetailsService)
.build();
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/controller/AuthenticationController.java b/src/main/java/mate/academy/springbootwebgreqit/controller/AuthenticationController.java
index b689b75..86192b7 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/controller/AuthenticationController.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/controller/AuthenticationController.java
@@ -1,7 +1,12 @@
package mate.academy.springbootwebgreqit.controller;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
+import mate.academy.springbootwebgreqit.dto.user.RefreshTokenRequestDto;
import mate.academy.springbootwebgreqit.dto.user.UserLoginRequestDto;
import mate.academy.springbootwebgreqit.dto.user.UserLoginResponseDto;
import mate.academy.springbootwebgreqit.dto.user.UserRegistrationRequestDto;
@@ -13,6 +18,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+@Tag(name = "Auth managemant", description = "Endpoint to auth")
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@@ -20,15 +26,33 @@ public class AuthenticationController {
private final UserService userService;
private final AuthenticationService authenticationService;
+ @Operation(summary = "Register a new user")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "User registered successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid input data")
+ })
@PostMapping("/registration")
- public UserResponseDto register(@RequestBody
- @Valid UserRegistrationRequestDto requestBody) {
+ public UserResponseDto register(@RequestBody @Valid UserRegistrationRequestDto requestBody) {
return userService.register(requestBody);
}
+ @Operation(summary = "Authenticate a user")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "User authenticated successfully"),
+ @ApiResponse(responseCode = "401", description = "Invalid credentials")
+ })
@PostMapping("/login")
- public UserLoginResponseDto login(@RequestBody
- @Valid UserLoginRequestDto response) {
+ public UserLoginResponseDto login(@RequestBody @Valid UserLoginRequestDto response) {
return authenticationService.authenticate(response);
}
+
+ @Operation(summary = "Refresh access token using refresh token")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Tokens issued successfully"),
+ @ApiResponse(responseCode = "401", description = "Invalid refresh token")
+ })
+ @PostMapping("/refresh")
+ public UserLoginResponseDto refresh(@RequestBody @Valid RefreshTokenRequestDto request) {
+ return authenticationService.refreshAccessToken(request);
+ }
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/controller/BookController.java b/src/main/java/mate/academy/springbootwebgreqit/controller/BookController.java
index ca81229..1440833 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/controller/BookController.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/controller/BookController.java
@@ -1,6 +1,8 @@
package mate.academy.springbootwebgreqit.controller;
-import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.BookDto;
@@ -27,40 +29,68 @@ public class BookController {
private final BookService bookService;
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Get all books with pagination")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
+ @ApiResponse(responseCode = "403", description = "Access denied")
+ })
@GetMapping
- @ApiOperation(value = "get all books with pagination")
public Page getAll(Pageable pageable) {
return bookService.findAll(pageable);
}
+ @PreAuthorize("hasAnyRole('USER','ADMIN')")
+ @Operation(summary = "Get book by id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Book retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Book not found")
+ })
@GetMapping("/{id}")
- @ApiOperation(value = "Get book by id")
public BookDto getBookById(@Valid @PathVariable Long id) {
return bookService.findById(id);
}
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Create a book")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Book created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid input data")
+ })
@PostMapping
- @ApiOperation(value = "create a book")
public BookDto createBook(@Valid @RequestBody CreateBookRequestDto requestDto) {
return bookService.save(requestDto);
}
- @PutMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
- public BookDto updateBook(@PathVariable Long id, @RequestBody @Valid UpdateBookRequestDto updateBookRequestDto) {
+ @Operation(summary = "Update a book")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Book updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Book not found")
+ })
+ @PutMapping("/{id}")
+ public BookDto updateBook(@PathVariable Long id,
+ @RequestBody @Valid UpdateBookRequestDto updateBookRequestDto) {
return bookService.update(updateBookRequestDto);
}
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Delete a book")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Book deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Book not found")
+ })
@DeleteMapping("/{id}")
- @ApiOperation(value = "delete a book")
public void deleteBook(@PathVariable Long id) {
bookService.deleteById(id);
}
+ @PreAuthorize("hasAnyRole('USER','ADMIN')")
+ @Operation(summary = "Search a book")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid search parameters")
+ })
@GetMapping("/search")
- @ApiOperation(value = "search a book")
public Page searchBooks(BookSearchParameters searchParameters, Pageable pageable) {
return bookService.search(searchParameters, pageable);
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/controller/CategoryController.java b/src/main/java/mate/academy/springbootwebgreqit/controller/CategoryController.java
index 8f184af..408ce00 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/controller/CategoryController.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/controller/CategoryController.java
@@ -1,5 +1,8 @@
package mate.academy.springbootwebgreqit.controller;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.BookDtoWithoutCategotyIds;
@@ -26,34 +29,65 @@ public class CategoryController {
private final CategoryService categoryService;
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Create a new category")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "201", description = "Category created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid input data")
+ })
@PostMapping
public CategoryDto createCategory(@Valid @RequestBody CreateCategoryRequestDto categoryDto) {
return categoryService.save(categoryDto);
}
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Get all categories")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Categories retrieved successfully"),
+ @ApiResponse(responseCode = "403", description = "Access denied")
+ })
@GetMapping
public List getAll() {
return categoryService.findAll();
}
+ @Operation(summary = "Get category by id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Category retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Category not found")
+ })
@GetMapping("/{id}")
public CategoryDto getCategoryById(@Valid @PathVariable Long id) {
return categoryService.getById(id);
}
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Update a category")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Category updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Category not found")
+ })
@PutMapping("/{id}")
- public CategoryDto updateCategory(@PathVariable Long id, @RequestBody @Valid UpdateCategoryRequestDto categoryDto) {
+ public CategoryDto updateCategory(@PathVariable Long id,
+ @RequestBody @Valid UpdateCategoryRequestDto categoryDto) {
return categoryService.update(id, categoryDto);
}
@PreAuthorize("hasRole('ADMIN')")
+ @Operation(summary = "Delete a category")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Category deleted successfully"),
+ @ApiResponse(responseCode = "404", description = "Category not found")
+ })
@DeleteMapping("/{id}")
public void deleteCategory(@PathVariable Long id) {
categoryService.deleteById(id);
}
+ @Operation(summary = "Get books by category id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Books retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Category not found")
+ })
@GetMapping("/{id}/books")
public List getBooksByCategoryId(@PathVariable Long id) {
return categoryService.findBooksByCategoryId(id);
diff --git a/src/main/java/mate/academy/springbootwebgreqit/controller/OrderController.java b/src/main/java/mate/academy/springbootwebgreqit/controller/OrderController.java
index 35fbf29..fb3c1c6 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/controller/OrderController.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/controller/OrderController.java
@@ -1,6 +1,8 @@
package mate.academy.springbootwebgreqit.controller;
-import io.swagger.annotations.ApiOperation;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.order.CreateOrderRequestDto;
@@ -28,14 +30,22 @@ public class OrderController {
private final OrderService orderService;
@PostMapping
- @ApiOperation("Make order")
+ @Operation(summary = "Make order")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Order created successfully"),
+ @ApiResponse(responseCode = "400", description = "Invalid input data")
+ })
@PreAuthorize("hasRole('USER')")
public OrderResponseDto addOrder(@RequestParam Long userId,
@RequestBody CreateOrderRequestDto createOrderRequestDto) {
return orderService.addOrder(userId, createOrderRequestDto);
}
- @ApiOperation("Get all orders")
+ @Operation(summary = "Get all orders")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Orders retrieved successfully"),
+ @ApiResponse(responseCode = "403", description = "Access denied")
+ })
@GetMapping
@PreAuthorize("hasRole('USER')")
public List getAllOrders(@RequestParam Long userId) {
@@ -44,26 +54,37 @@ public List getAllOrders(@RequestParam Long userId) {
@PreAuthorize("hasRole('ADMIN')")
@PatchMapping("/{id}")
- @ApiOperation("Update order status by id")
+ @Operation(summary = "Update order status by id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Order status updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found")
+ })
public OrderResponseDto updateOrderStatus(@PathVariable Long id,
@RequestBody @Valid OrderRequestDto requestDto) {
return orderService.updateOrderStatus(id, requestDto);
}
+ @Operation(summary = "Get all items from order by order id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Order items retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Order not found")
+ })
@GetMapping("/{orderId}/items")
- @ApiOperation("Get all items from order by order id")
@PreAuthorize("hasRole('USER')")
public List getAllItemsFromOrder(@PathVariable Long orderId,
Authentication authentication) {
return orderService.getAllItemsFromOrder(orderId);
}
+ @Operation(summary = "Get item from order by order id and item id")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Order item retrieved successfully"),
+ @ApiResponse(responseCode = "404", description = "Order or item not found")
+ })
@GetMapping("{orderId}/items/{itemId}")
- @ApiOperation("Get item from order by order id and item id")
@PreAuthorize("hasRole('USER')")
public OrderItemResponseDto getItemFromOrderById(@PathVariable Long orderId,
- @PathVariable Long itemId,
- Authentication authentication) {
+ @PathVariable Long itemId) {
return orderService.getItemFromOrderById(orderId, itemId);
}
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/controller/ShoppingCartController.java b/src/main/java/mate/academy/springbootwebgreqit/controller/ShoppingCartController.java
index b9c4c97..a7b1a24 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/controller/ShoppingCartController.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/controller/ShoppingCartController.java
@@ -1,9 +1,15 @@
package mate.academy.springbootwebgreqit.controller;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.springbootwebgreqit.dto.cartitem.CartItemRequestDto;
+import mate.academy.springbootwebgreqit.dto.shoppingcart.UpdateCartItemDto;
import mate.academy.springbootwebgreqit.dto.shoppingcart.ShoppingCartDto;
import mate.academy.springbootwebgreqit.service.ShoppingCartService;
+import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -11,7 +17,6 @@
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@@ -20,26 +25,46 @@
public class ShoppingCartController {
private final ShoppingCartService shoppingCartService;
- @GetMapping
- public ShoppingCartDto getShoppingCartForCurrentUser(@RequestParam Long userId) {
- return shoppingCartService.getShoppingCartForCurrentUser(userId);
+ @Operation(summary = "Get shopping cart for current user")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Shopping cart retrieved successfully"),
+ @ApiResponse(responseCode = "403", description = "Access denied")
+ })
+ @GetMapping("/{shoppingCartId}")
+ public ShoppingCartDto getShoppingCartForCurrentUser(Authentication authentication,
+ @PathVariable Long shoppingCartId) {
+ return shoppingCartService.getShoppingCartForCurrentUser(authentication, shoppingCartId);
}
+ @Operation(summary = "Add book to shopping cart")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Book added to shopping cart"),
+ @ApiResponse(responseCode = "400", description = "Invalid input data")
+ })
@PostMapping
public ShoppingCartDto addBookToShoppingCart(@RequestBody CartItemRequestDto cartItem,
- @RequestParam Long userId) {
- return shoppingCartService.addBookToShoppingCart(cartItem, userId);
+ Authentication authentication) {
+ return shoppingCartService.addBookToShoppingCart(cartItem, authentication);
}
+ @Operation(summary = "Update cart item quantity")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Cart item quantity updated successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart item not found")
+ })
@PutMapping("/items/{cartItemId}")
public ShoppingCartDto updateCartItemQuantity(@PathVariable Long cartItemId,
- @RequestParam int quantity,
- @RequestParam Long userId) {
- return shoppingCartService.updateCartItemQuantity(cartItemId, quantity, userId);
+ @RequestBody @Valid UpdateCartItemDto quantity) {
+ return shoppingCartService.updateCartItemQuantity(cartItemId, quantity);
}
+ @Operation(summary = "Remove cart item")
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "204", description = "Cart item removed successfully"),
+ @ApiResponse(responseCode = "404", description = "Cart item not found")
+ })
@DeleteMapping("/items/{cartItemId}")
- public void removeCartItem(@PathVariable Long cartItemId, @RequestParam Long userId) {
- shoppingCartService.removeCartItem(cartItemId, userId);
+ public void removeCartItem(@PathVariable Long cartItemId, Authentication authentication) {
+ shoppingCartService.removeCartItem(cartItemId, authentication);
}
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/dto/category/CreateCategoryRequestDto.java b/src/main/java/mate/academy/springbootwebgreqit/dto/category/CreateCategoryRequestDto.java
index 69f177b..43b6dd9 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/dto/category/CreateCategoryRequestDto.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/dto/category/CreateCategoryRequestDto.java
@@ -1,7 +1,6 @@
package mate.academy.springbootwebgreqit.dto.category;
import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Data
diff --git a/src/main/java/mate/academy/springbootwebgreqit/dto/shoppingcart/UpdateCartItemDto.java b/src/main/java/mate/academy/springbootwebgreqit/dto/shoppingcart/UpdateCartItemDto.java
new file mode 100644
index 0000000..b4ecbfd
--- /dev/null
+++ b/src/main/java/mate/academy/springbootwebgreqit/dto/shoppingcart/UpdateCartItemDto.java
@@ -0,0 +1,10 @@
+package mate.academy.springbootwebgreqit.dto.shoppingcart;
+
+import jakarta.validation.constraints.Positive;
+import lombok.Data;
+
+@Data
+public class UpdateCartItemDto {
+ @Positive
+ private int quantity;
+}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/dto/user/RefreshTokenRequestDto.java b/src/main/java/mate/academy/springbootwebgreqit/dto/user/RefreshTokenRequestDto.java
new file mode 100644
index 0000000..6823974
--- /dev/null
+++ b/src/main/java/mate/academy/springbootwebgreqit/dto/user/RefreshTokenRequestDto.java
@@ -0,0 +1,10 @@
+package mate.academy.springbootwebgreqit.dto.user;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+@Data
+public class RefreshTokenRequestDto {
+ @NotBlank
+ private String refreshToken;
+}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/dto/user/UserLoginResponseDto.java b/src/main/java/mate/academy/springbootwebgreqit/dto/user/UserLoginResponseDto.java
index 670023e..82f0088 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/dto/user/UserLoginResponseDto.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/dto/user/UserLoginResponseDto.java
@@ -2,9 +2,12 @@
import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
+@NoArgsConstructor
public class UserLoginResponseDto {
- private String token;
+ private String accessToken;
+ private String refreshToken;
}
diff --git a/src/main/java/mate/academy/springbootwebgreqit/exception/GlobalExceptionHandler.java b/src/main/java/mate/academy/springbootwebgreqit/exception/GlobalExceptionHandler.java
index 437c057..b73b33a 100644
--- a/src/main/java/mate/academy/springbootwebgreqit/exception/GlobalExceptionHandler.java
+++ b/src/main/java/mate/academy/springbootwebgreqit/exception/GlobalExceptionHandler.java
@@ -7,15 +7,18 @@
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity