diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java index 434d328..42ae3d4 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/ProductController.java @@ -104,5 +104,47 @@ public ApiResponse getProduct( return ApiResponse.ok(product); } + + + /** + * 제조사 목록 조회 API + * @param category 카테고리 + */ + @GetMapping("/mnf") + public ApiResponse> getManufacturers( + PageRequest pageRequest, + @RequestParam CategoryType category) { + + /// Pageable + Pageable pageable = org.springframework.data.domain.PageRequest.of( + pageRequest.getPage() - 1, + pageRequest.getSize(), + Sort.by(Sort.Direction.DESC, "id") + ); + + /// 서비스 + Slice responses = productService.getManufacturers(category.getValue(), pageable); + + /// SliceResponse 변환 후, 리턴 + return ApiResponse.ok(SliceResponse.from(responses)); + } + + + /** + * 전체 상품 개수 조회 API + * @param category 카테 고리 + */ + @GetMapping("/total") + public ApiResponse getTotalProducts( + @RequestParam CategoryType category + ) { + + /// 서비스 호출 + Long response = productService.getCountProducts(category.getValue()); + + /// 리턴 + return ApiResponse.ok(response); + } + } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java index b2e0d64..064abe3 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/dto/response/product/ProductDetailResponse.java @@ -34,6 +34,9 @@ public record ProductDetailResponse( @Schema(description = "상품 썸네일 이미지 URL", example = "https://example.com/product/101.jpg") String thumbnail, + @Schema(description = "원본 상품 구매 URL", example = "https://example.com/product") + String siteUrl, + @Schema(description = "제조사명", example = "독거미") String manufacturerName, @@ -64,6 +67,7 @@ public static ProductDetailResponse from(Product product) { return ProductDetailResponse.builder() .id(product.getId()) .thumbnail(product.getThumbnail()) + .siteUrl(product.getDetailPageUrl()) .manufacturerName(product.getManufacturer()) .category(product.getCategory()) .productName(product.getName()) @@ -80,6 +84,7 @@ public static ProductDetailResponse from(Product product, boolean likedByMe) { return ProductDetailResponse.builder() .id(product.getId()) .thumbnail(product.getThumbnail()) + .siteUrl(product.getDetailPageUrl()) .manufacturerName(product.getManufacturer()) .category(product.getCategory()) .productName(product.getName()) diff --git a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java index c083e33..093233e 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/in/web/swagger/ProductControllerSpec.java @@ -18,6 +18,15 @@ @Tag(name = "상품 조회 API", description = "상품 조회를 수행하는 API 입니다.") public interface ProductControllerSpec { + + @Operation( + summary = "상품 개수 조회 API", + description = "카테고리별 상품 전체 개수를 파악합니다." + ) + ApiResponse getTotalProducts( + @RequestParam CategoryType category + ); + /** * 상품 상세 조회 API * @param id 상품 아이디 @@ -66,5 +75,16 @@ ApiResponse> getProductList( @Parameter(hidden = true) @AuthenticationPrincipal PrincipalDetails principalDetails); + + + @Operation( + summary = "제조사 목록 조회 API", + description = "카테고리에 따라서 제조사 목록을 조회할 수 있습니다." + ) + ApiResponse> getManufacturers( + PageRequest pageRequest, + + @Parameter(description = "카테고리", required = true, example = "keyboard") + @RequestParam CategoryType category); } diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/ProductMongoAdapter.java b/src/main/java/site/kikihi/custom/platform/adapter/out/ProductMongoAdapter.java index 2734a47..94845d8 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/ProductMongoAdapter.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/ProductMongoAdapter.java @@ -26,7 +26,6 @@ public class ProductMongoAdapter implements ProductPort { private final ProductDocumentRepository documentRepository; - // ================= // 상품 상세 조회 // ================= @@ -94,6 +93,14 @@ public Slice getProducts(String category, List manufacturer, map(ProductDocument::toDomain); } + /// 카테고리 기반 제조사 목록 조회 + @Override + public List getManufacturers(String category) { + + /// DB 조회 + return documentRepository.findManufacturersByCategory(category); + } + // ================= // 상품 조회 // ================= @@ -120,6 +127,15 @@ public Map getProductsByIds(List productIds) { .collect(Collectors.toMap(Product::getId, Function.identity())); } + /** + * 상품 개수 조회 + * @param category 카테고리 + */ + @Override + public Long getProductsCount(String category) { + return documentRepository.countByCategory(category); + } + // ================= // 상품 추천 // ================= diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java b/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java index b8338b6..24807c2 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocument.java @@ -37,7 +37,7 @@ public class ProductDocument { private String manufacturer; - @Field("detail_purchase_url") + @Field("detail_page_url") private String detailPageUrl; @Field("options") diff --git a/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocumentRepository.java b/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocumentRepository.java index 4a35895..2eea375 100644 --- a/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocumentRepository.java +++ b/src/main/java/site/kikihi/custom/platform/adapter/out/mongo/product/ProductDocumentRepository.java @@ -42,6 +42,13 @@ Slice findByCategoryAndPriceRange( Pageable pageable ); + @Aggregation(pipeline = { + "{ '$match': { 'category': ?0 } }", + "{ '$group': { '_id': '$manufacturer' } }" + }) + List findManufacturersByCategory(String category); + + /// 카테고리 기반 목록 조회 (카테고리, 제조사, 가격 포함) @Query(""" { @@ -77,6 +84,8 @@ Slice findByCategoryAndManufacturerAndPriceRange( }) List findRandomExcludeIds(List excludedIds, int limit); + /// 상품 개수 + Long countByCategory(String category); // ================= // 삭제 함수 // ================= diff --git a/src/main/java/site/kikihi/custom/platform/application/in/product/ProductUseCase.java b/src/main/java/site/kikihi/custom/platform/application/in/product/ProductUseCase.java index 541c702..d9f9bf7 100644 --- a/src/main/java/site/kikihi/custom/platform/application/in/product/ProductUseCase.java +++ b/src/main/java/site/kikihi/custom/platform/application/in/product/ProductUseCase.java @@ -31,9 +31,15 @@ public interface ProductUseCase { // 카테고리별 목록 조회 (카테고리, 제조사, 가격 포함) Slice getProductsByCategoryIdAndManufacturerIdAndPrice(UUID userId, String categoryId, List manufacturer, Integer minPrice, Integer maxPrice, Pageable pageable); + /// 상품 개수 조회 + Long getCountProducts(String categoryId); + /// 상품 상세 조회 ProductDetailResponse getProduct(UUID userId, String id); + /// 카테고리 목록 조회 + Slice getManufacturers(String categoryId, Pageable pageable); + /// 외부 의존성 // 커스텀 키보드에 맞는 부품들 조회 Slice getProductsByLayout(UUID userId, String categoryId, CustomKeyboardLayout layout, Pageable pageable); diff --git a/src/main/java/site/kikihi/custom/platform/application/out/product/ProductPort.java b/src/main/java/site/kikihi/custom/platform/application/out/product/ProductPort.java index 24647a1..a1bdfc2 100644 --- a/src/main/java/site/kikihi/custom/platform/application/out/product/ProductPort.java +++ b/src/main/java/site/kikihi/custom/platform/application/out/product/ProductPort.java @@ -35,6 +35,10 @@ public interface ProductPort { /// 카테고리 기반 상품 목록 조회 (카테고리, 제조사, 가격 포함) Slice getProducts(String category, List manufacturer, Integer minPrice, Integer maxPrice, Pageable pageable); + /// 카테고리 기반 제조사 조회 + List getManufacturers(String category); + + // ================= // 상품 조회 // ================= @@ -44,6 +48,9 @@ public interface ProductPort { /// 상품 ID들 바탕으로 조회 Map getProductsByIds(List productIds); + /// 상품 개수 조회 + Long getProductsCount(String category); + // ================= // 상품 추천 // ================= diff --git a/src/main/java/site/kikihi/custom/platform/application/service/ProductService.java b/src/main/java/site/kikihi/custom/platform/application/service/ProductService.java index 4fe1895..88a8f20 100644 --- a/src/main/java/site/kikihi/custom/platform/application/service/ProductService.java +++ b/src/main/java/site/kikihi/custom/platform/application/service/ProductService.java @@ -119,6 +119,17 @@ public Slice getProductsByCategoryIdAndManufacturerIdAndPri return toProductListResponse(userId, categoryId, products); } + /** + * 카테고리에 따른 전체 상품 개수 + * @param categoryId 카테고리 + */ + @Override + public Long getCountProducts(String categoryId) { + + /// Port에서 조회 + return productPort.getProductsCount(categoryId); + } + // ================= // 상품 상세 조회 // ================= @@ -150,6 +161,27 @@ public ProductDetailResponse getProduct(UUID userId, String id) { } + /** + * 모든 제조사를 가져오는 로직입니다. + * @param categoryId 조회할 카테고리 ID + */ + @Override + public Slice getManufacturers(String categoryId, Pageable pageable) { + + /// Port에서 일단 전부 조회 + // TODO! DB가 적기 때문에, 일단은 다 가져와서 슬라이싱,,, 추후에 데이터의 개수가 많아지게 된다면 로직을 다시 생각해야 될 듯 + List manufacturers = productPort.getManufacturers(categoryId); + + // 페이징 적용 + int start = (int) pageable.getOffset(); + int end = Math.min(start + pageable.getPageSize(), manufacturers.size()); + List content = manufacturers.subList(start, end); + + boolean hasNext = end < manufacturers.size(); + + return new SliceImpl<>(content, pageable, hasNext); + } + // ======================== // 외부 의존성 // ========================