Skip to content

Commit 2191e71

Browse files
authored
[feat] 칵테일 컨트롤러 단건조회 구현
* {feat} : domain * {fix}:Cocktail-Wishlist relation * fix : enums * fix : bug * feat : 조회기능, 조init data * feat : cocktailSearch * feat : search * feat: get cocktailTest * feat : controller
1 parent a6fbadb commit 2191e71

File tree

10 files changed

+344
-32
lines changed

10 files changed

+344
-32
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.back.domain.cocktail.controller;
2+
3+
import com.back.domain.cocktail.dto.CocktailDetailDto;
4+
import com.back.domain.cocktail.service.CocktailService;
5+
import com.back.domain.user.service.UserService;
6+
import com.back.global.rsData.RsData;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.transaction.annotation.Transactional;
10+
import org.springframework.web.bind.annotation.GetMapping;
11+
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
@RestController
16+
@RequestMapping("api/cocktails")
17+
@RequiredArgsConstructor
18+
public class CocktailController {
19+
20+
private final CocktailService cocktailService;
21+
private final UserService userService;
22+
23+
@GetMapping("/{id}")
24+
@Transactional
25+
@Operation(summary = "칵테일 단건 조회")
26+
public RsData<CocktailDetailDto> getCocktailDetailById(@PathVariable long id) {
27+
28+
CocktailDetailDto cocktailDetailDto = cocktailService.getCocktailDetailById(id);
29+
return RsData.successOf(cocktailDetailDto);
30+
}
31+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.back.domain.cocktail.dto;
2+
3+
import com.back.domain.cocktail.entity.Cocktail;
4+
import com.back.domain.cocktail.enums.AlcoholBaseType;
5+
import com.back.domain.cocktail.enums.AlcoholStrength;
6+
import com.back.domain.cocktail.enums.CocktailType;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
10+
@Getter
11+
@NoArgsConstructor
12+
public class CocktailDetailDto {
13+
private Long cocktailId;
14+
private String cocktailName;
15+
private String ingredient;
16+
private AlcoholStrength alcoholStrength;
17+
private CocktailType cocktailType;
18+
private AlcoholBaseType alcoholBaseType;
19+
private String cocktailImgUrl;
20+
private String cocktailStory;
21+
22+
public CocktailDetailDto(Cocktail c) {
23+
this.cocktailId = c.getCocktailId();
24+
this.cocktailName = c.getCocktailName();
25+
this.ingredient = c.getIngredient();
26+
this.alcoholStrength = c.getAlcoholStrength();
27+
this.cocktailType = c.getCocktailType();
28+
this.alcoholBaseType = c.getAlcoholBaseType();
29+
this.cocktailImgUrl = c.getCocktailImgUrl();
30+
this.cocktailStory = c.getCocktailStory();
31+
}
32+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.back.domain.cocktail.dto;
2+
3+
import com.back.domain.cocktail.enums.AlcoholBaseType;
4+
import com.back.domain.cocktail.enums.AlcoholStrength;
5+
import com.back.domain.cocktail.enums.CocktailType;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
import lombok.Setter;
9+
10+
import java.util.List;
11+
12+
@Getter
13+
@Setter
14+
@NoArgsConstructor
15+
public class CocktailFilterRequestDto {
16+
17+
private String keyword; // 검색 키워드
18+
19+
private List<AlcoholStrength> alcoholStrengths;
20+
21+
private List<CocktailType> cocktailTypes;
22+
23+
private List<AlcoholBaseType> alcoholBaseTypes;
24+
25+
// 페이징/정렬 추가하고 싶으면 여기 옵션 추가
26+
private Integer page; // 0-based 페이지 번호
27+
private Integer size; // 페이지 사이즈
28+
29+
// 생성자
30+
public CocktailFilterRequestDto(String keyword,
31+
List<AlcoholStrength> alcoholStrengths,
32+
List<CocktailType> cocktailTypes,
33+
List<AlcoholBaseType> alcoholBaseTypes,
34+
Integer page, Integer size) {
35+
this.keyword = keyword;
36+
this.alcoholStrengths = alcoholStrengths;
37+
this.cocktailTypes = cocktailTypes;
38+
this.alcoholBaseTypes = alcoholBaseTypes;
39+
this.page = page;
40+
this.size = size;
41+
}
42+
}

src/main/java/com/back/domain/cocktail/dto/CocktailResponseDto.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,17 @@ public class CocktailResponseDto {
2626
private LocalDateTime createdAt;
2727
private LocalDateTime updatedAt;
2828

29+
public CocktailResponseDto(long cocktailId, String cocktailName,
30+
AlcoholStrength alcoholStrength, CocktailType cocktailType,
31+
AlcoholBaseType alcoholBaseType, String cocktailImgUrl,
32+
String cocktailStory, LocalDateTime createdAt) {
33+
this.cocktailId = cocktailId;
34+
this.cocktailName = cocktailName;
35+
this.alcoholStrength = alcoholStrength;
36+
this.cocktailType = cocktailType;
37+
this.alcoholBaseType = alcoholBaseType;
38+
this.cocktailImgUrl = cocktailImgUrl;
39+
this.cocktailStory = cocktailStory;
40+
this.createdAt = createdAt;
41+
}
2942
}

src/main/java/com/back/domain/cocktail/entity/Cocktail.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
import com.back.domain.cocktail.enums.CocktailType;
66
import jakarta.persistence.*;
77
import lombok.*;
8-
98
import java.time.LocalDateTime;
10-
119
import static jakarta.persistence.GenerationType.IDENTITY;
1210

1311

src/main/java/com/back/domain/cocktail/repository/CocktailRepository.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package com.back.domain.cocktail.repository;
22

33
import com.back.domain.cocktail.entity.Cocktail;
4+
import com.back.domain.cocktail.enums.AlcoholBaseType;
5+
import com.back.domain.cocktail.enums.AlcoholStrength;
6+
import com.back.domain.cocktail.enums.CocktailType;
7+
import org.springframework.data.domain.Page;
48
import org.springframework.data.domain.Pageable;
59
import org.springframework.data.jpa.repository.JpaRepository;
10+
import org.springframework.data.jpa.repository.Query;
11+
import org.springframework.data.repository.query.Param;
612
import org.springframework.stereotype.Repository;
713

814
import java.util.List;
@@ -17,4 +23,17 @@ public interface CocktailRepository extends JpaRepository<Cocktail, Long> {
1723
List<Cocktail> findByCocktailIdLessThanOrderByCocktailIdDesc(Long lastId, Pageable pageable);
1824

1925
List<Cocktail> findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(String cocktailName, String ingredient);
26+
27+
@Query("SELECT c FROM Cocktail c " +
28+
"WHERE (:keyword IS NULL OR :keyword = '' OR " +
29+
" LOWER(c.cocktailName) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " +
30+
" LOWER(c.ingredient) LIKE LOWER(CONCAT('%', :keyword, '%')))" +
31+
" AND (:strengths IS NULL OR c.alcoholStrength IN :strengths) " + // 알코올 도수 필터를 담당
32+
" AND (:types IS NULL OR c.cocktailType IN :types) " + // 칵테일 타입 필터를 담당
33+
" AND (:bases IS NULL OR c.alcoholBaseType IN :bases) ") // 알코올 베이스 필터를 담당
34+
Page<Cocktail> searchWithFilters(@Param("keyword") String keyword,
35+
@Param("strengths") List<AlcoholStrength> strengths,
36+
@Param("types") List<CocktailType> types,
37+
@Param("bases") List<AlcoholBaseType> bases,
38+
Pageable pageable);
2039
}

src/main/java/com/back/domain/cocktail/service/CocktailService.java

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package com.back.domain.cocktail.service;
22

3+
import com.back.domain.cocktail.dto.CocktailDetailDto;
4+
import com.back.domain.cocktail.dto.CocktailFilterRequestDto;
5+
import com.back.domain.cocktail.dto.CocktailResponseDto;
36
import com.back.domain.cocktail.dto.CocktailSummaryDto;
47
import com.back.domain.cocktail.entity.Cocktail;
8+
import com.back.domain.cocktail.enums.AlcoholBaseType;
9+
import com.back.domain.cocktail.enums.AlcoholStrength;
10+
import com.back.domain.cocktail.enums.CocktailType;
511
import com.back.domain.cocktail.repository.CocktailRepository;
612
import lombok.RequiredArgsConstructor;
13+
import org.springframework.data.domain.Page;
714
import org.springframework.data.domain.PageRequest;
15+
import org.springframework.data.domain.Pageable;
816
import org.springframework.stereotype.Service;
917
import org.springframework.transaction.annotation.Transactional;
18+
import org.springframework.util.CollectionUtils;
19+
1020
import java.util.List;
21+
import java.util.NoSuchElementException;
1122
import java.util.stream.Collectors;
1223

1324
@Service
@@ -20,38 +31,103 @@ public class CocktailService {
2031

2132
@Transactional(readOnly = true)
2233
public Cocktail getCocktailById(Long id) {
23-
return cocktailRepository.findById(id)
24-
.orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id));
25-
}
34+
return cocktailRepository.findById(id)
35+
.orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id));
36+
}
2637

