From eeb9920db31f95ed62ee2885962ba9fd875bfc8a Mon Sep 17 00:00:00 2001 From: Hansu Park Date: Tue, 16 Jan 2024 15:54:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=20=EB=AA=A9=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 메뉴 카테고리 조회 기능 구현 * refactor: 응답 DTO 생성 로직 수정 * refactor: cascade 처리 방식 변경 refactor: cascade 처리 방식 변경 * test: 테스트 문맥 변경 --- .../shop/controller/ShopController.java | 7 ++ .../shop/dto/MenuCategoriesResponse.java | 25 +++++++ .../domain/shop/model/MenuCategoryMap.java | 3 +- .../repository/MenuCategoryRepository.java | 13 ++++ .../koin/domain/shop/service/ShopService.java | 11 ++- .../koin/acceptance/ShopApiTest.java | 74 ++++++++++++++++++- 6 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 src/main/java/in/koreatech/koin/domain/shop/dto/MenuCategoriesResponse.java create mode 100644 src/main/java/in/koreatech/koin/domain/shop/repository/MenuCategoryRepository.java diff --git a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java index efb4520ef..fd6a99000 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java +++ b/src/main/java/in/koreatech/koin/domain/shop/controller/ShopController.java @@ -5,6 +5,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; +import in.koreatech.koin.domain.shop.dto.MenuCategoriesResponse; import in.koreatech.koin.domain.shop.dto.ShopMenuResponse; import in.koreatech.koin.domain.shop.service.ShopService; import lombok.RequiredArgsConstructor; @@ -20,4 +21,10 @@ public ResponseEntity findMenu(@PathVariable Long shopId, @Pat ShopMenuResponse shopMenu = shopService.findMenu(menuId); return ResponseEntity.ok(shopMenu); } + + @GetMapping("/shops/{shopId}/menus/categories") + public ResponseEntity findMenuCategories(@PathVariable Long shopId) { + MenuCategoriesResponse menuCategories = shopService.getMenuCategories(shopId); + return ResponseEntity.ok(menuCategories); + } } diff --git a/src/main/java/in/koreatech/koin/domain/shop/dto/MenuCategoriesResponse.java b/src/main/java/in/koreatech/koin/domain/shop/dto/MenuCategoriesResponse.java new file mode 100644 index 000000000..c8fec73bb --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/shop/dto/MenuCategoriesResponse.java @@ -0,0 +1,25 @@ +package in.koreatech.koin.domain.shop.dto; + +import java.util.List; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +import in.koreatech.koin.domain.shop.model.MenuCategory; + +@JsonNaming(value = SnakeCaseStrategy.class) +public record MenuCategoriesResponse(Long count, List menuCategories) { + public static MenuCategoriesResponse from(List menuCategories) { + List categories = menuCategories.stream() + .map(menuCategory -> MenuCategoryResponse.of(menuCategory.getId(), menuCategory.getName())) + .toList(); + + return new MenuCategoriesResponse((long)categories.size(), categories); + } + + private record MenuCategoryResponse(Long id, String name) { + public static MenuCategoryResponse of(Long id, String name) { + return new MenuCategoryResponse(id, name); + } + } +} diff --git a/src/main/java/in/koreatech/koin/domain/shop/model/MenuCategoryMap.java b/src/main/java/in/koreatech/koin/domain/shop/model/MenuCategoryMap.java index b5417e86f..9765179f1 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/model/MenuCategoryMap.java +++ b/src/main/java/in/koreatech/koin/domain/shop/model/MenuCategoryMap.java @@ -1,6 +1,5 @@ package in.koreatech.koin.domain.shop.model; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -29,7 +28,7 @@ public class MenuCategoryMap { @JoinColumn(name = "shop_menu_id") private Menu menu; - @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "shop_menu_category_id") private MenuCategory menuCategory; diff --git a/src/main/java/in/koreatech/koin/domain/shop/repository/MenuCategoryRepository.java b/src/main/java/in/koreatech/koin/domain/shop/repository/MenuCategoryRepository.java new file mode 100644 index 000000000..8fa21336e --- /dev/null +++ b/src/main/java/in/koreatech/koin/domain/shop/repository/MenuCategoryRepository.java @@ -0,0 +1,13 @@ +package in.koreatech.koin.domain.shop.repository; + +import java.util.List; + +import org.springframework.data.repository.Repository; + +import in.koreatech.koin.domain.shop.model.MenuCategory; + +public interface MenuCategoryRepository extends Repository { + List findAllByShopId(Long shopId); + + MenuCategory save(MenuCategory menuCategory); +} diff --git a/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java b/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java index 9d2ee55b2..89e4d0dea 100644 --- a/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java +++ b/src/main/java/in/koreatech/koin/domain/shop/service/ShopService.java @@ -5,10 +5,12 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import in.koreatech.koin.domain.shop.dto.MenuCategoriesResponse; +import in.koreatech.koin.domain.shop.dto.ShopMenuResponse; import in.koreatech.koin.domain.shop.model.Menu; import in.koreatech.koin.domain.shop.model.MenuCategory; import in.koreatech.koin.domain.shop.model.MenuCategoryMap; -import in.koreatech.koin.domain.shop.dto.ShopMenuResponse; +import in.koreatech.koin.domain.shop.repository.MenuCategoryRepository; import in.koreatech.koin.domain.shop.repository.MenuRepository; import lombok.RequiredArgsConstructor; @@ -18,6 +20,7 @@ public class ShopService { private final MenuRepository menuRepository; + private final MenuCategoryRepository menuCategoryRepository; public ShopMenuResponse findMenu(Long menuId) { Menu menu = menuRepository.findById(menuId) @@ -37,4 +40,10 @@ private ShopMenuResponse createShopMenuResponse(Menu menu, List me } return ShopMenuResponse.createForSingleOption(menu, menuCategories); } + + public MenuCategoriesResponse getMenuCategories(Long shopId) { + //TODO 존재하는 상점인지 검증하고, 없다면 401를 반환하여야 한다. 작업시점: 상점 도메인 조회 기능 추가시 + List menuCategories = menuCategoryRepository.findAllByShopId(shopId); + return MenuCategoriesResponse.from(menuCategories); + } } diff --git a/src/test/java/in/koreatech/koin/acceptance/ShopApiTest.java b/src/test/java/in/koreatech/koin/acceptance/ShopApiTest.java index 72acf9604..8aa830bbe 100644 --- a/src/test/java/in/koreatech/koin/acceptance/ShopApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/ShopApiTest.java @@ -12,6 +12,7 @@ import in.koreatech.koin.domain.shop.model.MenuCategoryMap; import in.koreatech.koin.domain.shop.model.MenuImage; import in.koreatech.koin.domain.shop.model.MenuOption; +import in.koreatech.koin.domain.shop.repository.MenuCategoryRepository; import in.koreatech.koin.domain.shop.repository.MenuRepository; import io.restassured.RestAssured; import io.restassured.response.ExtractableResponse; @@ -22,6 +23,9 @@ class ShopApiTest extends AcceptanceTest { @Autowired private MenuRepository menuRepository; + @Autowired + private MenuCategoryRepository menuCategoryRepository; + @Test @DisplayName("옵션이 하나 있는 상점의 메뉴를 조회한다.") void findMenuSingleOption() { @@ -45,15 +49,15 @@ void findMenuSingleOption() { .shopId(1L) .name("중식") .build(); + menuCategoryRepository.save(menuCategory); MenuCategoryMap menuCategoryMap = MenuCategoryMap.create(); - // when then menuOption.setMenu(menu); menuImage.setMenu(menu); + // when then menuCategoryMap.map(menu, menuCategory); - menuRepository.save(menu); ExtractableResponse response = RestAssured @@ -124,6 +128,7 @@ void findMenuMultipleOption() { .build(); MenuCategoryMap menuCategoryMap = MenuCategoryMap.create(); + menuCategoryRepository.save(menuCategory); // when then menuOption1.setMenu(menu); @@ -177,4 +182,69 @@ void findMenuMultipleOption() { } ); } + + @Test + @DisplayName("상점의 메뉴 카테고리들을 조회한다.") + void findShopMenuCategories() { + // given + final long SHOP_ID = 1L; + + Menu menu = Menu.builder() + .shopId(SHOP_ID) + .name("짜장면") + .description("맛있는 짜장면") + .build(); + + MenuCategory menuCategory1 = MenuCategory.builder() + .shopId(SHOP_ID) + .name("이벤트 메뉴") + .build(); + + MenuCategory menuCategory2 = MenuCategory.builder() + .shopId(SHOP_ID) + .name("메인 메뉴") + .build(); + + menuCategoryRepository.save(menuCategory1); + menuCategoryRepository.save(menuCategory2); + + MenuCategoryMap menuCategoryMap1 = MenuCategoryMap.create(); + MenuCategoryMap menuCategoryMap2 = MenuCategoryMap.create(); + + // when then + menuCategoryMap1.map(menu, menuCategory1); + menuCategoryMap2.map(menu, menuCategory2); + + menuRepository.save(menu); + + ExtractableResponse response = RestAssured + .given() + .log().all() + .when() + .log().all() + .get("/shops/{shopId}/menus/categories", menu.getShopId()) + .then() + .log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(response.body().jsonPath().getLong("count")).isEqualTo(2); + + softly.assertThat(response.body().jsonPath().getList("menu_categories")) + .hasSize(2); + + softly.assertThat(response.body().jsonPath().getLong("menu_categories[0].id")) + .isEqualTo(menuCategory1.getId()); + softly.assertThat(response.body().jsonPath().getString("menu_categories[0].name")) + .isEqualTo(menuCategory1.getName()); + + softly.assertThat(response.body().jsonPath().getLong("menu_categories[1].id")) + .isEqualTo(menuCategory2.getId()); + softly.assertThat(response.body().jsonPath().getString("menu_categories[1].name")) + .isEqualTo(menuCategory2.getName()); + } + ); + } }