This commit is contained in:
2025-11-19 14:34:43 +07:00
parent 7395c32e26
commit d5d10fe7a6
8 changed files with 367 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
package com.example.project_it207_server.controller;
import com.example.project_it207_server.model.dto.request.AddWishlistRequest;
import com.example.project_it207_server.model.dto.response.WishlistPaginatedResponse;
import com.example.project_it207_server.model.dto.response.WishlistResponse;
import com.example.project_it207_server.service.AuthService;
import com.example.project_it207_server.service.WishlistService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/wishlists")
@RequiredArgsConstructor
public class WishlistController {
private final WishlistService wishlistService;
private final AuthService authService;
@PostMapping("/add")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> addToWishlist(
@RequestBody AddWishlistRequest request,
Authentication authentication) {
try {
// Lấy email từ token
String email = authentication.getName();
// Lấy userId từ email
Long userId = authService.getUserIdByEmail(email);
WishlistResponse response = wishlistService.addToWishlist(userId, request);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "Thêm vào danh sách ưu thích thành công");
result.put("data", response);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
@DeleteMapping("/remove/{productId}")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> removeFromWishlist(
@PathVariable Long productId,
Authentication authentication) {
try {
// Lấy email từ token
String email = authentication.getName();
// Lấy userId từ email
Long userId = authService.getUserIdByEmail(email);
wishlistService.removeFromWishlist(userId, productId);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "Xóa khỏi danh sách ưu thích thành công");
return ResponseEntity.ok(result);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
}
}
@GetMapping
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> getWishlist(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "createdAt") String sortBy,
Authentication authentication) {
try {
// Lấy email từ token
String email = authentication.getName();
// Lấy userId từ email
Long userId = authService.getUserIdByEmail(email);
WishlistPaginatedResponse response = wishlistService.getWishlist(userId, page, size, sortBy);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "Lấy danh sách ưu thích thành công");
result.put("data", response);
return ResponseEntity.ok(result);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
@GetMapping("/count")
@PreAuthorize("isAuthenticated()")
public ResponseEntity<?> getWishlistCount(Authentication authentication) {
try {
// Lấy email từ token
String email = authentication.getName();
// Lấy userId từ email
Long userId = authService.getUserIdByEmail(email);
long count = wishlistService.getWishlistCount(userId);
Map<String, Object> result = new HashMap<>();
result.put("success", true);
result.put("message", "Lấy số lượng danh sách ưu thích thành công");
result.put("data", count);
return ResponseEntity.ok(result);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
}

View File

@@ -0,0 +1,14 @@
package com.example.project_it207_server.model.dto.request;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AddWishlistRequest {
private Long productId;
}

View File

@@ -0,0 +1,18 @@
package com.example.project_it207_server.model.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WishlistPaginatedResponse {
private java.util.List<WishlistResponse> wishlists;
private int totalPages;
private long totalElements;
private int currentPage;
private int pageSize;
}

View File

@@ -0,0 +1,38 @@
package com.example.project_it207_server.model.dto.response;
import com.example.project_it207_server.model.entity.Product;
import com.example.project_it207_server.model.entity.Wishlist;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class WishlistResponse {
private Long wishlistId;
private Long productId;
private String productName;
private String productImage;
private BigDecimal price;
private String description;
private LocalDateTime addedAt;
public static WishlistResponse fromEntity(Wishlist wishlist) {
Product product = wishlist.getProduct();
return WishlistResponse.builder()
.wishlistId(wishlist.getWishlistId())
.productId(product.getProductId())
.productName(product.getProductName())
.productImage(product.getImageUrl())
.price(product.getPrice())
.description(product.getDescription())
.addedAt(wishlist.getCreatedAt())
.build();
}
}

View File

@@ -0,0 +1,40 @@
package com.example.project_it207_server.model.entity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Entity
@Table(name = "wishlist", uniqueConstraints = {
@UniqueConstraint(columnNames = {"user_id", "product_id"})
})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Wishlist {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long wishlistId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "product_id", nullable = false)
private Product product;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
}

View File

@@ -0,0 +1,31 @@
package com.example.project_it207_server.repository;
import com.example.project_it207_server.model.entity.Wishlist;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface WishlistRepository extends JpaRepository<Wishlist, Long> {
@Query("SELECT w FROM Wishlist w WHERE w.user.userId = :userId ORDER BY w.createdAt DESC")
Page<Wishlist> findByUserId(@Param("userId") Long userId, Pageable pageable);
@Query("SELECT w FROM Wishlist w WHERE w.user.userId = :userId AND w.product.productId = :productId")
Optional<Wishlist> findByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);
@Query("SELECT CASE WHEN COUNT(w) > 0 THEN true ELSE false END " +
"FROM Wishlist w WHERE w.user.userId = :userId AND w.product.productId = :productId")
boolean existsByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);
@Query("DELETE FROM Wishlist w WHERE w.user.userId = :userId AND w.product.productId = :productId")
void deleteByUserIdAndProductId(@Param("userId") Long userId, @Param("productId") Long productId);
@Query("SELECT COUNT(w) FROM Wishlist w WHERE w.user.userId = :userId")
long countByUserId(@Param("userId") Long userId);
}