27-
// 칵테일 무한스크롤 조회
28-
@Transactional(readOnly = true)
29-
public List<CocktailSummaryDto> getCocktails(Long lastId, Integer size) { // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트)
30-
int fetchSize = (size != null) ? size : DEFAULT_SIZE;
31-
32-
List<Cocktail> cocktails;
33-
if (lastId == null) {
34-
// 첫 요청 → 최신 데이터부터
35-
cocktails = cocktailRepository.findAllByOrderByCocktailIdDesc(PageRequest.of(0, fetchSize));
36-
} else {
37-
// 무한스크롤 → 마지막 ID보다 작은 데이터 조회
38-
cocktails = cocktailRepository.findByCocktailIdLessThanOrderByCocktailIdDesc(lastId, PageRequest.of(0, fetchSize));
38+
// 칵테일 무한스크롤 조회
39+
@Transactional(readOnly = true)
40+
public List<CocktailSummaryDto> getCocktails (Long lastId, Integer size)
41+
{ // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트)
42+
int fetchSize = (size != null) ? size : DEFAULT_SIZE;
43+
44+
List<Cocktail> cocktails;
45+
if (lastId == null) {
46+
// 첫 요청 → 최신 데이터부터
47+
cocktails = cocktailRepository.findAllByOrderByCocktailIdDesc(PageRequest.of(0, fetchSize));
48+
} else {
49+
// 무한스크롤 → 마지막 ID보다 작은 데이터 조회
50+
cocktails = cocktailRepository.findByCocktailIdLessThanOrderByCocktailIdDesc(lastId, PageRequest.of(0, fetchSize));
51+
}
52+
return cocktails.stream()
53+
.map(c -> new CocktailSummaryDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl()))
54+
.collect(Collectors.toList());
3955
}
4056

41-
return cocktails.stream()
42-
.map(c -> new CocktailSummaryDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl()))
43-
.collect(Collectors.toList());
44-
}
57+
// 칵테일 검색기능
58+
@Transactional(readOnly = true)
59+
public List<Cocktail> cocktailSearch (String keyword){
60+
// cockTailName, ingredient이 하나만 있을 수도 있고 둘 다 있을 수도 있음
61+
if (keyword == null || keyword.trim().isEmpty()) {
62+
// 아무 검색어 없으면 전체 반환 처리
63+
return cocktailRepository.findAll();
64+
} else {
65+
// 이름 또는 재료 둘 중 하나라도 매칭되면 결과 반환
66+
return cocktailRepository.findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(keyword, keyword);
67+
}
68+
}
69+
70+
// 칵테일 검색,필터기능
71+
@Transactional(readOnly = true)
72+
public List<CocktailResponseDto> searchAndFilter (CocktailFilterRequestDto cocktailFilterRequestDto){
73+
// 기본값 페이지/사이즈 정하기(PAGE 기본값 0, 사이즈 10)
74+
int page = cocktailFilterRequestDto.getPage() != null && cocktailFilterRequestDto.getPage() >= 0
75+
? cocktailFilterRequestDto.getPage() : 0;
76+
77+
int size = cocktailFilterRequestDto.getSize() != null && cocktailFilterRequestDto.getSize() > 0
78+
? cocktailFilterRequestDto.getSize() : DEFAULT_SIZE;
79+
80+
// searchWithFilters에서 조회한 결과값을 pageResult에 저장.
81+
Pageable pageable = PageRequest.of(page, size);
82+
83+
// 빈 리스트(null 또는 [])는 null로 변환
84+
List<AlcoholStrength> strengths = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholStrengths())
85+
? null
86+
: cocktailFilterRequestDto.getAlcoholStrengths();
87+
88+
List<CocktailType> types = CollectionUtils.isEmpty(cocktailFilterRequestDto.getCocktailTypes())
89+
? null
90+
: cocktailFilterRequestDto.getCocktailTypes();
91+
92+
List<AlcoholBaseType> bases = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholBaseTypes())
93+
? null
94+
: cocktailFilterRequestDto.getAlcoholBaseTypes();
95+
96+
// Repository 호출
97+
Page<Cocktail> pageResult = cocktailRepository.searchWithFilters(
98+
cocktailFilterRequestDto.getKeyword(),
99+
strengths, // List<AlcoholStrength>
100+
types, // List<CocktailType>
101+
bases, // List<AlcoholBaseType>
102+
pageable
103+
);
104+
105+
//Cocktail 엔티티 → CocktailResponseDto 응답 DTO로 바꿔주는 과정
106+
List<CocktailResponseDto> resultDtos = pageResult.stream()
107+
.map(c -> new CocktailResponseDto(
108+
c.getCocktailId(),
109+
c.getCocktailName(),
110+
c.getAlcoholStrength(),
111+
c.getCocktailType(),
112+
c.getAlcoholBaseType(),
113+
c.getCocktailImgUrl(),
114+
c.getCocktailStory(),
115+
c.getCreatedAt()
116+
))
117+
.collect(Collectors.toList());
118+
119+
return resultDtos;
120+
}
121+
122+
// private <T> List<T> nullIfEmpty(List<T> list) {
123+
// return CollectionUtils.isEmpty(list) ? null : list;
124+
// }
45125

