diff --git a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java index a389f05a..6c2268af 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java @@ -31,6 +31,7 @@ public RsData getCocktailDetailById(@PathVariable lon return RsData.successOf(cocktailDetailResponseDto); } + // @param lastValue 다음 페이지에서 이 값보다 작은 항목만 가져오기 위해 사용 // @param lastId 마지막으로 가져온 칵테일 ID (첫 요청 null 가능) // @param size 가져올 데이터 개수 (기본값 DEFAULT_SIZE) // @return RsData 형태의 칵테일 요약 정보 리스트 @@ -38,10 +39,12 @@ public RsData getCocktailDetailById(@PathVariable lon @Transactional @Operation(summary = "칵테일 다건 조회") public RsData> getCocktails( + @RequestParam(value = "lastValue", required = false) Long lastValue, @RequestParam(value = "lastId", required = false) Long lastId, - @RequestParam(value = "size", required = false) Integer size + @RequestParam(value = "size", required = false) Integer size, + @RequestParam(value = "sortBy", required = false, defaultValue = "recent") String sortBy ) { - List cocktails = cocktailService.getCocktails(lastId, size); + List cocktails = cocktailService.getCocktails(lastValue, lastId, size, sortBy); return RsData.successOf(cocktails); } diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java index 06078a19..63e10c87 100644 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java @@ -1,34 +1,34 @@ package com.back.domain.cocktail.dto; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import com.back.domain.cocktail.entity.Cocktail; -@Getter -@Setter -@NoArgsConstructor -public class CocktailSearchResponseDto { +public record CocktailSearchResponseDto ( - private long cocktailId; - private String cocktailName; - private String cocktailNameKo; - private String alcoholStrength; - private String cocktailType; - private String alcoholBaseType; - private String cocktailImgUrl; - private String cocktailStory; + Long cocktailId, + String cocktailName, + String cocktailNameKo, + String alcoholStrength, + String cocktailType, + String alcoholBaseType, + String cocktailImgUrl, + String cocktailStory, + String cocktailPreview +){ + public static CocktailSearchResponseDto from(Cocktail cocktail){ + String preview =cocktail.getCocktailStory().length() >80 ? + cocktail.getCocktailStory().substring(0,80)+"..." + : cocktail.getCocktailStory(); - public CocktailSearchResponseDto(long cocktailId, String cocktailName, String cocktailNameKo, - String alcoholStrength, String cocktailType, - String alcoholBaseType, String cocktailImgUrl, - String cocktailStory) { - this.cocktailId = cocktailId; - this.cocktailName = cocktailName; - this.cocktailNameKo = cocktailNameKo; - this.alcoholStrength = alcoholStrength; - this.cocktailType = cocktailType; - this.alcoholBaseType = alcoholBaseType; - this.cocktailImgUrl = cocktailImgUrl; - this.cocktailStory = cocktailStory; + return new CocktailSearchResponseDto( + cocktail.getId(), + cocktail.getCocktailName(), + cocktail.getCocktailNameKo(), + cocktail.getAlcoholStrength().getDescription(), + cocktail.getCocktailType().getDescription(), + cocktail.getAlcoholBaseType().getDescription(), + cocktail.getCocktailImgUrl(), + cocktail.getCocktailStory(), + preview + ); } } \ No newline at end of file diff --git a/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java b/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java index 0695c469..8187f9c3 100644 --- a/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java +++ b/src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java @@ -16,17 +16,58 @@ @Repository public interface CocktailRepository extends JpaRepository { - // 첫 요청 → 최신순(내림차순)으로 정렬해서 가져오기 + // 전체조회 : 최신순 List findAllByOrderByIdDesc(Pageable pageable); - - // 무한스크롤 → lastId보다 작은 ID들 가져오기 List findByIdLessThanOrderByIdDesc(Long lastId, Pageable pageable); - List findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(String cocktailName, String ingredient); + // 전체 조회: keepsCount 기준 내림차순 + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN MyBar m ON m.cocktail = c AND m.status = 'ACTIVE' + GROUP BY c.id + ORDER BY COUNT(m) DESC, c.id DESC + """) + List findAllOrderByKeepCountDesc(Pageable pageable); + + // 무한스크롤 조회: lastKeepCount 이하 + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN MyBar m ON m.cocktail = c AND m.status = 'ACTIVE' + GROUP BY c.id + HAVING COUNT(m) < :lastKeepCount OR (COUNT(m) = :lastKeepCount AND c.id < :lastId) + ORDER BY COUNT(m) DESC, c.id DESC +""") + List findByKeepCountLessThanOrderByKeepCountDesc( + @Param("lastKeepCount") Long lastKeepCount, + @Param("lastId") Long lastId, + Pageable pageable + ); + + // 댓글순 + @Query("SELECT c FROM Cocktail c " + + "LEFT JOIN CocktailComment cm ON cm.cocktail = c " + + "GROUP BY c.id " + + "ORDER BY COUNT(cm) DESC, c.id DESC") + List findAllOrderByCommentsCountDesc(Pageable pageable); + + @Query(""" + SELECT c FROM Cocktail c + LEFT JOIN CocktailComment cm ON cm.cocktail = c + GROUP BY c.id + HAVING COUNT(cm) < :lastCommentsCount OR (COUNT(cm) = :lastCommentsCount AND c.id < :lastId) + ORDER BY COUNT(cm) DESC, c.id DESC + """) + List findByCommentsCountLessThanOrderByCommentsCountDesc( + @Param("lastCommentsCount") Long lastCommentsCount, + @Param("lastId") Long lastId, + Pageable pageable + ); + //검색, 필터 @Query("SELECT c FROM Cocktail c " + "WHERE (:keyword IS NULL OR :keyword = '' OR " + " LOWER(c.cocktailName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + + " LOWER(c.cocktailNameKo) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + " LOWER(c.ingredient) LIKE LOWER(CONCAT('%', :keyword, '%')))" + " AND (:strengths IS NULL OR c.alcoholStrength IN :strengths) " + // 알코올 도수 필터를 담당 " AND (:types IS NULL OR c.cocktailType IN :types) " + // 칵테일 타입 필터를 담당 diff --git a/src/main/java/com/back/domain/cocktail/service/CocktailService.java b/src/main/java/com/back/domain/cocktail/service/CocktailService.java index 4865fcec..f302310b 100644 --- a/src/main/java/com/back/domain/cocktail/service/CocktailService.java +++ b/src/main/java/com/back/domain/cocktail/service/CocktailService.java @@ -37,37 +37,41 @@ public Cocktail getCocktailById(Long id) { .orElseThrow(() -> new IllegalArgumentException("Cocktail not found. id=" + id)); } - // 칵테일 무한스크롤 조회 @Transactional(readOnly = true) - public List getCocktails(Long lastId, Integer size) { // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트) + public List getCocktails(Long lastValue, Long lastId, Integer size, String sortBy) { int fetchSize = (size != null) ? size : DEFAULT_SIZE; - + Pageable pageable = PageRequest.of(0, fetchSize); List cocktails; - if (lastId == null) { - // 첫 요청 → 최신 데이터부터 - cocktails = cocktailRepository.findAllByOrderByIdDesc(PageRequest.of(0, fetchSize)); - } else { - // 무한스크롤 → 마지막 ID보다 작은 데이터 조회 - cocktails = cocktailRepository.findByIdLessThanOrderByIdDesc(lastId, PageRequest.of(0, fetchSize)); + + switch (sortBy != null ? sortBy.toLowerCase() : "") { + case "keeps": + cocktails = (lastValue == null) + ? cocktailRepository.findAllOrderByKeepCountDesc(pageable) + : cocktailRepository.findByKeepCountLessThanOrderByKeepCountDesc(lastValue, lastId, pageable); + break; + case "comments": + cocktails = (lastValue == null) + ? cocktailRepository.findAllOrderByCommentsCountDesc(pageable) + : cocktailRepository.findByCommentsCountLessThanOrderByCommentsCountDesc(lastValue, lastId, pageable); + break; + default: + cocktails = (lastValue == null) + ? cocktailRepository.findAllByOrderByIdDesc(pageable) + : cocktailRepository.findByIdLessThanOrderByIdDesc(lastValue, pageable); + break; } + return cocktails.stream() - .map(c -> new CocktailSummaryResponseDto(c.getId(), c.getCocktailName(), c.getCocktailNameKo(), c.getCocktailImgUrl(), c.getAlcoholStrength().getDescription())) + .map(c -> new CocktailSummaryResponseDto( + c.getId(), + c.getCocktailName(), + c.getCocktailNameKo(), + c.getCocktailImgUrl(), + c.getAlcoholStrength().getDescription() + )) .collect(Collectors.toList()); } - // 칵테일 검색기능 - @Transactional(readOnly = true) - public List cocktailSearch(String keyword) { - // cockTailName, ingredient이 하나만 있을 수도 있고 둘 다 있을 수도 있음 - if (keyword == null || keyword.trim().isEmpty()) { - // 아무 검색어 없으면 전체 반환 처리 - return cocktailRepository.findAll(); - } else { - // 이름 또는 재료 둘 중 하나라도 매칭되면 결과 반환 - return cocktailRepository.findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(keyword, keyword); - } - } - // 칵테일 검색,필터기능 @Transactional(readOnly = true) public List searchAndFilter(CocktailSearchRequestDto cocktailSearchRequestDto) { @@ -105,25 +109,12 @@ public List searchAndFilter(CocktailSearchRequestDto //Cocktail 엔티티 → CocktailResponseDto 응답 DTO로 바꿔주는 과정 List resultDtos = pageResult.stream() - .map(c -> new CocktailSearchResponseDto( - c.getId(), - c.getCocktailName(), - c.getCocktailNameKo(), - c.getAlcoholStrength().getDescription(), - c.getCocktailType().getDescription(), - c.getAlcoholBaseType().getDescription(), - c.getCocktailImgUrl(), - c.getCocktailStory() - )) + .map(CocktailSearchResponseDto::from) .collect(Collectors.toList()); return resultDtos; } -// private List nullIfEmpty(List list) { -// return CollectionUtils.isEmpty(list) ? null : list; -// } - // 칵테일 상세조회 @Transactional(readOnly = true) public CocktailDetailResponseDto getCocktailDetailById(Long cocktailId) {