Skip to content

Commit

Permalink
feat: add to cart
Browse files Browse the repository at this point in the history
  • Loading branch information
lcaohoanq committed Dec 10, 2024
1 parent d0c6ce4 commit e6a5e76
Show file tree
Hide file tree
Showing 26 changed files with 630 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.lcaohoanq.shoppe.base.exception;

import lombok.NoArgsConstructor;

@NoArgsConstructor
public class OutOfStockException extends RuntimeException{

public OutOfStockException(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
String.format("%s/orders-details/**", apiPrefix),
String.format("%s/orders/**", apiPrefix),
String.format("%s/assets/**", apiPrefix),
String.format("%s/carts/**", apiPrefix),
"/error"
).permitAll()
// Swagger UI with basic auth
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package com.lcaohoanq.shoppe.domain.auth;

import com.github.javafaker.Faker;
import com.lcaohoanq.shoppe.component.JwtTokenUtils;
import com.lcaohoanq.shoppe.component.LocalizationUtils;
import com.lcaohoanq.shoppe.constant.Regex;
import com.lcaohoanq.shoppe.domain.cart.Cart;
import com.lcaohoanq.shoppe.domain.cart.CartProduct;
import com.lcaohoanq.shoppe.domain.cart.CartItem;
import com.lcaohoanq.shoppe.domain.cart.CartRepository;
import com.lcaohoanq.shoppe.domain.role.Role;
import com.lcaohoanq.shoppe.domain.cart.CartResponse;
import com.lcaohoanq.shoppe.domain.cart.CartService;
import com.lcaohoanq.shoppe.domain.user.UserResponse;
import com.lcaohoanq.shoppe.enums.Country;
import com.lcaohoanq.shoppe.enums.Currency;
import com.lcaohoanq.shoppe.enums.Gender;
import com.lcaohoanq.shoppe.enums.UserStatus;
import com.lcaohoanq.shoppe.exception.ExpiredTokenException;
import com.lcaohoanq.shoppe.exception.MalformBehaviourException;
Expand All @@ -36,10 +35,8 @@
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
Expand Down Expand Up @@ -71,11 +68,12 @@ public class AuthService implements IAuthService, DTOConverter {
private final UserService userService;
private final WalletRepository walletRepository;
private final CartRepository cartRepository;
private final CartService cartService;

@Override
@Transactional
public User register(AccountRegisterDTO accountRegisterDTO) throws Exception {

if (!accountRegisterDTO.password().matches(Regex.PASSWORD_REGEX)) {
throw new PasswordWrongFormatException(
"Password must contain at least one uppercase letter, one lowercase letter, one number, and one special character");
Expand All @@ -89,7 +87,7 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception {
if (userRepository.existsByEmail(email)) {
throw new DataIntegrityViolationException("Email already exists");
}

if (userRepository.existsByPhoneNumber(accountRegisterDTO.phoneNumber())) {
throw new DataIntegrityViolationException("Phone number already exists");
}
Expand All @@ -98,13 +96,15 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception {
((ServletRequestAttributes) Objects.requireNonNull(
RequestContextHolder.getRequestAttributes())).getRequest();
String acceptLanguage = request.getHeader("Accept-Language");
String preferredLanguage = String.valueOf(Optional.ofNullable(accountRegisterDTO.preferredLanguage())
.orElse(acceptLanguage == null || acceptLanguage.isEmpty()
? Country.UNITED_STATES
: Country.valueOf(acceptLanguage.toUpperCase())));

String preferredCurrency = String.valueOf(Optional.ofNullable(accountRegisterDTO.preferredCurrency())
.orElse(Currency.USD));
String preferredLanguage = String.valueOf(
Optional.ofNullable(accountRegisterDTO.preferredLanguage())
.orElse(acceptLanguage == null || acceptLanguage.isEmpty()
? Country.UNITED_STATES
: Country.valueOf(acceptLanguage.toUpperCase())));

String preferredCurrency = String.valueOf(
Optional.ofNullable(accountRegisterDTO.preferredCurrency())
.orElse(Currency.USD));

return Single.fromCallable(() -> {
User newUser = User.builder()
Expand Down Expand Up @@ -135,23 +135,14 @@ public User register(AccountRegisterDTO accountRegisterDTO) throws Exception {
.balance(0F)
.user(newUser) // Set the saved user
.build();

newWallet = walletRepository.save(newWallet);

Cart newCart = Cart.builder()
.user(newUser)
.cartProducts(new ArrayList<>())
.build();

CartProduct cartProduct = CartProduct.builder()
.cart(newCart)
.quantity(0)
.build();
newWallet = walletRepository.save(newWallet);

newCart.addCartProduct(cartProduct);
newCart = cartRepository.save(newCart);
Cart newCart = cartRepository.findById
(cartService.create(newUser.getId()).id())
.orElseThrow(() -> new DataNotFoundException("Cart not found"));

// Step 3: Set the wallet on the user and save the user again
// Step 3: Set the wallet on the user and save the user again
newUser.setWallet(newWallet);
newUser.setCart(newCart);
userRepository.save(newUser);
Expand All @@ -171,7 +162,7 @@ public String login(String email, String password) throws Exception {
localizationUtils.getLocalizedMessage(MessageKey.WRONG_PHONE_PASSWORD));
}
User existingUser = optionalUser.get();

existingUser.setLastLoginTimestamp(LocalDateTime.now());
userRepository.save(existingUser);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.lcaohoanq.shoppe.domain.cart;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotNull;

public record AddNewCartDTO(
@JsonProperty("user_id")
@NotNull(message = "User id is required when creating a new cart")
Long userId
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,20 @@ public class Cart extends BaseEntity {
private User user;

@OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, orphanRemoval = true)
private List<CartProduct> cartProducts = new ArrayList<>();
private List<CartItem> cartItems = new ArrayList<>();

public void addCartProduct(CartProduct cartProduct) {
if (cartProducts == null) {
cartProducts = new ArrayList<>();
public void addCartProduct(CartItem cartItem) {
if (cartItems == null) {
cartItems = new ArrayList<>();
}
cartProducts.add(cartProduct);
cartProduct.setCart(this);
cartItems.add(cartItem);
cartItem.setCart(this);
}

public void removeCartProduct(CartProduct cartProduct) {
if (cartProducts != null) {
cartProducts.remove(cartProduct);
cartProduct.setCart(null);
public void removeCartProduct(CartItem cartItem) {
if (cartItems != null) {
cartItems.remove(cartItem);
cartItem.setCart(null);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.lcaohoanq.shoppe.domain.cart;

import com.lcaohoanq.shoppe.api.ApiResponse;
import com.lcaohoanq.shoppe.domain.user.IUserService;
import com.lcaohoanq.shoppe.domain.user.User;
import com.lcaohoanq.shoppe.exception.MethodArgumentNotValidException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("${api.prefix}/carts")
@Slf4j
@RequiredArgsConstructor
public class CartController {

private final ICartItemService cartItemService;
private final IUserService userService;

@GetMapping("")
public ResponseEntity<ApiResponse<CartResponse>> getAll(
@RequestParam(value = "page", defaultValue = "0") int page,
@RequestParam(value = "limit", defaultValue = "10") int limit
) {
return ResponseEntity.ok(
ApiResponse.<CartResponse>builder()
.data(null)
.build()
);
}

@PostMapping("/item")
@PreAuthorize("hasRole('ROLE_MEMBER')")
public ResponseEntity<ApiResponse<CartItemResponse>> addItem(
@Valid @RequestBody CartItemDTO cartItemDTO,
BindingResult result
) {

if (result.hasErrors()) {
throw new MethodArgumentNotValidException(result);
}

UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
User user = userService.findByUsername(userDetails.getUsername());

return ResponseEntity.ok(
ApiResponse.<CartItemResponse>builder()
.message("Add item to cart successfully")
.statusCode(HttpStatus.OK.value())
.isSuccess(true)
.data(cartItemService.createCartItem(user.getId(),cartItemDTO))
.build()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
import lombok.Setter;

@Entity
@Table(name = "cart_products")
@Table(name = "cart_items")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CartProduct extends BaseEntity {
public class CartItem extends BaseEntity {

@Id
@SequenceGenerator(name = "cartproducts_seq", sequenceName = "cartproducts_id_seq", allocationSize = 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package com.lcaohoanq.shoppe.domain.cart;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;

public record CartItemDTO(
@JsonProperty("product_id")
Long productId,
@Min(value = 1, message = "Product id must be greater than 0")
@NotNull(message = "Product id is required") Long productId,

@JsonProperty("quantity")
Integer quantity
@Min(value = 1, message = "Quantity must be greater than 0")
@NotNull(message = "Quantity is required") Integer quantity
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.lcaohoanq.shoppe.domain.cart;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CartItemRepository extends JpaRepository<CartItem, Long> {

List<CartItem> findByCartId(Long cartId);

Optional<CartItem> findByCartIdAndProductId(long cartId, long productId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.lcaohoanq.shoppe.domain.cart;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.lcaohoanq.shoppe.domain.product.Product;
import com.lcaohoanq.shoppe.domain.product.ProductResponse;
import java.time.LocalDateTime;

public record CartItemResponse(
@JsonProperty("id")
Long id,
@JsonProperty("cart_id")
Long cartId,
@JsonProperty("product")
ProductResponse product,
@JsonProperty("quantity")
int quantity,

@JsonProperty("created_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS")
LocalDateTime createdAt,

@JsonProperty("updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSSSSS")
LocalDateTime updatedAt
) {}
Loading

0 comments on commit e6a5e76

Please sign in to comment.