46-
// 칵테일 검색기능
47-
public List<Cocktail> cocktailSearch(String keyword) {
48-
// cockTailName, ingredient이 하나만 있을 수도 있고 둘 다 있을 수도 있음
49-
if (keyword == null || keyword.trim().isEmpty()) {
50-
// 아무 검색어 없으면 전체 반환 처리
51-
return cocktailRepository.findAll();
52-
} else {
53-
// 이름 또는 재료 둘 중 하나라도 매칭되면 결과 반환
54-
return cocktailRepository.findByCocktailNameContainingIgnoreCaseOrIngredientContainingIgnoreCase(keyword, keyword);
126+
// 칵테일 상세조회
127+
@Transactional(readOnly = true)
128+
public CocktailDetailDto getCocktailDetailById (Long cocktailId){
129+
Cocktail cocktail = cocktailRepository.findById(cocktailId)
130+
.orElseThrow(() -> new NoSuchElementException("칵테일을 찾을 수 없습니다. id: " + cocktailId));
131+
return new CocktailDetailDto(cocktail);
55132
}
56133
}
57-
}

src/main/java/com/back/global/init/DevInitData.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.global.init;
22

3+
import com.back.domain.cocktail.dto.CocktailFilterRequestDto;
34
import com.back.domain.cocktail.entity.Cocktail;
45
import com.back.domain.cocktail.enums.AlcoholStrength;
56
import com.back.domain.cocktail.repository.CocktailRepository;
@@ -13,6 +14,8 @@
1314
import org.springframework.context.annotation.Profile;
1415
import org.springframework.transaction.annotation.Transactional;
1516

