diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/drink/controller/DrinkController.java b/oversweet-api/src/main/java/com/depromeet/oversweet/drink/controller/DrinkController.java index 9e1738c..01dc8a4 100644 --- a/oversweet-api/src/main/java/com/depromeet/oversweet/drink/controller/DrinkController.java +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/drink/controller/DrinkController.java @@ -1,44 +1,47 @@ package com.depromeet.oversweet.drink.controller; +import static org.springframework.http.HttpStatus.OK; + +import java.time.LocalDate; +import java.util.List; + +import com.depromeet.oversweet.search.dto.response.DrinkAllInfoResponse; +import com.depromeet.oversweet.search.service.DrinkSearchService; +import io.swagger.v3.oas.annotations.Parameter; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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.RequestParam; +import org.springframework.web.bind.annotation.RestController; + import com.depromeet.oversweet.annotation.SecurityExclusion; -import com.depromeet.oversweet.domain.franchise.entity.FranchiseEntity; -import com.depromeet.oversweet.drink.dto.request.DrinkInfoRequest; -import com.depromeet.oversweet.drink.dto.request.DrinkWeeklySugarDateRequest; -import com.depromeet.oversweet.drink.dto.response.DrinkAllInfoResponse; import com.depromeet.oversweet.drink.dto.response.DrinkDailySugarStatisticsResponse; import com.depromeet.oversweet.drink.dto.response.DrinkDetailInfoResponse; +import com.depromeet.oversweet.drink.dto.response.DrinkRecommendResponse; import com.depromeet.oversweet.drink.dto.response.DrinkRedisInfo; import com.depromeet.oversweet.drink.dto.response.DrinkWeeklySugarStatisticsResponse; import com.depromeet.oversweet.drink.service.DrinkDailyStatisticsService; import com.depromeet.oversweet.drink.service.DrinkDetailSearchService; +import com.depromeet.oversweet.drink.service.DrinkRecommendService; import com.depromeet.oversweet.drink.service.DrinkRedisService; -import com.depromeet.oversweet.drink.service.DrinkSearchService; import com.depromeet.oversweet.drink.service.DrinkWeeklyStatisticsService; import com.depromeet.oversweet.response.DataResponse; import com.depromeet.oversweet.security.service.CustomUserDetails; + import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import java.util.List; - -import static org.springframework.http.HttpStatus.OK; @Tag(name = "음료", description = "음료 관련 API") @RestController @@ -50,6 +53,7 @@ public class DrinkController { private final DrinkWeeklyStatisticsService drinkWeeklyStatisticsService; private final DrinkDetailSearchService drinkDetailSearchService; private final DrinkRedisService drinkRedisService; + private final DrinkRecommendService drinkRecommendService; private final DrinkSearchService drinkSearchService; /** @@ -77,10 +81,11 @@ public ResponseEntity> retrieveU @SecurityRequirement(name = "accessToken") @GetMapping("/statistics/weekly") public ResponseEntity> retrieveUserWeeklySugarStatistics( - @RequestBody @Valid final DrinkWeeklySugarDateRequest request, + @RequestParam("startDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam("endDate") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate, @AuthenticationPrincipal CustomUserDetails userDetails ) { - final DrinkWeeklySugarStatisticsResponse response = drinkWeeklyStatisticsService.retrieveUserWeeklySugarStatistics(userDetails.getId(), request); + final DrinkWeeklySugarStatisticsResponse response = drinkWeeklyStatisticsService.retrieveUserWeeklySugarStatistics(userDetails.getId(), startDate, endDate); return ResponseEntity.ok() .body(DataResponse.of(HttpStatus.OK, "유저가 먹은 주간 당 통계 조회 성공", response)); } @@ -93,10 +98,11 @@ public ResponseEntity> retrieve @SecurityRequirement(name = "accessToken") @GetMapping("/detail") public ResponseEntity> retrieveDrinkDetail( - @RequestBody @Valid final DrinkInfoRequest request, + @RequestParam @Valid @NotNull(message = "필수 값 입니다.") Long franchiseId, + @RequestParam @Valid @NotEmpty(message = "필수 값 입니다.") String drinkName, @AuthenticationPrincipal CustomUserDetails userDetails ) { - DrinkDetailInfoResponse response = drinkDetailSearchService.retrieveDrinkDetail(userDetails.getId(), request); + DrinkDetailInfoResponse response = drinkDetailSearchService.retrieveDrinkDetail(userDetails.getId(), franchiseId, drinkName); return ResponseEntity.ok().body(DataResponse.of(HttpStatus.OK, "음료 상세 조회 성공", response)); } @@ -110,15 +116,17 @@ public ResponseEntity>> getOrCreateDrinkAtRedi .body(DataResponse.of(OK, "레디스에 저장된 음료 목록 조회 성공", drinks)); } - @Operation(summary = "해당 프랜차이즈의 음료 목록을 키워드로 조회합니다.", description = "해당 프랜차이즈의 키워드에 매칭되는 음료 조회 API") - @ApiResponses(@ApiResponse(responseCode = "200", description = "해당 프랜차이즈의 키워드에 매칭되는 음료 조회 성공")) - @GetMapping("/{franchiseId}/search") - public ResponseEntity>> getDrinksByKeywordAndFranchise( - @PathVariable @Parameter(description = "프랜차이즈 Id") final Long franchiseId, - @RequestParam @Parameter(description = "프랜차이즈 검색을 위한 키워드") final String keyword) { - final List drinks = drinkSearchService.getDrinksByKeywordAndFranchise(franchiseId, keyword); - return ResponseEntity.ok() - .body(DataResponse.of(OK, "해당 프랜차이즈의 키워드에 매칭되는 음료 조회 성공", drinks)); + + /** + * 음료 사이즈 기준으로 당 성분이 비슷한 음료 추천 + */ + @Operation(summary = "음료 사이즈 기준으로 당 성분이 비슷한 음료 추천", description = "음료 사이즈 기준으로 당 성분이 비슷한 음료 추천") + @ApiResponses(@ApiResponse(responseCode = "200", description = "음료 사이즈 기준으로 당 성분이 비슷한 음료 추천")) + @SecurityExclusion + @GetMapping("/recommend/{drinkId}") + public ResponseEntity> recommendDrink(@PathVariable Long drinkId) { + DrinkRecommendResponse response = drinkRecommendService.recommendDrink(drinkId); + return ResponseEntity.ok().body(DataResponse.of(OK, "음료 사이즈 기준으로 당 성분이 비슷한 음료 추천 성공", response)); } /** diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/drink/service/DrinkSearchService.java b/oversweet-api/src/main/java/com/depromeet/oversweet/drink/service/DrinkSearchService.java deleted file mode 100644 index a6c5048..0000000 --- a/oversweet-api/src/main/java/com/depromeet/oversweet/drink/service/DrinkSearchService.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.depromeet.oversweet.drink.service; - - -import com.depromeet.oversweet.domain.drink.entity.DrinkEntity; -import com.depromeet.oversweet.domain.drink.repository.FindDrinksByFranchiseAndCategoryRepository; -import com.depromeet.oversweet.domain.drink.repository.FindDrinksByFranchiseAndKeywordRepository; -import com.depromeet.oversweet.domain.franchise.entity.FranchiseEntity; -import com.depromeet.oversweet.domain.franchise.repository.FindFranchiseRepository; -import com.depromeet.oversweet.drink.dto.response.DrinkAllInfoResponse; -import com.depromeet.oversweet.drink.vo.DrinkSameNameInfo; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; - -import static java.util.stream.Collectors.groupingBy; -import static java.util.stream.Collectors.toMap; - -/** - * 해당 프랜차이즈의 음료 검색 서비스 - */ -@Service -@RequiredArgsConstructor -public class DrinkSearchService { - - private final FindFranchiseRepository findFranchiseRepository; - private final FindDrinksByFranchiseAndKeywordRepository findDrinksByFranchiseAndKeywordRepository; - private final FindDrinksByFranchiseAndCategoryRepository findDrinksByFranchiseAndCategoryRepository; - - public List getDrinksByKeywordAndFranchise(final Long franchiseId, final String keyword) { - // 프랜차이즈 존재 여부 확인 - final FranchiseEntity findFranchise = findFranchiseRepository.findFranchiseById(franchiseId); - - // 해당 프랜차이즈의 키워드로 검색 되는 음료 검색 - final List findDrinks = findDrinksByFranchiseAndKeywordRepository.findDrinksByFranchiseAndKeyword(findFranchise.getId(), keyword); - - // 키워드로 검색된 음료 목록 중 이름이 같은 것들로 묶어주기 - // 묶은 목록 중 IsMiniMun 찾기 - final DrinkSameNameInfo sameNameInfo = new DrinkSameNameInfo(findDrinks); - - return sameNameInfo.getSameNameDrinksSize(); - } - - public List getDrinksByFranchiseAndCategoryAndDirection(Long franchiseId, String category, String column, String direction) { - //franchiseId 별로 List 를 묶는다. 이유는 다른 프랜차이즈이지만 음료 이름이 같으면 중복으로 나오기 때문이다. - Map> drinkEntitiesWithFranchise = findDrinksByFranchiseAndCategoryRepository.findDrinksByFranchiseAndCategoryAndDirection(franchiseId, category, column, direction); - - //프랜차이즈별 && 음료명 별로 묶는다. - Map>> drinkEntitiesWithFranchiseGroupByName = drinkEntitiesWithFranchise - .entrySet() - .stream() - .collect(toMap( - Map.Entry::getKey, - entry -> entry.getValue().stream() - .collect(groupingBy(DrinkEntity::getName)) - )); - - List drinkAllInfoResponses = new ArrayList<>(); - drinkEntitiesWithFranchiseGroupByName.entrySet().stream() - .forEach(entry -> { - entry.getValue().entrySet().stream() - .forEach(innerEntry -> { - //프랜차이즈별 && 음료명별 안에서 가장 작은 사이즈의 음료와 그 음료가 가지는 사이즈 list 를 찾아서 넣어준다. - List drinkEntities = innerEntry.getValue(); - - DrinkEntity minimumDrinkEntity = drinkEntities.stream().filter(DrinkEntity::getIsMinimum).findFirst().get(); - List sizes = drinkEntities.stream().map(DrinkEntity::getSize).sorted().toList(); - - DrinkAllInfoResponse drinkAllInfoResponse = DrinkAllInfoResponse.of(minimumDrinkEntity, sizes); - drinkAllInfoResponses.add(drinkAllInfoResponse); - }); - }); - - return sortByColumnAndDirection(drinkAllInfoResponses, column, direction); - } - - private List sortByColumnAndDirection(List drinkAllInfoResponses, String column, String direction) { - switch (column) { - case "sugar": - if (direction.equalsIgnoreCase("desc")) { - return drinkAllInfoResponses.stream() - .sorted(Comparator.comparing(DrinkAllInfoResponse::sugar).reversed()) - .toList(); - } - - return drinkAllInfoResponses.stream() - .sorted(Comparator.comparing(DrinkAllInfoResponse::sugar)) - .toList(); - default: - return drinkAllInfoResponses; - } - } - -} diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/search/controller/SearchController.java b/oversweet-api/src/main/java/com/depromeet/oversweet/search/controller/SearchController.java index 9ec62fd..8802948 100644 --- a/oversweet-api/src/main/java/com/depromeet/oversweet/search/controller/SearchController.java +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/search/controller/SearchController.java @@ -4,8 +4,8 @@ import com.depromeet.oversweet.common.dto.response.FranchiseInfo; import com.depromeet.oversweet.search.dto.response.DrinkAllInfoResponse; import com.depromeet.oversweet.search.dto.response.SearchInfoResponse; -import com.depromeet.oversweet.search.service.DrinkSearchService; import com.depromeet.oversweet.response.DataResponse; +import com.depromeet.oversweet.search.service.DrinkSearchService; import com.depromeet.oversweet.search.service.FranchiseSearchService; import com.depromeet.oversweet.search.service.SearchService; import io.swagger.v3.oas.annotations.Operation; @@ -14,7 +14,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; diff --git a/oversweet-api/src/main/java/com/depromeet/oversweet/search/service/DrinkSearchService.java b/oversweet-api/src/main/java/com/depromeet/oversweet/search/service/DrinkSearchService.java index a6c5048..38de441 100644 --- a/oversweet-api/src/main/java/com/depromeet/oversweet/search/service/DrinkSearchService.java +++ b/oversweet-api/src/main/java/com/depromeet/oversweet/search/service/DrinkSearchService.java @@ -1,13 +1,13 @@ -package com.depromeet.oversweet.drink.service; +package com.depromeet.oversweet.search.service; import com.depromeet.oversweet.domain.drink.entity.DrinkEntity; import com.depromeet.oversweet.domain.drink.repository.FindDrinksByFranchiseAndCategoryRepository; -import com.depromeet.oversweet.domain.drink.repository.FindDrinksByFranchiseAndKeywordRepository; +import com.depromeet.oversweet.domain.drink.repository.FindDrinksSearchRepository; import com.depromeet.oversweet.domain.franchise.entity.FranchiseEntity; import com.depromeet.oversweet.domain.franchise.repository.FindFranchiseRepository; -import com.depromeet.oversweet.drink.dto.response.DrinkAllInfoResponse; -import com.depromeet.oversweet.drink.vo.DrinkSameNameInfo; +import com.depromeet.oversweet.search.dto.response.DrinkAllInfoResponse; +import com.depromeet.oversweet.search.vo.DrinkSameNameInfo; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,7 +27,7 @@ public class DrinkSearchService { private final FindFranchiseRepository findFranchiseRepository; - private final FindDrinksByFranchiseAndKeywordRepository findDrinksByFranchiseAndKeywordRepository; + private final FindDrinksSearchRepository findDrinksSearchRepository; private final FindDrinksByFranchiseAndCategoryRepository findDrinksByFranchiseAndCategoryRepository; public List getDrinksByKeywordAndFranchise(final Long franchiseId, final String keyword) { @@ -35,7 +35,7 @@ public List getDrinksByKeywordAndFranchise(final Long fran final FranchiseEntity findFranchise = findFranchiseRepository.findFranchiseById(franchiseId); // 해당 프랜차이즈의 키워드로 검색 되는 음료 검색 - final List findDrinks = findDrinksByFranchiseAndKeywordRepository.findDrinksByFranchiseAndKeyword(findFranchise.getId(), keyword); + final List findDrinks = findDrinksSearchRepository.findDrinksByFranchiseAndKeyword(findFranchise.getId(), keyword); // 키워드로 검색된 음료 목록 중 이름이 같은 것들로 묶어주기 // 묶은 목록 중 IsMiniMun 찾기 diff --git a/oversweet-api/src/test/java/com/depromeet/oversweet/drink/service/DrinkSearchServiceTest.java b/oversweet-api/src/test/java/com/depromeet/oversweet/drink/service/DrinkSearchServiceTest.java index 29235d8..0614c88 100644 --- a/oversweet-api/src/test/java/com/depromeet/oversweet/drink/service/DrinkSearchServiceTest.java +++ b/oversweet-api/src/test/java/com/depromeet/oversweet/drink/service/DrinkSearchServiceTest.java @@ -4,7 +4,8 @@ import com.depromeet.oversweet.domain.drink.entity.DrinkEntity; import com.depromeet.oversweet.domain.drink.repository.FindDrinksByFranchiseAndCategoryRepository; import com.depromeet.oversweet.domain.franchise.entity.FranchiseEntity; -import com.depromeet.oversweet.drink.dto.response.DrinkAllInfoResponse; +import com.depromeet.oversweet.search.dto.response.DrinkAllInfoResponse; +import com.depromeet.oversweet.search.service.DrinkSearchService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test;