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
Expand Up @@ -5,13 +5,16 @@
import org.springframework.web.bind.annotation.PathVariable;

import in.koreatech.koin.domain.shop.dto.MenuCategoriesResponse;
import in.koreatech.koin.domain.shop.dto.MenuDetailResponse;
import in.koreatech.koin.domain.shop.dto.ShopMenuResponse;
import in.koreatech.koin.domain.shop.dto.ShopResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;

import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH;

import java.util.List;

import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -29,11 +32,25 @@ public interface ShopApi {
)
@Operation(summary = "메뉴 단건 조회")
@GetMapping("/shops/{shopId}/menus/{menuId}")
ResponseEntity<ShopMenuResponse> findMenu(
ResponseEntity<MenuDetailResponse> findMenu(
@Parameter(in = PATH) @PathVariable Long shopId,
@Parameter(in = PATH) @PathVariable Long menuId
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
@ApiResponse(responseCode = "401", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "403", content = @Content(schema = @Schema(hidden = true))),
@ApiResponse(responseCode = "404", content = @Content(schema = @Schema(hidden = true))),
}
)
@Operation(summary = "특정 상점의 모든 메뉴 조회")
@GetMapping("/shops/{id}/menus")
ResponseEntity<ShopMenuResponse> findMenu(
@Parameter(in = PATH) @PathVariable Long id
);

