diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java index c853794..77b7714 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/controller/PostsController.java @@ -10,6 +10,7 @@ import org.jboss.logging.MDC; import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; +import org.springframework.security.core.parameters.P; import org.springframework.web.bind.annotation.*; import site.billbill.apiserver.api.borrowPosts.dto.request.PostsRequest; import site.billbill.apiserver.api.borrowPosts.dto.response.PostsResponse; @@ -52,6 +53,23 @@ public BaseResponse getPostsController( Sort.Direction direction = "asc".equalsIgnoreCase(order) ? Sort.Direction.ASC : Sort.Direction.DESC; return new BaseResponse<>(postsService.ViewAllPostService(category,page,direction,sortBy)); } + @GetMapping("/search") + public BaseResponse getSearchPostsController( + @Parameter(name = "category", description = "카테고리 필터 (예: entire, camp, sports,tools )", example = "entire", in = ParameterIn.QUERY, required = false) + @RequestParam(value ="category",required = false,defaultValue = "entire") String category, + @Parameter(name = "page", description = "페이지 번호 (1부터 시작)", example = "1", in = ParameterIn.QUERY, required = false) + @RequestParam(value ="page",required = false,defaultValue = "1") int page, + @Parameter(name = "order", description = "정렬 방향 (asc: 오름차순, desc: 내림차순)", example = "desc", in = ParameterIn.QUERY, required = false) + @RequestParam(value ="order",required = false,defaultValue = "desc") String order, + @Parameter(name = "sortBy", description = "정렬 기준 (예: price, createdAt, likeCount)", example = "createdAt", in = ParameterIn.QUERY, required = true) + @RequestParam(value="sortBy",required = true,defaultValue = "accuracy") String sortBy, + @Parameter(name="keyword",description = "검색 키워드(예: 6인용+텐트)",in = ParameterIn.QUERY, required = true) + @RequestParam(value = "keyword",required = true) String keyword) { + + + Sort.Direction direction = "asc".equalsIgnoreCase(order) ? Sort.Direction.ASC : Sort.Direction.DESC; + return new BaseResponse<>(postsService.ViewSearchPostService(category, page, direction, sortBy,keyword)); + } @GetMapping("/{postId}") public BaseResponse getPostController(@PathVariable(value = "postId",required = true)String postId){ @@ -77,4 +95,5 @@ public BaseResponse updatePostController(@PathVariable(value="postId",re } + } diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java index 2d434f1..d214d29 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/converter/PostsConverter.java @@ -52,6 +52,7 @@ public static ItemsBorrowStatusJpaEntity toItemBorrowStatus(ItemsJpaEntity item, public static PostsResponse.Post toPost(ItemsJpaEntity item,ItemsBorrowJpaEntity borrowItem){ return PostsResponse.Post.builder() .postId(item.getId()) + .title(item.getTitle()) .image(Optional.ofNullable(item.getImages()) .filter(images -> !images.isEmpty()) .map(images -> images.get(0)) diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/dto/response/PostsResponse.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/dto/response/PostsResponse.java index 24b172c..0407387 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/dto/response/PostsResponse.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/dto/response/PostsResponse.java @@ -27,6 +27,7 @@ public static class ViewAllResultResponse{ public static class Post{ private String postId; private String image; + private String title; private int price; private String userId; private String userName; diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java index db54e11..9029484 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsService.java @@ -13,4 +13,6 @@ public interface PostsService { String deletePostService(String postId,String userId); String UpdatePostService(String postId,String userId,PostsRequest.UploadRequest request); + + PostsResponse.ViewAllResultResponse ViewSearchPostService(String category, int page, Sort.Direction direction, String orderType,String keyword); } diff --git a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java index 2634ac6..67eeb8d 100644 --- a/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java +++ b/src/main/java/site/billbill/apiserver/api/borrowPosts/service/PostsServiceImpl.java @@ -77,42 +77,9 @@ public PostsResponse.UploadResponse uploadPostService(PostsRequest.UploadRequest public PostsResponse.ViewAllResultResponse ViewAllPostService( String category, int page, Sort.Direction direction, String orderType) { - // 기본 정렬 필드와 방향 설정 - String sortField = switch (orderType) { - case "price" -> "price"; - case "createdAt" -> "createdAt"; - case "likeCount" -> "likeCount"; - default -> "createdAt"; // 기본 정렬 - }; - - direction = (direction == null) ? Sort.Direction.DESC : direction; - - Pageable pageable = PageRequest.of( - Math.max(0, page - 1), // 페이지 번호 조정 (0부터 시작) - 20, - Sort.by(direction, sortField) - ); - - // Repository 호출 - Page itemsPage = itemsRepository.findItemsWithConditions(category, pageable, sortField); - - // 빈 결과 체크 - if (itemsPage.isEmpty()) { - log.warn("No items found for category: {}, page: {}, sortField: {}", category, page, sortField); - return PostsConverter.toViewAllList(List.of()); - } - - // 데이터 변환 - List borrowItems = itemsPage.getContent().stream() - .map(item -> { - ItemsBorrowJpaEntity borrowItem = itemsBorrowRepository.findById(item.getId()).orElse(null); - if (borrowItem == null) { - log.warn("No borrow item found for item ID: {}", item.getId()); - } - return PostsConverter.toPost(item, borrowItem); - }).toList(); - - return PostsConverter.toViewAllList(borrowItems); + Pageable pageable = createPageable(page, direction, orderType); + List items = findAndConvertItems(category, pageable, null); + return PostsConverter.toViewAllList(items); } public PostsResponse.ViewPostResponse ViewPostService(String postId){ @@ -193,4 +160,55 @@ public String UpdatePostService(String postId,String userId,PostsRequest.UploadR } return "success"; } + public PostsResponse.ViewAllResultResponse ViewSearchPostService(String category, int page, Sort.Direction direction, String orderType,String keyword){ + Pageable pageable = createPageable(page, direction, orderType); + List items = findAndConvertItems(category, pageable, keyword); + return PostsConverter.toViewAllList(items); + } + + + + //모듈화 코드 + + private Pageable createPageable(int page, Sort.Direction direction, String orderType) { + //카테고리 필드 + String sortField = switch (orderType) { + case "price" -> "price"; + case "createdAt" -> "createdAt"; + case "likeCount" -> "likeCount"; + default -> "createdAt"; // 기본 정렬 + }; + //정렬 순서 + direction = (direction == null) ? Sort.Direction.DESC : direction; + //페이지 생성 + return PageRequest.of( + Math.max(0, page - 1), // 페이지 번호 조정 (0부터 시작) + 20, + Sort.by(direction, sortField) + ); + } + + private List findAndConvertItems(String category, Pageable pageable, String keyword) { + // Repository 호출 + Page itemsPage = itemsRepository.findItemsWithConditions(category, pageable, null, keyword); + + // 빈 결과 체크 + if (itemsPage.isEmpty()) { + log.warn("No items found for category: {}", category); + return List.of(); + } + + // 데이터 변환 + return itemsPage.getContent().stream() + .map(item -> { + ItemsBorrowJpaEntity borrowItem = itemsBorrowRepository.findById(item.getId()).orElse(null); + if (borrowItem == null) { + log.warn("No borrow item found for item ID: {}", item.getId()); + } + return PostsConverter.toPost(item, borrowItem); + }) + .toList(); + } + + } diff --git a/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepository.java b/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepository.java index 2b8251d..c286d94 100644 --- a/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepository.java +++ b/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepository.java @@ -5,5 +5,5 @@ import site.billbill.apiserver.model.post.ItemsJpaEntity; public interface ItemDslRepository { - Page findItemsWithConditions(String category, Pageable pageable, String sortField); + Page findItemsWithConditions(String category, Pageable pageable, String sortField,String keyword); } diff --git a/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepositoryImpl.java b/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepositoryImpl.java index a9722b9..e989f2b 100644 --- a/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepositoryImpl.java +++ b/src/main/java/site/billbill/apiserver/repository/borrowPosts/ItemDslRepositoryImpl.java @@ -1,6 +1,7 @@ package site.billbill.apiserver.repository.borrowPosts; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @@ -21,12 +22,12 @@ public class ItemDslRepositoryImpl implements ItemDslRepository{ private final JPAQueryFactory queryFactory; @Override - public Page findItemsWithConditions(String category, Pageable pageable, String sortField) { + public Page findItemsWithConditions(String category, Pageable pageable, String sortField,String keyword) { QItemsJpaEntity items = QItemsJpaEntity.itemsJpaEntity; QItemsBorrowJpaEntity borrow = QItemsBorrowJpaEntity.itemsBorrowJpaEntity; QItemsCategoryJpaEntity categoryEntity = QItemsCategoryJpaEntity.itemsCategoryJpaEntity; - log.info("Category: {}, Sort Field: {}, Pageable: {}", category, sortField, pageable); + JPAQuery query = queryFactory.selectFrom(items) .leftJoin(borrow).on(items.id.eq(borrow.item.id)) // 식별 관계 조인 @@ -39,21 +40,31 @@ public Page findItemsWithConditions(String category, Pageable pa .fetchOne(); if (fetchedCategory == null) { - log.warn("Category '{}' not found. Returning empty result.", category); + log.warn("카테고리를 찾을 수 없음.", category); return new PageImpl<>(List.of(), pageable, 0); } query.where(items.category.eq(fetchedCategory)); } - // 정렬 조건 - if (sortField != null) { - OrderSpecifier orderSpecifier = getOrderSpecifier(sortField, pageable.getSort().getOrderFor(sortField)); - if (orderSpecifier != null) { - query.orderBy(orderSpecifier); - } else { - log.warn("Invalid sort field: {}, no sorting applied.", sortField); + //키워드 필터링 + if(keyword !=null && !keyword.isEmpty()){ + query.where(applyKeywordFilter(items,keyword)); + } + + // 정렬 조건 처리 + pageable.getSort().forEach(order -> { + OrderSpecifier orderSpecifier; + switch (order.getProperty()) { + case "price" -> orderSpecifier = order.isAscending() ? borrow.price.asc() : borrow.price.desc(); + case "createdAt" -> orderSpecifier = order.isAscending() ? items.createdAt.asc() : items.createdAt.desc(); + case "likeCount" -> orderSpecifier = order.isAscending() ? items.likeCount.asc() : items.likeCount.desc(); + default -> { + log.warn("Invalid sort field: {}", order.getProperty()); + return; } -} + } + query.orderBy(orderSpecifier); + }); // 페이징 처리 List content = query.offset(pageable.getOffset()) @@ -94,6 +105,29 @@ private OrderSpecifier getOrderSpecifier(String sortField, Sort.Order sortOrd return null; // 기본 정렬 없음 } } + private BooleanExpression applyKeywordFilter(QItemsJpaEntity items, String keyword) { + if (keyword == null || keyword.isEmpty()) { + return null; + } + + // '+'를 기준으로 키워드 분리 + String[] keywords = keyword.split("\\+"); + + // 키워드 조건 생성 + BooleanExpression keywordCondition = null; + for (String key : keywords) { + BooleanExpression condition = items.title.containsIgnoreCase(key) + .or(items.content.containsIgnoreCase(key)); + + if (keywordCondition == null) { + keywordCondition = condition; + } else { + keywordCondition = keywordCondition.or(condition); // AND 대신 OR 사용 + } + } + + return keywordCondition; + } }