Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.back.domain.cocktail.controller;

import com.back.domain.cocktail.dto.CocktailDetailDto;
import com.back.domain.cocktail.service.CocktailService;
import com.back.domain.user.service.UserService;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("api/cocktails")
@RequiredArgsConstructor
public class CocktailController {

private final CocktailService cocktailService;
private final UserService userService;

@GetMapping("/{id}")
@Transactional
@Operation(summary = "칵테일 단건 조회")
public RsData<CocktailDetailDto> getCocktailDetailById(@PathVariable long id) {

CocktailDetailDto cocktailDetailDto = cocktailService.getCocktailDetailById(id);
return RsData.successOf(cocktailDetailDto);
}
}
32 changes: 32 additions & 0 deletions src/main/java/com/back/domain/cocktail/dto/CocktailDetailDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.back.domain.cocktail.dto;

import com.back.domain.cocktail.entity.Cocktail;
import com.back.domain.cocktail.enums.AlcoholBaseType;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.enums.CocktailType;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class CocktailDetailDto {
private Long cocktailId;
private String cocktailName;
private String ingredient;
private AlcoholStrength alcoholStrength;
private CocktailType cocktailType;
private AlcoholBaseType alcoholBaseType;
private String cocktailImgUrl;
private String cocktailStory;

public CocktailDetailDto(Cocktail c) {
this.cocktailId = c.getCocktailId();
this.cocktailName = c.getCocktailName();
this.ingredient = c.getIngredient();
this.alcoholStrength = c.getAlcoholStrength();
this.cocktailType = c.getCocktailType();
this.alcoholBaseType = c.getAlcoholBaseType();
this.cocktailImgUrl = c.getCocktailImgUrl();
this.cocktailStory = c.getCocktailStory();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.back.domain.cocktail.dto;

import com.back.domain.cocktail.enums.AlcoholBaseType;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.enums.CocktailType;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.List;

@Getter
@Setter
@NoArgsConstructor
public class CocktailFilterRequestDto {

private String keyword; // 검색 키워드

private List<AlcoholStrength> alcoholStrengths;

private List<CocktailType> cocktailTypes;

private List<AlcoholBaseType> alcoholBaseTypes;

// 페이징/정렬 추가하고 싶으면 여기 옵션 추가
private Integer page; // 0-based 페이지 번호
private Integer size; // 페이지 사이즈

// 생성자
public CocktailFilterRequestDto(String keyword,
List<AlcoholStrength> alcoholStrengths,
List<CocktailType> cocktailTypes,
List<AlcoholBaseType> alcoholBaseTypes,
Integer page, Integer size) {
this.keyword = keyword;
this.alcoholStrengths = alcoholStrengths;
this.cocktailTypes = cocktailTypes;
this.alcoholBaseTypes = alcoholBaseTypes;
this.page = page;
this.size = size;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,17 @@ public class CocktailResponseDto {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;

public CocktailResponseDto(long cocktailId, String cocktailName,
AlcoholStrength alcoholStrength, CocktailType cocktailType,
AlcoholBaseType alcoholBaseType, String cocktailImgUrl,
String cocktailStory, LocalDateTime createdAt) {
this.cocktailId = cocktailId;
this.cocktailName = cocktailName;
this.alcoholStrength = alcoholStrength;
this.cocktailType = cocktailType;
this.alcoholBaseType = alcoholBaseType;
this.cocktailImgUrl = cocktailImgUrl;
this.cocktailStory = cocktailStory;
this.createdAt = createdAt;
}
}
2 changes: 0 additions & 2 deletions src/main/java/com/back/domain/cocktail/entity/Cocktail.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import com.back.domain.cocktail.enums.CocktailType;
import jakarta.persistence.*;
import lombok.*;

import java.time.LocalDateTime;

import static jakarta.persistence.GenerationType.IDENTITY;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package com.back.domain.cocktail.repository;

import com.back.domain.cocktail.entity.Cocktail;
import com.back.domain.cocktail.enums.AlcoholBaseType;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.enums.CocktailType;
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.List;
Expand All @@ -17,4 +23,17 @@ public interface CocktailRepository extends JpaRepository<Cocktail, Long> {
List<Cocktail> findByCocktailIdLessThanOrderByCocktailIdDesc(Long lastId, Pageable pageable);

List<Cocktail> findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(String cocktailName, String ingredient);

@Query("SELECT c FROM Cocktail c " +
"WHERE (:keyword IS NULL OR :keyword = '' OR " +
" LOWER(c.cocktailName) 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) " + // 칵테일 타입 필터를 담당
" AND (:bases IS NULL OR c.alcoholBaseType IN :bases) ") // 알코올 베이스 필터를 담당
Page<Cocktail> searchWithFilters(@Param("keyword") String keyword,
@Param("strengths") List<AlcoholStrength> strengths,
@Param("types") List<CocktailType> types,
@Param("bases") List<AlcoholBaseType> bases,
Pageable pageable);
}
134 changes: 105 additions & 29 deletions src/main/java/com/back/domain/cocktail/service/CocktailService.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package com.back.domain.cocktail.service;

import com.back.domain.cocktail.dto.CocktailDetailDto;
import com.back.domain.cocktail.dto.CocktailFilterRequestDto;
import com.back.domain.cocktail.dto.CocktailResponseDto;
import com.back.domain.cocktail.dto.CocktailSummaryDto;
import com.back.domain.cocktail.entity.Cocktail;
import com.back.domain.cocktail.enums.AlcoholBaseType;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.enums.CocktailType;
import com.back.domain.cocktail.repository.CocktailRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;

@Service
Expand All @@ -20,38 +31,103 @@ public class CocktailService {

@Transactional(readOnly = true)
public Cocktail getCocktailById(Long id) {
return cocktailRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id));
}
return cocktailRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id));
}

