-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #10 from viacheslav-torbin/shopping-cart
Shopping cart implemented
- Loading branch information
Showing
17 changed files
with
439 additions
and
1 deletion.
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
src/main/java/org/bookstore/controller/ShoppingCartController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package org.bookstore.controller; | ||
|
||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.validation.Valid; | ||
import jakarta.validation.constraints.Positive; | ||
import lombok.RequiredArgsConstructor; | ||
import org.bookstore.dto.shoppingcart.CartItemRequestDto; | ||
import org.bookstore.dto.shoppingcart.CartItemUpdateDto; | ||
import org.bookstore.dto.shoppingcart.ShoppingCartDto; | ||
import org.bookstore.model.User; | ||
import org.bookstore.service.ShoppingCartService; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.security.access.prepost.PreAuthorize; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.validation.annotation.Validated; | ||
import org.springframework.web.bind.annotation.DeleteMapping; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.PathVariable; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
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.ResponseStatus; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
@Tag(name = "Shopping cart management", | ||
description = "Endpoints for managing user`s shopping carts") | ||
@RestController | ||
@RequiredArgsConstructor | ||
@Validated | ||
@RequestMapping(value = "/cart") | ||
public class ShoppingCartController { | ||
private final ShoppingCartService cartService; | ||
|
||
@Operation(summary = "Get the shopping cart of a user") | ||
@GetMapping | ||
@PreAuthorize("hasRole('USER')") | ||
public ShoppingCartDto getShoppingCart(Authentication authentication) { | ||
User user = (User) authentication.getPrincipal(); | ||
return cartService.getCart(user.getId()); | ||
} | ||
|
||
@Operation(summary = "Add a book to the shopping cart", description = """ | ||
Add a new book or update the quantity of existing in the cart of user""") | ||
@PostMapping | ||
@PreAuthorize("hasRole('USER')") | ||
public ShoppingCartDto addBook(Authentication authentication, | ||
@RequestBody @Valid CartItemRequestDto cartItem) { | ||
User user = (User) authentication.getPrincipal(); | ||
return cartService.addBook(user.getId(), cartItem); | ||
} | ||
|
||
@Operation(summary = "Update the number of books in the cart", description = """ | ||
Update the number of existing books in the cart of a logged-in user. | ||
Parameters : cart item id and new quantity""") | ||
@PutMapping("/cart-items/{cartItemId}") | ||
@PreAuthorize("hasRole('USER')") | ||
public ShoppingCartDto updateBookQuantity(Authentication authentication, | ||
@PathVariable @Positive Long cartItemId, | ||
@RequestBody @Valid CartItemUpdateDto item) { | ||
User user = (User) authentication.getPrincipal(); | ||
return cartService.updateQuantity(user.getId(), cartItemId, item); | ||
} | ||
|
||
@Operation(summary = "Delete book from the cart of user", description = """ | ||
Delete book from the cart of a user. | ||
Parameter: id of cart item""") | ||
@DeleteMapping("/cart-items/{cartItemId}") | ||
@PreAuthorize("hasRole('USER')") | ||
@ResponseStatus(HttpStatus.NO_CONTENT) | ||
public void deleteBookFromCart(Authentication authentication, | ||
@PathVariable @Positive Long cartItemId) { | ||
User user = (User) authentication.getPrincipal(); | ||
cartService.deleteBook(user.getId(), cartItemId); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/org/bookstore/dto/shoppingcart/CartItemDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.bookstore.dto.shoppingcart; | ||
|
||
public record CartItemDto(Long id, | ||
Long bookId, | ||
String bookTitle, | ||
Integer quantity) { | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/org/bookstore/dto/shoppingcart/CartItemRequestDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.bookstore.dto.shoppingcart; | ||
|
||
import jakarta.validation.constraints.NotNull; | ||
import jakarta.validation.constraints.Positive; | ||
|
||
public record CartItemRequestDto(@NotNull @Positive Long bookId, | ||
@NotNull @Positive Integer quantity) { | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/org/bookstore/dto/shoppingcart/CartItemUpdateDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package org.bookstore.dto.shoppingcart; | ||
|
||
import jakarta.validation.constraints.NotNull; | ||
import jakarta.validation.constraints.Positive; | ||
|
||
public record CartItemUpdateDto(@NotNull @Positive Integer quantity) { | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/java/org/bookstore/dto/shoppingcart/ShoppingCartDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package org.bookstore.dto.shoppingcart; | ||
|
||
import java.util.List; | ||
|
||
public record ShoppingCartDto(Long id, | ||
Long userId, | ||
List<CartItemDto> cartItems) { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package org.bookstore.mapper; | ||
|
||
import org.bookstore.dto.shoppingcart.CartItemDto; | ||
import org.bookstore.dto.shoppingcart.CartItemRequestDto; | ||
import org.bookstore.model.CartItem; | ||
import org.mapstruct.InjectionStrategy; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.Mapping; | ||
import org.mapstruct.NullValueCheckStrategy; | ||
|
||
@Mapper( | ||
componentModel = "spring", | ||
injectionStrategy = InjectionStrategy.CONSTRUCTOR, | ||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, | ||
implementationPackage = "<PACKAGE_NAME>.impl" | ||
) | ||
public interface CartItemMapper { | ||
@Mapping(source = "book.id", target = "bookId") | ||
@Mapping(source = "book.title", target = "bookTitle") | ||
CartItemDto toDto(CartItem cartItem); | ||
|
||
CartItem toCartItem(CartItemRequestDto itemDto); | ||
} | ||
|
25 changes: 25 additions & 0 deletions
25
src/main/java/org/bookstore/mapper/ShoppingCartMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.bookstore.mapper; | ||
|
||
import org.bookstore.dto.shoppingcart.CartItemDto; | ||
import org.bookstore.dto.shoppingcart.ShoppingCartDto; | ||
import org.bookstore.model.CartItem; | ||
import org.bookstore.model.ShoppingCart; | ||
import org.mapstruct.InjectionStrategy; | ||
import org.mapstruct.Mapper; | ||
import org.mapstruct.Mapping; | ||
import org.mapstruct.NullValueCheckStrategy; | ||
|
||
@Mapper( | ||
componentModel = "spring", | ||
injectionStrategy = InjectionStrategy.CONSTRUCTOR, | ||
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, | ||
implementationPackage = "<PACKAGE_NAME>.impl" | ||
) | ||
public interface ShoppingCartMapper { | ||
@Mapping(source = "user.id", target = "userId") | ||
ShoppingCartDto toDto(ShoppingCart shoppingCart); | ||
|
||
@Mapping(source = "book.id", target = "bookId") | ||
@Mapping(source = "book.title", target = "bookTitle") | ||
CartItemDto toCartItemDto(CartItem cartItem); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package org.bookstore.model; | ||
|
||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.OneToOne; | ||
import jakarta.persistence.Table; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Entity | ||
@Getter | ||
@Setter | ||
@Table(name = "cart_items") | ||
public class CartItem { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@JoinColumn(name = "shopping_cart_id", nullable = false) | ||
@ManyToOne(fetch = FetchType.LAZY, optional = false) | ||
private ShoppingCart shoppingCart; | ||
|
||
@JoinColumn(name = "book_id", nullable = false) | ||
@OneToOne(fetch = FetchType.LAZY, optional = false, orphanRemoval = true) | ||
private Book book; | ||
|
||
@Column(nullable = false) | ||
private int quantity; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package org.bookstore.model; | ||
|
||
import jakarta.persistence.CascadeType; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.OneToMany; | ||
import jakarta.persistence.OneToOne; | ||
import jakarta.persistence.Table; | ||
import java.util.HashSet; | ||
import java.util.Set; | ||
import lombok.Data; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.ToString; | ||
|
||
@Entity | ||
@Data | ||
@Table(name = "shopping_carts") | ||
public class ShoppingCart { | ||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@OneToOne(fetch = FetchType.LAZY, optional = false) | ||
@JoinColumn(name = "user_id", nullable = false) | ||
@EqualsAndHashCode.Exclude | ||
@ToString.Exclude | ||
private User user; | ||
|
||
@OneToMany(mappedBy = "shoppingCart", cascade = CascadeType.ALL, orphanRemoval = true) | ||
private Set<CartItem> cartItems = new HashSet<>(); | ||
|
||
public void addItemToCart(CartItem cartItem) { | ||
cartItem.setShoppingCart(this); | ||
cartItems.add(cartItem); | ||
} | ||
|
||
public void removeItemFromCart(CartItem cartItem) { | ||
cartItems.remove(cartItem); | ||
cartItem.setShoppingCart(null); | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/org/bookstore/repository/CartItemRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package org.bookstore.repository; | ||
|
||
import java.util.Optional; | ||
import org.bookstore.model.CartItem; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface CartItemRepository extends JpaRepository<CartItem, Long> { | ||
Optional<CartItem> findByIdAndShoppingCartId(Long cartItemId, Long id); | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/org/bookstore/repository/ShoppingCartRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package org.bookstore.repository; | ||
|
||
import org.bookstore.model.ShoppingCart; | ||
import org.springframework.data.jpa.repository.EntityGraph; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface ShoppingCartRepository extends JpaRepository<ShoppingCart, Long> { | ||
@EntityGraph(attributePaths = {"cartItems", "user", "cartItems.book"}) | ||
ShoppingCart findByUserId(Long id); | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/org/bookstore/service/ShoppingCartService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package org.bookstore.service; | ||
|
||
import org.bookstore.dto.shoppingcart.CartItemRequestDto; | ||
import org.bookstore.dto.shoppingcart.CartItemUpdateDto; | ||
import org.bookstore.dto.shoppingcart.ShoppingCartDto; | ||
import org.bookstore.model.User; | ||
|
||
public interface ShoppingCartService { | ||
void createShoppingCart(User user); | ||
|
||
ShoppingCartDto getCart(Long userId); | ||
|
||
ShoppingCartDto addBook(Long userId, CartItemRequestDto item); | ||
|
||
ShoppingCartDto updateQuantity(Long userId, Long cartItemId, CartItemUpdateDto itemDto); | ||
|
||
void deleteBook(Long userId, Long itemId); | ||
} |
103 changes: 103 additions & 0 deletions
103
src/main/java/org/bookstore/service/impl/ShoppingCartServiceImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package org.bookstore.service.impl; | ||
|
||
import jakarta.transaction.Transactional; | ||
import java.util.Objects; | ||
import java.util.Set; | ||
import lombok.RequiredArgsConstructor; | ||
import org.bookstore.dto.shoppingcart.CartItemRequestDto; | ||
import org.bookstore.dto.shoppingcart.CartItemUpdateDto; | ||
import org.bookstore.dto.shoppingcart.ShoppingCartDto; | ||
import org.bookstore.exceptions.EntityNotFoundException; | ||
import org.bookstore.mapper.CartItemMapper; | ||
import org.bookstore.mapper.ShoppingCartMapper; | ||
import org.bookstore.model.Book; | ||
import org.bookstore.model.CartItem; | ||
import org.bookstore.model.ShoppingCart; | ||
import org.bookstore.model.User; | ||
import org.bookstore.repository.BookRepository; | ||
import org.bookstore.repository.CartItemRepository; | ||
import org.bookstore.repository.ShoppingCartRepository; | ||
import org.bookstore.service.ShoppingCartService; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class ShoppingCartServiceImpl implements ShoppingCartService { | ||
private final CartItemRepository cartItemRepository; | ||
private final ShoppingCartRepository shoppingCartRepository; | ||
private final BookRepository bookRepository; | ||
private final ShoppingCartMapper shoppingCartMapper; | ||
private final CartItemMapper cartItemMapper; | ||
|
||
@Transactional | ||
@Override | ||
public void createShoppingCart(User user) { | ||
ShoppingCart shoppingCart = new ShoppingCart(); | ||
shoppingCart.setUser(user); | ||
shoppingCartRepository.save(shoppingCart); | ||
} | ||
|
||
@Transactional | ||
@Override | ||
public ShoppingCartDto getCart(Long userId) { | ||
return shoppingCartMapper.toDto(shoppingCartRepository.findByUserId(userId)); | ||
} | ||
|
||
@Transactional | ||
@Override | ||
public ShoppingCartDto addBook(Long userId, CartItemRequestDto itemDto) { | ||
Book book = bookRepository.findById(itemDto.bookId()) | ||
.orElseThrow(() -> new EntityNotFoundException( | ||
String.format("Book with id: %d is not found", itemDto.bookId()) | ||
)); | ||
ShoppingCart cart = shoppingCartRepository.findByUserId(userId); | ||
Set<CartItem> cartItems = cart.getCartItems(); | ||
for (CartItem item : cartItems) { | ||
if (Objects.equals(item.getBook().getId(), itemDto.bookId())) { | ||
item.setQuantity(item.getQuantity() + itemDto.quantity()); | ||
shoppingCartRepository.save(cart); | ||
return shoppingCartMapper.toDto(cart); | ||
} | ||
} | ||
addCartItemToShoppingCart(itemDto, book, cart); | ||
shoppingCartRepository.save(cart); | ||
return shoppingCartMapper.toDto(cart); | ||
} | ||
|
||
@Transactional | ||
@Override | ||
public ShoppingCartDto updateQuantity(Long userId, | ||
Long cartItemId, CartItemUpdateDto itemDto) { | ||
ShoppingCart cart = shoppingCartRepository.findByUserId(userId); | ||
CartItem cartItem = cartItemRepository | ||
.findByIdAndShoppingCartId(cartItemId, cart.getId()) | ||
.map(item -> { | ||
item.setQuantity(itemDto.quantity()); | ||
return item; | ||
}).orElseThrow(() -> new EntityNotFoundException( | ||
String.format("No cart item with id: %d for user: %d", cartItemId, userId) | ||
)); | ||
cartItemRepository.save(cartItem); | ||
return shoppingCartMapper.toDto(cart); | ||
} | ||
|
||
@Transactional | ||
@Override | ||
public void deleteBook(Long userId, Long itemId) { | ||
ShoppingCart shoppingCart = shoppingCartRepository.findByUserId(userId); | ||
CartItem cartItem = cartItemRepository | ||
.findByIdAndShoppingCartId(itemId, shoppingCart.getId()) | ||
.orElseThrow(() -> new EntityNotFoundException( | ||
String.format("No such cartItem with id %d in shopping cart", itemId) | ||
)); | ||
shoppingCart.removeItemFromCart(cartItem); | ||
} | ||
|
||
private void addCartItemToShoppingCart(CartItemRequestDto itemDto, | ||
Book book, | ||
ShoppingCart cart) { | ||
CartItem cartItem = cartItemMapper.toCartItem(itemDto); | ||
cartItem.setBook(book); | ||
cart.addItemToCart(cartItem); | ||
} | ||
} |
Oops, something went wrong.