Skip to content

Commit

Permalink
Implement Shopping Cart Management with entities ShoppingCart and Car…
Browse files Browse the repository at this point in the history
…tItem... (#13)

* Implement Shopping Cart Management with entities ShoppingCart and CartItem, endpoints for adding, updating, viewing, and deleting cart items, security configurations, Liquibase integration, pagination, sorting, and BookMapper.

* Implemented QuantityRequestDto and refactored related code

* CartItemService methods merged into ShoppingCartService to prevent code duplication and improve convenience. Fixed LazyInitializationException occurrences.
  • Loading branch information
nklimovych authored May 22, 2024
1 parent a94f80f commit 56913c9
Show file tree
Hide file tree
Showing 20 changed files with 454 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package mate.academy.bookstore.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import mate.academy.bookstore.dto.cart.CartItemRequestDto;
import mate.academy.bookstore.dto.cart.CartItemResponseDto;
import mate.academy.bookstore.dto.cart.QuantityRequestDto;
import mate.academy.bookstore.dto.cart.ShoppingCartDto;
import mate.academy.bookstore.model.User;
import mate.academy.bookstore.service.ShoppingCartService;
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.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 the shopping cart")
@RestController
@RequestMapping("/cart")
@RequiredArgsConstructor
public class ShoppingCartController {
private final ShoppingCartService shoppingCartService;

@GetMapping
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "Retrieve the shopping cart",
description = "Fetch the current shopping cart for the authenticated user")
public ShoppingCartDto getShoppingCart(@AuthenticationPrincipal User user) {
return shoppingCartService.getByUser(user);
}

@PostMapping
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "Add a new item to the shopping cart",
description = "Add a new item to the user's shopping cart")
public CartItemResponseDto addCartItem(@RequestBody @Valid CartItemRequestDto requestDto,
@AuthenticationPrincipal User user) {
return shoppingCartService.addCartItem(user, requestDto);
}

@PutMapping("/cart-items/{cartItemId}")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@ResponseStatus(HttpStatus.ACCEPTED)
@Operation(summary = "Modify the quantity of an item in the cart",
description = "Update the quantity of a specified item in the user's shopping cart")
public CartItemResponseDto updateCartItem(@PathVariable Long cartItemId,
@RequestBody QuantityRequestDto quantity) {
return shoppingCartService.updateQuantity(cartItemId, quantity);
}