// 칵테일 무한스크롤 조회
@Transactional(readOnly = true)
public List<CocktailSummaryDto> getCocktails(Long lastId, Integer size) { // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트)
int fetchSize = (size != null) ? size : DEFAULT_SIZE;

List<Cocktail> cocktails;
if (lastId == null) {
// 첫 요청 → 최신 데이터부터
cocktails = cocktailRepository.findAllByOrderByCocktailIdDesc(PageRequest.of(0, fetchSize));
} else {
// 무한스크롤 → 마지막 ID보다 작은 데이터 조회
cocktails = cocktailRepository.findByCocktailIdLessThanOrderByCocktailIdDesc(lastId, PageRequest.of(0, fetchSize));
// 칵테일 무한스크롤 조회
@Transactional(readOnly = true)
public List<CocktailSummaryDto> getCocktails (Long lastId, Integer size)
{ // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트)
int fetchSize = (size != null) ? size : DEFAULT_SIZE;

List<Cocktail> cocktails;
if (lastId == null) {
// 첫 요청 → 최신 데이터부터
cocktails = cocktailRepository.findAllByOrderByCocktailIdDesc(PageRequest.of(0, fetchSize));
} else {
// 무한스크롤 → 마지막 ID보다 작은 데이터 조회
cocktails = cocktailRepository.findByCocktailIdLessThanOrderByCocktailIdDesc(lastId, PageRequest.of(0, fetchSize));
}
return cocktails.stream()
.map(c -> new CocktailSummaryDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl()))
.collect(Collectors.toList());
}

return cocktails.stream()
.map(c -> new CocktailSummaryDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl()))
.collect(Collectors.toList());
}
// 칵테일 검색기능
@Transactional(readOnly = true)
public List<Cocktail> 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<CocktailResponseDto> searchAndFilter (CocktailFilterRequestDto cocktailFilterRequestDto){
// 기본값 페이지/사이즈 정하기(PAGE 기본값 0, 사이즈 10)
int page = cocktailFilterRequestDto.getPage() != null && cocktailFilterRequestDto.getPage() >= 0
? cocktailFilterRequestDto.getPage() : 0;

int size = cocktailFilterRequestDto.getSize() != null && cocktailFilterRequestDto.getSize() > 0
? cocktailFilterRequestDto.getSize() : DEFAULT_SIZE;

// searchWithFilters에서 조회한 결과값을 pageResult에 저장.
Pageable pageable = PageRequest.of(page, size);

// 빈 리스트(null 또는 [])는 null로 변환
List<AlcoholStrength> strengths = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholStrengths())
? null
: cocktailFilterRequestDto.getAlcoholStrengths();