View File

@@ -0,0 +1,89 @@
package com.example.project_it207_server.service;
import com.example.project_it207_server.model.dto.request.AddWishlistRequest;
import com.example.project_it207_server.model.dto.response.WishlistPaginatedResponse;
import com.example.project_it207_server.model.dto.response.WishlistResponse;
import com.example.project_it207_server.model.entity.Product;
import com.example.project_it207_server.model.entity.User;
import com.example.project_it207_server.model.entity.Wishlist;
import com.example.project_it207_server.repository.ProductRepository;
import com.example.project_it207_server.repository.UserRepository;
import com.example.project_it207_server.repository.WishlistRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class WishlistService {
private final WishlistRepository wishlistRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
@Transactional
public WishlistResponse addToWishlist(Long userId, AddWishlistRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("Người dùng không tồn tại"));
Product product = productRepository.findById(request.getProductId())
.orElseThrow(() -> new RuntimeException("Sản phẩm không tồn tại"));
boolean exists = wishlistRepository.existsByUserIdAndProductId(userId, request.getProductId());
if (exists) {
throw new RuntimeException("Sản phẩm đã có trong danh sách ưu thích");
}
Wishlist wishlist = Wishlist.builder()
.user(user)
.product(product)
.build();
Wishlist saved = wishlistRepository.save(wishlist);
return WishlistResponse.fromEntity(saved);
}
@Transactional
public void removeFromWishlist(Long userId, Long productId) {
Wishlist wishlist = wishlistRepository.findByUserIdAndProductId(userId, productId)
.orElseThrow(() -> new RuntimeException("Sản phẩm không trong danh sách ưu thích"));
wishlistRepository.delete(wishlist);
}
@Transactional(readOnly = true)
public WishlistPaginatedResponse getWishlist(Long userId, int page, int size, String sortBy) {
Sort.Direction direction = Sort.Direction.DESC;
if ("name".equalsIgnoreCase(sortBy)) {
sortBy = "product.name";
} else if ("price".equalsIgnoreCase(sortBy)) {
sortBy = "product.price";
} else {
sortBy = "createdAt";
}
Pageable pageable = PageRequest.of(page, size, Sort.by(direction, sortBy));
Page<Wishlist> wishlistPage = wishlistRepository.findByUserId(userId, pageable);
return WishlistPaginatedResponse.builder()
.wishlists(wishlistPage.getContent().stream()
.map(WishlistResponse::fromEntity)
.collect(Collectors.toList()))
.totalPages(wishlistPage.getTotalPages())
.totalElements(wishlistPage.getTotalElements())
.currentPage(page)
.pageSize(size)
.build();
}
@Transactional(readOnly = true)
public long getWishlistCount(Long userId) {
return wishlistRepository.countByUserId(userId);
}
}

View File

@@ -7,11 +7,16 @@ spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA & Hibernate Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.jpa.properties.hibernate.format_sql=true
# Redis Configuration
spring.redis.host=localhost
spring.redis.port=6379
# JWT Configuration
jwt.secret-key=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
jwt.expiration=86400000