@DeleteMapping("/cart-items/{cartItemId}")
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "Remove an item from the shopping cart",
description = "Delete a specified item from the user's shopping cart, if it exists")
public void deleteCartItem(@PathVariable Long cartItemId) {
shoppingCartService.deleteCartItem(cartItemId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.dto.cart;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class CartItemRequestDto {
@NotNull
private Long bookId;
@Min(1)
private int quantity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.bookstore.dto.cart;

import lombok.Data;

@Data
public class CartItemResponseDto {
private Long id;
private Long bookId;
private String bookTitle;
private int quantity;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mate.academy.bookstore.dto.cart;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class QuantityRequestDto {
@NotNull
@Min(1)
private int quantity;
}
11 changes: 11 additions & 0 deletions src/main/java/mate/academy/bookstore/dto/cart/ShoppingCartDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mate.academy.bookstore.dto.cart;

import java.util.Set;
import lombok.Data;

@Data
public class ShoppingCartDto {
private Long id;
private Long userId;
private Set<CartItemResponseDto> cartItems;
}
14 changes: 14 additions & 0 deletions src/main/java/mate/academy/bookstore/mapper/CartItemMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.cart.CartItemResponseDto;
import mate.academy.bookstore.model.CartItem;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(config = MapperConfig.class)
public interface CartItemMapper {
@Mapping(source = "book.id", target = "bookId")
@Mapping(source = "book.title", target = "bookTitle")
CartItemResponseDto toDto(CartItem cartItem);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.mapper;

import mate.academy.bookstore.config.MapperConfig;
import mate.academy.bookstore.dto.cart.ShoppingCartDto;
import mate.academy.bookstore.model.ShoppingCart;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(config = MapperConfig.class, uses = {CartItemMapper.class})
public interface ShoppingCartMapper {
@Mapping(source = "user.id", target = "userId")
ShoppingCartDto toDto(ShoppingCart shoppingCart);
}
36 changes: 36 additions & 0 deletions src/main/java/mate/academy/bookstore/model/CartItem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package mate.academy.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.Table;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@EqualsAndHashCode(of = {"id", "shoppingCart", "book"})
@Entity
@Table(name = "cart_items")
public class CartItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "shopping_cart_id", nullable = false)
private ShoppingCart shoppingCart;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", nullable = false)
private Book book;

@Column(nullable = false)
private int quantity;
}
41 changes: 41 additions & 0 deletions src/main/java/mate/academy/bookstore/model/ShoppingCart.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package mate.academy.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.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.SoftDelete;

@Getter
@Setter
@Entity
@SoftDelete
@NoArgsConstructor
@Table(name = "shopping_carts")
public class ShoppingCart {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)
private User user;

@OneToMany(mappedBy = "shoppingCart", cascade = CascadeType.REMOVE)
private Set<CartItem> cartItems = new HashSet<>();

public ShoppingCart(User user) {
this.user = user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@

import java.util.List;
import mate.academy.bookstore.model.Book;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface BookRepository extends JpaRepository<Book, Long>, JpaSpecificationExecutor<Book> {

@Query("FROM Book b JOIN FETCH b.categories")
List<Book> findAllBooks(Pageable pageable);

@EntityGraph(attributePaths = {"categories"})
Page<Book> findAll(Specification<Book> specification, Pageable pageable);

@EntityGraph(attributePaths = {"categories"})
Book findBookByIsbn(String isbn);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mate.academy.bookstore.repository.cart;

import java.util.Optional;
import mate.academy.bookstore.model.Book;
import mate.academy.bookstore.model.CartItem;
import mate.academy.bookstore.model.ShoppingCart;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

Optional<CartItem> findByShoppingCartAndBook(ShoppingCart shoppingCart, Book book);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package mate.academy.bookstore.repository.cart;

import java.util.Optional;
import mate.academy.bookstore.model.ShoppingCart;
import mate.academy.bookstore.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface ShoppingCartRepository extends JpaRepository<ShoppingCart, Long> {

@Query("FROM ShoppingCart sc JOIN FETCH sc.cartItems WHERE sc.user = :user")
Optional<ShoppingCart> findByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import java.util.Optional;
import mate.academy.bookstore.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface UserRepository extends JpaRepository<User, Long> {
boolean existsByEmailIgnoreCase(String email);

@Query("FROM User u JOIN FETCH u.roles WHERE u.email = :email")
Optional<User> findByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package mate.academy.bookstore.service;

import mate.academy.bookstore.dto.cart.CartItemRequestDto;
import mate.academy.bookstore.dto.cart.CartItemResponseDto;
import mate.academy.bookstore.dto.cart.QuantityRequestDto;
import mate.academy.bookstore.dto.cart.ShoppingCartDto;
import mate.academy.bookstore.model.ShoppingCart;
import mate.academy.bookstore.model.User;

public interface ShoppingCartService {

ShoppingCart create(User user);

ShoppingCartDto getByUser(User user);

CartItemResponseDto addCartItem(User user, CartItemRequestDto requestItemDto);

CartItemResponseDto updateQuantity(Long itemId, QuantityRequestDto quantity);

void deleteCartItem(Long cartItemId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public BookDto save(CreateBookRequestDto requestDto) {

@Override
public List<BookDto> findAll(Pageable pageable) {
return bookRepository.findAll(pageable).stream()
return bookRepository.findAllBooks(pageable).stream()
.map(bookMapper::toDto)
.toList();
}
Expand Down
Loading

0 comments on commit 56913c9

Please sign in to comment.