List<CocktailType> types = CollectionUtils.isEmpty(cocktailFilterRequestDto.getCocktailTypes())
? null
: cocktailFilterRequestDto.getCocktailTypes();

List<AlcoholBaseType> bases = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholBaseTypes())
? null
: cocktailFilterRequestDto.getAlcoholBaseTypes();

// Repository 호출
Page<Cocktail> pageResult = cocktailRepository.searchWithFilters(
cocktailFilterRequestDto.getKeyword(),
strengths, // List<AlcoholStrength>
types, // List<CocktailType>
bases, // List<AlcoholBaseType>
pageable
);

//Cocktail 엔티티 → CocktailResponseDto 응답 DTO로 바꿔주는 과정
List<CocktailResponseDto> resultDtos = pageResult.stream()
.map(c -> new CocktailResponseDto(
c.getCocktailId(),
c.getCocktailName(),
c.getAlcoholStrength(),
c.getCocktailType(),
c.getAlcoholBaseType(),
c.getCocktailImgUrl(),
c.getCocktailStory(),
c.getCreatedAt()
))
.collect(Collectors.toList());

return resultDtos;
}

// private <T> List<T> nullIfEmpty(List<T> list) {
// return CollectionUtils.isEmpty(list) ? null : list;
// }

// 칵테일 검색기능
public List<Cocktail> cocktailSearch(String keyword) {
// cockTailName, ingredient이 하나만 있을 수도 있고 둘 다 있을 수도 있음
if (keyword == null || keyword.trim().isEmpty()) {
// 아무 검색어 없으면 전체 반환 처리
return cocktailRepository.findAll();
} else {
// 이름 또는 재료 둘 중 하나라도 매칭되면 결과 반환
return cocktailRepository.findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(keyword, keyword);
// 칵테일 상세조회
@Transactional(readOnly = true)
public CocktailDetailDto getCocktailDetailById (Long cocktailId){
Cocktail cocktail = cocktailRepository.findById(cocktailId)
.orElseThrow(() -> new NoSuchElementException("칵테일을 찾을 수 없습니다. id: " + cocktailId));
return new CocktailDetailDto(cocktail);
}
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/back/global/init/DevInitData.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.back.global.init;

import com.back.domain.cocktail.dto.CocktailFilterRequestDto;
import com.back.domain.cocktail.entity.Cocktail;
import com.back.domain.cocktail.enums.AlcoholStrength;
import com.back.domain.cocktail.repository.CocktailRepository;
Expand All @@ -13,6 +14,8 @@
import org.springframework.context.annotation.Profile;
import org.springframework.transaction.annotation.Transactional;

import java.util.Arrays;

@Configuration
@Profile("dev")
@RequiredArgsConstructor
Expand Down Expand Up @@ -47,10 +50,16 @@ public void cocktailInit() {
.alcoholStrength(AlcoholStrength.NON_ALCOHOLIC)
.build());
}

CocktailFilterRequestDto filterDto = new CocktailFilterRequestDto();
filterDto.setKeyword("cocktail 4"); // 검색 키워드 설정
filterDto.setAlcoholStrengths(Arrays.asList(AlcoholStrength.NON_ALCOHOLIC));

System.out.println("DevInitData: 테스트 칵테일 20개 삽입");
System.out.println(cocktailService.getCocktailById(2l));
System.out.println(cocktailService.cocktailSearch("cocktail 3"));
System.out.println(cocktailService.cocktailSearch("Ingredient 4"));
System.out.println("filterDTO 결과값"+cocktailService.searchAndFilter(filterDto));
}
}

2 changes: 1 addition & 1 deletion src/main/java/com/back/global/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/login/oauth2/**").permitAll()
.requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()
.requestMatchers("/api/user/**").permitAll()
.requestMatchers("/api/cocktail/**").permitAll()
.requestMatchers("/api/cocktails/**").permitAll()


// 회원 or 인증된 사용자만 가능
Expand Down
Loading