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 62db435e..8ec833fb 100644 --- a/src/main/java/com/back/domain/cocktail/controller/CocktailController.java +++ b/src/main/java/com/back/domain/cocktail/controller/CocktailController.java @@ -1,16 +1,17 @@ package com.back.domain.cocktail.controller; -import com.back.domain.cocktail.dto.CocktailDetailDto; +import com.back.domain.cocktail.dto.CocktailDetailResponseDto; +import com.back.domain.cocktail.dto.CocktailSearchRequestDto; +import com.back.domain.cocktail.dto.CocktailSearchResponseDto; +import com.back.domain.cocktail.dto.CocktailSummaryResponseDto; 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; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequestMapping("api/cocktails") @@ -18,14 +19,43 @@ public class CocktailController { private final CocktailService cocktailService; - private final UserService userService; @GetMapping("/{id}") @Transactional @Operation(summary = "칵테일 단건 조회") - public RsData getCocktailDetailById(@PathVariable long id) { + public RsData getCocktailDetailById(@PathVariable long id){ + + CocktailDetailResponseDto cocktailDetailResponseDto = cocktailService.getCocktailDetailById(id); + return RsData.successOf(cocktailDetailResponseDto); + } + + // @param lastId 마지막으로 가져온 칵테일 ID (첫 요청 시 null 가능) + // @param size 가져올 데이터 개수 (기본값 DEFAULT_SIZE) + // @return RsData 형태의 칵테일 요약 정보 리스트 + @GetMapping + @Transactional + @Operation(summary = "칵테일 다건 조회") + public RsData> getCocktails( + @RequestParam(value = "lastId", required = false) Long lastId, + @RequestParam(value = "size", required = false) Integer size + ) { + List cocktails = cocktailService.getCocktails(lastId, size); + return RsData.successOf(cocktails); + } + + + // 칵테일 검색 및 필터링 + // POST 방식으로 JSON body를 통해 검색 조건 전달 + + @PostMapping("/search") + @Operation(summary = "칵테일 검색 및 필터링") + public RsData> searchAndFilter( + @RequestBody CocktailSearchRequestDto cocktailSearchRequestDto + ) { + // 서비스 호출 + List searchResults = cocktailService.searchAndFilter(cocktailSearchRequestDto); - CocktailDetailDto cocktailDetailDto = cocktailService.getCocktailDetailById(id); - return RsData.successOf(cocktailDetailDto); + // RsData로 통일된 응답 반환 + return RsData.successOf(searchResults); } } diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailDetailDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailDetailDto.java deleted file mode 100644 index 033ceb8a..00000000 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailDetailDto.java +++ /dev/null @@ -1,32 +0,0 @@ -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(); - } -} diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailDetailResponseDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailDetailResponseDto.java new file mode 100644 index 00000000..7b0b940d --- /dev/null +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailDetailResponseDto.java @@ -0,0 +1,52 @@ +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 CocktailDetailResponseDto { + private Long cocktailId; + private String cocktailName; + private AlcoholStrength alcoholStrength; + private CocktailType cocktailType; + private AlcoholBaseType alcoholBaseType; + private String cocktailImgUrl; + private String cocktailStory; + private String ingredient; + private String recipe; + + public CocktailDetailResponseDto( + long cocktailId, String cocktailName, + AlcoholStrength alcoholStrength, CocktailType cocktailType, + AlcoholBaseType alcoholBaseType, String cocktailImgUrl, + String cocktailStory, String ingredient, + String recipe + ) { + this.cocktailId = cocktailId; + this.cocktailName = cocktailName; + this.alcoholStrength = alcoholStrength; + this.cocktailType = cocktailType; + this.alcoholBaseType = alcoholBaseType; + this.cocktailImgUrl = cocktailImgUrl; + this.cocktailStory = cocktailStory; + this.ingredient = ingredient; + this.recipe = recipe; + } + + public CocktailDetailResponseDto(Cocktail cocktail) { + this.cocktailId = cocktail.getCocktailId(); + this.cocktailName = cocktail.getCocktailName(); + this.alcoholStrength = cocktail.getAlcoholStrength(); + this.cocktailType = cocktail.getCocktailType(); + this.alcoholBaseType = cocktail.getAlcoholBaseType(); + this.cocktailImgUrl = cocktail.getCocktailImgUrl(); + this.cocktailStory = cocktail.getCocktailStory(); + this.ingredient = cocktail.getIngredient(); + this.recipe = cocktail.getRecipe(); + } +} diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailRequestDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailRequestDto.java deleted file mode 100644 index e2e0c683..00000000 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailRequestDto.java +++ /dev/null @@ -1,29 +0,0 @@ -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 jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -public class CocktailRequestDto { - - @NotBlank - private String cocktailName; - - @NotNull - private AlcoholStrength alcoholStrength; - - private String cocktailStory; - private CocktailType cocktailType; - private AlcoholBaseType alcoholBaseType; - private String ingredient; - private String recipe; - private String cocktailImgUrl; -} diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailFilterRequestDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchRequestDto.java similarity index 69% rename from src/main/java/com/back/domain/cocktail/dto/CocktailFilterRequestDto.java rename to src/main/java/com/back/domain/cocktail/dto/CocktailSearchRequestDto.java index 8b092451..530437c5 100644 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailFilterRequestDto.java +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchRequestDto.java @@ -3,6 +3,8 @@ import com.back.domain.cocktail.enums.AlcoholBaseType; import com.back.domain.cocktail.enums.AlcoholStrength; import com.back.domain.cocktail.enums.CocktailType; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -12,7 +14,7 @@ @Getter @Setter @NoArgsConstructor -public class CocktailFilterRequestDto { +public class CocktailSearchRequestDto { private String keyword; // 검색 키워드 @@ -23,15 +25,20 @@ public class CocktailFilterRequestDto { private List alcoholBaseTypes; // 페이징/정렬 추가하고 싶으면 여기 옵션 추가 + @Min(0) private Integer page; // 0-based 페이지 번호 + @Max(100) private Integer size; // 페이지 사이즈 // 생성자 - public CocktailFilterRequestDto(String keyword, - List alcoholStrengths, - List cocktailTypes, - List alcoholBaseTypes, - Integer page, Integer size) { + public CocktailSearchRequestDto( + String keyword, + List alcoholStrengths, + List cocktailTypes, + List alcoholBaseTypes, + Integer page, + Integer size + ) { this.keyword = keyword; this.alcoholStrengths = alcoholStrengths; this.cocktailTypes = cocktailTypes; diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailResponseDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java similarity index 68% rename from src/main/java/com/back/domain/cocktail/dto/CocktailResponseDto.java rename to src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java index a89c642e..641f033e 100644 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailResponseDto.java +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailSearchResponseDto.java @@ -12,24 +12,21 @@ @Getter @Setter @NoArgsConstructor -public class CocktailResponseDto { +public class CocktailSearchResponseDto { private long cocktailId; private String cocktailName; private AlcoholStrength alcoholStrength; - private String cocktailStory; private CocktailType cocktailType; private AlcoholBaseType alcoholBaseType; - private String ingredient; - private String recipe; private String cocktailImgUrl; + private String cocktailStory; private LocalDateTime createdAt; - private LocalDateTime updatedAt; - public CocktailResponseDto(long cocktailId, String cocktailName, - AlcoholStrength alcoholStrength, CocktailType cocktailType, - AlcoholBaseType alcoholBaseType, String cocktailImgUrl, - String cocktailStory, LocalDateTime createdAt) { + public CocktailSearchResponseDto(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; diff --git a/src/main/java/com/back/domain/cocktail/dto/CocktailSummaryDto.java b/src/main/java/com/back/domain/cocktail/dto/CocktailSummaryResponseDto.java similarity index 69% rename from src/main/java/com/back/domain/cocktail/dto/CocktailSummaryDto.java rename to src/main/java/com/back/domain/cocktail/dto/CocktailSummaryResponseDto.java index 436e9052..9c53c11b 100644 --- a/src/main/java/com/back/domain/cocktail/dto/CocktailSummaryDto.java +++ b/src/main/java/com/back/domain/cocktail/dto/CocktailSummaryResponseDto.java @@ -3,12 +3,12 @@ import lombok.Getter; @Getter -public class CocktailSummaryDto { +public class CocktailSummaryResponseDto { private Long cocktailId; private String cocktailName; private String cocktailImgUrl; - public CocktailSummaryDto(Long id, String name, String imageUrl) { + public CocktailSummaryResponseDto(Long id, String name, String imageUrl) { this.cocktailId = id; this.cocktailName = name; this.cocktailImgUrl = imageUrl; 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 dc610593..a4d6620a 100644 --- a/src/main/java/com/back/domain/cocktail/service/CocktailService.java +++ b/src/main/java/com/back/domain/cocktail/service/CocktailService.java @@ -1,9 +1,9 @@ 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.dto.CocktailDetailResponseDto; +import com.back.domain.cocktail.dto.CocktailSearchRequestDto; +import com.back.domain.cocktail.dto.CocktailSearchResponseDto; +import com.back.domain.cocktail.dto.CocktailSummaryResponseDto; import com.back.domain.cocktail.entity.Cocktail; import com.back.domain.cocktail.enums.AlcoholBaseType; import com.back.domain.cocktail.enums.AlcoholStrength; @@ -31,13 +31,14 @@ public class CocktailService { @Transactional(readOnly = true) public Cocktail getCocktailById(Long id) { + return cocktailRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id)); - } + } // 칵테일 무한스크롤 조회 @Transactional(readOnly = true) - public List getCocktails (Long lastId, Integer size) + public List getCocktails (Long lastId, Integer size) { // 무한스크롤 조회, 클라이언트 쪽에서 lastId와 size 정보를 받음.(스크롤 이벤트) int fetchSize = (size != null) ? size : DEFAULT_SIZE; @@ -50,7 +51,7 @@ public List getCocktails (Long lastId, Integer size) cocktails = cocktailRepository.findByCocktailIdLessThanOrderByCocktailIdDesc(lastId, PageRequest.of(0, fetchSize)); } return cocktails.stream() - .map(c -> new CocktailSummaryDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl())) + .map(c -> new CocktailSummaryResponseDto(c.getCocktailId(), c.getCocktailName(), c.getCocktailImgUrl())) .collect(Collectors.toList()); } @@ -69,33 +70,33 @@ public List cocktailSearch (String keyword){ // 칵테일 검색,필터기능 @Transactional(readOnly = true) - public List searchAndFilter (CocktailFilterRequestDto cocktailFilterRequestDto){ + public List searchAndFilter (CocktailSearchRequestDto cocktailSearchRequestDto){ // 기본값 페이지/사이즈 정하기(PAGE 기본값 0, 사이즈 10) - int page = cocktailFilterRequestDto.getPage() != null && cocktailFilterRequestDto.getPage() >= 0 - ? cocktailFilterRequestDto.getPage() : 0; + int page = cocktailSearchRequestDto.getPage() != null && cocktailSearchRequestDto.getPage() >= 0 + ? cocktailSearchRequestDto.getPage() : 0; - int size = cocktailFilterRequestDto.getSize() != null && cocktailFilterRequestDto.getSize() > 0 - ? cocktailFilterRequestDto.getSize() : DEFAULT_SIZE; + int size = cocktailSearchRequestDto.getSize() != null && cocktailSearchRequestDto.getSize() > 0 + ? cocktailSearchRequestDto.getSize() : DEFAULT_SIZE; // searchWithFilters에서 조회한 결과값을 pageResult에 저장. Pageable pageable = PageRequest.of(page, size); // 빈 리스트(null 또는 [])는 null로 변환 - List strengths = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholStrengths()) + List strengths = CollectionUtils.isEmpty(cocktailSearchRequestDto.getAlcoholStrengths()) ? null - : cocktailFilterRequestDto.getAlcoholStrengths(); + : cocktailSearchRequestDto.getAlcoholStrengths(); - List types = CollectionUtils.isEmpty(cocktailFilterRequestDto.getCocktailTypes()) + List types = CollectionUtils.isEmpty(cocktailSearchRequestDto.getCocktailTypes()) ? null - : cocktailFilterRequestDto.getCocktailTypes(); + : cocktailSearchRequestDto.getCocktailTypes(); - List bases = CollectionUtils.isEmpty(cocktailFilterRequestDto.getAlcoholBaseTypes()) + List bases = CollectionUtils.isEmpty(cocktailSearchRequestDto.getAlcoholBaseTypes()) ? null - : cocktailFilterRequestDto.getAlcoholBaseTypes(); + : cocktailSearchRequestDto.getAlcoholBaseTypes(); // Repository 호출 Page pageResult = cocktailRepository.searchWithFilters( - cocktailFilterRequestDto.getKeyword(), + cocktailSearchRequestDto.getKeyword(), strengths, // List types, // List bases, // List @@ -103,8 +104,8 @@ public List searchAndFilter (CocktailFilterRequestDto cockt ); //Cocktail 엔티티 → CocktailResponseDto 응답 DTO로 바꿔주는 과정 - List resultDtos = pageResult.stream() - .map(c -> new CocktailResponseDto( + List resultDtos = pageResult.stream() + .map(c -> new CocktailSearchResponseDto( c.getCocktailId(), c.getCocktailName(), c.getAlcoholStrength(), @@ -125,9 +126,9 @@ public List searchAndFilter (CocktailFilterRequestDto cockt // 칵테일 상세조회 @Transactional(readOnly = true) - public CocktailDetailDto getCocktailDetailById (Long cocktailId){ + public CocktailDetailResponseDto getCocktailDetailById (Long cocktailId){ Cocktail cocktail = cocktailRepository.findById(cocktailId) .orElseThrow(() -> new NoSuchElementException("칵테일을 찾을 수 없습니다. id: " + cocktailId)); - return new CocktailDetailDto(cocktail); + return new CocktailDetailResponseDto(cocktail); } } diff --git a/src/main/java/com/back/global/init/DevInitData.java b/src/main/java/com/back/global/init/DevInitData.java index 69d0c462..3098d152 100644 --- a/src/main/java/com/back/global/init/DevInitData.java +++ b/src/main/java/com/back/global/init/DevInitData.java @@ -1,6 +1,6 @@ package com.back.global.init; -import com.back.domain.cocktail.dto.CocktailFilterRequestDto; +import com.back.domain.cocktail.dto.CocktailSearchRequestDto; import com.back.domain.cocktail.entity.Cocktail; import com.back.domain.cocktail.enums.AlcoholStrength; import com.back.domain.cocktail.repository.CocktailRepository; @@ -51,12 +51,11 @@ public void cocktailInit() { .build()); } - CocktailFilterRequestDto filterDto = new CocktailFilterRequestDto(); + CocktailSearchRequestDto filterDto = new CocktailSearchRequestDto(); 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)); diff --git a/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java b/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java index fc44f4d2..5dbad44d 100644 --- a/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java +++ b/src/test/java/com/back/domain/cocktail/controller/CocktailControllerTest.java @@ -5,6 +5,7 @@ import com.back.domain.cocktail.enums.AlcoholStrength; import com.back.domain.cocktail.enums.CocktailType; import com.back.domain.cocktail.repository.CocktailRepository; +import com.back.domain.cocktail.service.CocktailService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -28,15 +29,15 @@ @AutoConfigureMockMvc(addFilters = false) @Transactional public class CocktailControllerTest { - @Autowired private MockMvc mvc; + @Autowired private CocktailRepository cocktailRepository; + @Autowired + private CocktailService cocktailService; -// @Autowired -// private UserService userService; @Test @DisplayName("칵테일 단건 조회 - 로그인 없이 성공") @@ -89,4 +90,44 @@ void t2() throws Exception { .andExpect(jsonPath("$.message").value("해당 데이터가 존재하지 않습니다")) .andExpect(jsonPath("$.data").isEmpty()); } + + @Test + @DisplayName("칵테일 다건 조회 - 성공 (파라미터 없음)") + void t3() throws Exception { + // when + ResultActions resultActions = mvc.perform( + get("/api/cocktails") + .contentType(MediaType.APPLICATION_JSON) + ).andDo(print()); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data").isArray()); + } + + @Test + @DisplayName("칵테일 다건 조회 - 성공 (파라미터 포함)") + void t4() throws Exception { + // given + Long lastId = 1L; + int size = 5; + + // when + ResultActions resultActions = mvc.perform( + get("/api/cocktails") + .param("lastId", lastId.toString()) + .param("size", String.valueOf(size)) + .contentType(MediaType.APPLICATION_JSON) + ).andDo(print()); + + // then + resultActions + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.message").value("success")) + .andExpect(jsonPath("$.data").isArray()); + } }