17+
import java.util.Arrays;
18+
1619
@Configuration
1720
@Profile("dev")
1821
@RequiredArgsConstructor
@@ -47,10 +50,16 @@ public void cocktailInit() {
4750
.alcoholStrength(AlcoholStrength.NON_ALCOHOLIC)
4851
.build());
4952
}
53+
54+
CocktailFilterRequestDto filterDto = new CocktailFilterRequestDto();
55+
filterDto.setKeyword("cocktail 4"); // 검색 키워드 설정
56+
filterDto.setAlcoholStrengths(Arrays.asList(AlcoholStrength.NON_ALCOHOLIC));
57+
5058
System.out.println("DevInitData: 테스트 칵테일 20개 삽입");
5159
System.out.println(cocktailService.getCocktailById(2l));
5260
System.out.println(cocktailService.cocktailSearch("cocktail 3"));
5361
System.out.println(cocktailService.cocktailSearch("Ingredient 4"));
62+
System.out.println("filterDTO 결과값"+cocktailService.searchAndFilter(filterDto));
5463
}
5564
}
5665

src/main/java/com/back/global/security/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
4646
.requestMatchers("/login/oauth2/**").permitAll()
4747
.requestMatchers("/swagger-ui/**", "/api-docs/**").permitAll()
4848
.requestMatchers("/api/user/**").permitAll()
49-
.requestMatchers("/api/cocktail/**").permitAll()
49+
.requestMatchers("/api/cocktails/**").permitAll()
5050

5151

5252
// 회원 or 인증된 사용자만 가능

0 commit comments

Comments
 (0)