@ApiResponses(
value = {
@ApiResponse(responseCode = "200"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.web.bind.annotation.RestController;

import in.koreatech.koin.domain.shop.dto.MenuCategoriesResponse;
import in.koreatech.koin.domain.shop.dto.MenuDetailResponse;
import in.koreatech.koin.domain.shop.dto.ShopMenuResponse;
import in.koreatech.koin.domain.shop.dto.ShopResponse;
import in.koreatech.koin.domain.shop.service.ShopService;
Expand All @@ -18,14 +19,19 @@ public class ShopController implements ShopApi {
private final ShopService shopService;

@GetMapping("/shops/{shopId}/menus/{menuId}")
public ResponseEntity<ShopMenuResponse> findMenu(
public ResponseEntity<MenuDetailResponse> findMenu(
@PathVariable Long shopId,
@PathVariable Long menuId
) {
ShopMenuResponse shopMenu = shopService.findMenu(menuId);
MenuDetailResponse shopMenu = shopService.findMenu(menuId);
return ResponseEntity.ok(shopMenu);
}

public ResponseEntity<ShopMenuResponse> findMenu(Long id) {
ShopMenuResponse shopMenuResponse = shopService.getShopMenu(id);
return ResponseEntity.ok(shopMenuResponse);
}

@GetMapping("/shops/{shopId}/menus/categories")
public ResponseEntity<MenuCategoriesResponse> getMenuCategories(
@PathVariable Long shopId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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.Menu;
import in.koreatech.koin.domain.shop.model.MenuCategory;
import in.koreatech.koin.domain.shop.model.MenuImage;
import in.koreatech.koin.domain.shop.model.MenuOption;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@JsonNaming(value = SnakeCaseStrategy.class)
public record MenuDetailResponse(
@Schema(example = "1", description = "고유id")
Long id,

@Schema(example = "1", description = "메뉴가 소속된 상점의 고유 id ")
Long shopId,

@Schema(example = "탕수육", description = "이름")
String name,

@Schema(example = "false", description = "숨김 여부")
Boolean isHidden,

@Schema(example = "false", description = "단일 메뉴 여부")
Boolean isSingle,

@Schema(example = "7000", description = "단일 메뉴일때(is_single이 true일때)의 가격")
Integer singlePrice,

@Schema(description = "옵션이 있는 메뉴일때(is_single이 false일때)의 가격")
List<InnerOptionPriceResponse> optionPrices,

@Schema(example = "돼지고기 + 튀김", description = "구성 설명")
String description,

@Schema(description = "소속되어 있는 메뉴 카테고리 고유 id 리스트")
List<Long> categoryIds,

@Schema(description = "이미지 URL 리스트")
List<String> imageUrls
) {

public static MenuDetailResponse createForSingleOption(Menu menu, List<MenuCategory> shopMenuCategories) {
if (menu.hasMultipleOption()) {
log.warn("{}는 옵션이 하나 이상인 메뉴입니다. createForMultipleOption 메서드를 이용해야 합니다.", menu);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A

상세한 로깅 좋네요

throw new IllegalStateException("서버에 에러가 발생했습니다.");
}

return new MenuDetailResponse(
menu.getId(),
menu.getShopId(),
menu.getName(),
menu.getIsHidden(),
true,
menu.getMenuOptions().get(0).getPrice(),
null,
menu.getDescription(),
shopMenuCategories.stream().map(MenuCategory::getId).toList(),
menu.getMenuImages().stream().map(MenuImage::getImageUrl).toList()
);
}

public static MenuDetailResponse createForMultipleOption(Menu menu, List<MenuCategory> shopMenuCategories) {
if (!menu.hasMultipleOption()) {
log.error("{}는 옵션이 하나인 메뉴입니다. createForSingleOption 메서드를 이용해야 합니다.", menu);
throw new IllegalStateException("서버에 에러가 발생했습니다.");
}

return new MenuDetailResponse(
menu.getId(),
menu.getShopId(),
menu.getName(),
menu.getIsHidden(),
false,
null,
menu.getMenuOptions().stream().map(InnerOptionPriceResponse::of).toList(), menu.getDescription(),
shopMenuCategories.stream().map(MenuCategory::getId).toList(),
menu.getMenuImages().stream().map(MenuImage::getImageUrl).toList()
);
}

private record InnerOptionPriceResponse(
@Schema(example = "소", description = "옵션명")
String option,

@Schema(example = "10000", description = "옵션에 대한 가격")
Integer price
) {
public static InnerOptionPriceResponse of(MenuOption menuOption) {
return new InnerOptionPriceResponse(menuOption.getOption(), menuOption.getPrice());
}
}
}
141 changes: 101 additions & 40 deletions src/main/java/in/koreatech/koin/domain/shop/dto/ShopMenuResponse.java
Original file line number Diff line number Diff line change
@@ -1,64 +1,125 @@
package in.koreatech.koin.domain.shop.dto;

import java.time.LocalDateTime;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy;
import com.fasterxml.jackson.databind.annotation.JsonNaming;

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.model.MenuImage;
import in.koreatech.koin.domain.shop.model.MenuOption;
import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.media.Schema;

@Slf4j
@JsonNaming(value = SnakeCaseStrategy.class)
public record ShopMenuResponse(Long id, Long shopId, String name, Boolean isHidden, Boolean isSingle,
Integer singlePrice, List<InnerOptionPriceResponse> optionPrices, String description,
List<Long> categoryIds, List<String> imageUrls) {

public static ShopMenuResponse createForSingleOption(Menu menu, List<MenuCategory> shopMenuCategories) {
if (menu.hasMultipleOption()) {
log.warn("{}는 옵션이 하나 이상인 메뉴입니다. createForMultipleOption 메서드를 이용해야 합니다.", menu);
throw new IllegalStateException("서버에 에러가 발생했습니다.");
}
public record ShopMenuResponse(
@Schema(example = "20", description = "개수")
Integer count,

@Schema(description = "카테고리 별로 분류된 소속 메뉴 리스트")
List<InnerMenuCategoriesResponse> menuCategories,

@Schema(example = "2024-03-16", description = "해당 상점 마지막 메뉴 업데이트 날짜")
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDateTime updatedAt
) {
public static ShopMenuResponse from(List<MenuCategory> menuCategories) {
LocalDateTime lastUpdatedDate = LocalDateTime.of(1900, 1, 1, 0, 0);
int count = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C

메뉴 개수면 menuCategories.size()로 충분하지 않나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MenuCategory객체 내부에 메뉴가 여러개 있을 수 있는 구조라서 전부 순회했습니다ㅠ

ex)
menu_categories[
카테고리1[
메뉴1
메뉴2
.
.
카테고리2[
메뉴1
메뉴2
.
.
]
]

for (MenuCategory menuCategory : menuCategories) {
for (MenuCategoryMap menuCategoryMap : menuCategory.getMenuCategoryMaps()) {
LocalDateTime updatedAt = menuCategoryMap.getMenu().getUpdatedAt();
if (updatedAt.isAfter(lastUpdatedDate)) {
lastUpdatedDate = updatedAt;
}
++count;
}
}
return new ShopMenuResponse(
menu.getId(),
menu.getShopId(),
menu.getName(),
menu.getIsHidden(),
true,
menu.getMenuOptions().get(0).getPrice(),
null,
menu.getDescription(),
shopMenuCategories.stream().map(MenuCategory::getId).toList(),
menu.getMenuImages().stream().map(MenuImage::getImageUrl).toList()
count,
menuCategories.stream().map(InnerMenuCategoriesResponse::from).toList(),
lastUpdatedDate
);
}

public static ShopMenuResponse createForMultipleOption(Menu menu, List<MenuCategory> shopMenuCategories) {
if (!menu.hasMultipleOption()) {
log.error("{}는 옵션이 하나인 메뉴입니다. createForSingleOption 메서드를 이용해야 합니다.", menu);
throw new IllegalStateException("서버에 에러가 발생했습니다.");
@JsonNaming(value = SnakeCaseStrategy.class)
private record InnerMenuCategoriesResponse(
@Schema(example = "1", description = "카테고리 id")
Long id,

@Schema(example = "중식", description = "카테고리 이름")
String name,

@Schema(description = "해당 상점의 모든 메뉴 리스트")
List<InnerMenuResponse> menuResponses
) {
public static InnerMenuCategoriesResponse from(MenuCategory menuCategory) {
return new InnerMenuCategoriesResponse(
menuCategory.getId(),
menuCategory.getName(),
menuCategory.getMenuCategoryMaps().stream().map(InnerMenuResponse::from).toList()
);
}

return new ShopMenuResponse(
menu.getId(),
menu.getShopId(),
menu.getName(),
menu.getIsHidden(),
false,
null,
menu.getMenuOptions().stream().map(InnerOptionPriceResponse::of).toList(), menu.getDescription(),
shopMenuCategories.stream().map(MenuCategory::getId).toList(),
menu.getMenuImages().stream().map(MenuImage::getImageUrl).toList()
);
}
@JsonNaming(value = SnakeCaseStrategy.class)
private record InnerMenuResponse(
@Schema(example = "저희 식당의 대표 메뉴 탕수육입니다.", description = "설명")
String description,

@Schema(example = "1", description = "고유 id")
Long id,

@Schema(description = "이미지 URL리스트")
List<String> imageUrls,

@Schema(example = "false", description = "숨김 여부")
Boolean isHidden,

@Schema(example = "false", description = "단일 메뉴 여부")
Boolean isSingle,

@Schema(example = "탕수육", description = "이름")
String name,

@Schema(description = "옵션이 있는 메뉴일때(is_single이 false일때)의 옵션에 따른 가격 리스트")
List<InnerOptionPrice> optionPrices,

@Schema(example = "10000", description = "단일 메뉴일때(is_single이 true일때)의 가격")
Integer singlePrice
) {
public static InnerMenuResponse from(MenuCategoryMap menuCategoryMap) {
Menu menu = menuCategoryMap.getMenu();
boolean isSingle = !menu.hasMultipleOption();
return new InnerMenuResponse(
menu.getDescription(),
menu.getId(),
menu.getMenuImages().stream().map(MenuImage::getImageUrl).toList(),
menu.getIsHidden(),
isSingle,
menu.getName(),
isSingle ? null : menu.getMenuOptions().stream().map(InnerOptionPrice::from).toList(),
isSingle ? menu.getMenuOptions().get(0).getPrice() : null
);
}

@JsonNaming(value = SnakeCaseStrategy.class)
private record InnerOptionPrice(
@Schema(example = "대", description = "옵션명")
String option,

private record InnerOptionPriceResponse(String option, Integer price) {
public static InnerOptionPriceResponse of(MenuOption menuOption) {
return new InnerOptionPriceResponse(menuOption.getOption(), menuOption.getPrice());
@Schema(example = "26000", description = "가격")
Integer price
) {
public static InnerOptionPrice from(MenuOption menuOption) {
return new InnerOptionPrice(
menuOption.getOption(),
menuOption.getPrice()
);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.springframework.transaction.annotation.Transactional;

import in.koreatech.koin.domain.shop.dto.MenuCategoriesResponse;
import in.koreatech.koin.domain.shop.dto.MenuDetailResponse;
import in.koreatech.koin.domain.shop.dto.ShopMenuResponse;
import in.koreatech.koin.domain.shop.dto.ShopResponse;
import in.koreatech.koin.domain.shop.model.Menu;
Expand Down Expand Up @@ -35,22 +36,22 @@ public class ShopService {
private final ShopCategoryMapRepository shopCategoryMapRepository;
private final ShopImageRepository shopImageRepository;

public ShopMenuResponse findMenu(Long menuId) {
public MenuDetailResponse findMenu(Long menuId) {
Menu menu = menuRepository.getById(menuId);

List<MenuCategory> menuCategories = menu.getMenuCategoryMaps()
.stream()
.map(MenuCategoryMap::getMenuCategory)
.toList();

return createShopMenuResponse(menu, menuCategories);
return createMenuDetailResponse(menu, menuCategories);
}

private ShopMenuResponse createShopMenuResponse(Menu menu, List<MenuCategory> menuCategories) {
private MenuDetailResponse createMenuDetailResponse(Menu menu, List<MenuCategory> menuCategories) {
if (menu.hasMultipleOption()) {
return ShopMenuResponse.createForMultipleOption(menu, menuCategories);
return MenuDetailResponse.createForMultipleOption(menu, menuCategories);
}
return ShopMenuResponse.createForSingleOption(menu, menuCategories);
return MenuDetailResponse.createForSingleOption(menu, menuCategories);
}

public MenuCategoriesResponse getMenuCategories(Long shopId) {
Expand All @@ -67,4 +68,9 @@ public ShopResponse getShop(Long shopId) {
List<MenuCategory> menuCategories = menuCategoryRepository.findAllByShopId(shopId);
return ShopResponse.of(shop, shopOpens, shopImages, shopCategoryMaps, menuCategories);
}

public ShopMenuResponse getShopMenu(Long shopId) {
List<MenuCategory> menuCategories = menuCategoryRepository.findAllByShopId(shopId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C

menuCategoryRepository에서 findAllByShopId 메서드에 대해 조회된 데이터가 없을때에 대한 예외처리 부분이 안보이는것같은데 따로 예외처리 안해도 상관없는건가요??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findAll이라서 List형태로 반환할거라서 따로 예외처리는 안했어요~

return ShopMenuResponse.from(menuCategories);
}
}
Loading