Skip to content

Commit

Permalink
Merge pull request #106 from modu-menu/feat/update-search
Browse files Browse the repository at this point in the history
테이블 변경 없이 계층 구조를 나타낼 수 있도록 FoodType 수정, 카테고리 목록 조회 API 구현
  • Loading branch information
eelseungmin authored May 31, 2024
2 parents eb88709 + 8761e88 commit dd794ee
Show file tree
Hide file tree
Showing 21 changed files with 374 additions and 156 deletions.
24 changes: 0 additions & 24 deletions src/main/java/modu/menu/food/domain/Food.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import jakarta.persistence.*;
import lombok.*;

import java.util.ArrayList;
import java.util.List;

@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -21,25 +18,4 @@ public class Food {
@Column(length = 30)
@Enumerated(EnumType.STRING)
private FoodType type;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "food_id")
private Food parent;

@OneToMany(mappedBy = "food")
private List<Food> children = new ArrayList<>();

public void addChildren(Food food) {
food.syncParent(this);
children.add(food);
}

public void removeChildren(Food food) {
food.syncParent(null);
children.remove(food);
}

public void syncParent(Food food) {
this.parent = food;
}
}
80 changes: 48 additions & 32 deletions src/main/java/modu/menu/food/domain/FoodType.java
Original file line number Diff line number Diff line change
@@ -1,51 +1,67 @@
package modu.menu.food.domain;

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
import java.util.Arrays;
import java.util.List;

@AllArgsConstructor
public enum FoodType {

// 한식
KOREAN_FOOD("한식"),
KOREAN_SEAFOOD("해물,생선"),
CONGEE("죽"),
JOKBAL("족발, 보쌈"),
HOT_POP("찌개,전골"),
MEAT("육류,고기"),
SUNDAE("순대"),
STREET_FOOD("분식"),
NOODLE("면"),
LUNCH_BOX("도시락"),
KOREAN_CHICKEN("닭요리"),
INTESTINE("곱창,막창"),
TEPPAN_YAKI("철판요리"),
KOREAN_FOOD("한식", null),
KOREAN_SEAFOOD("해물,생선", KOREAN_FOOD),
CONGEE("죽", KOREAN_FOOD),
JOKBAL("족발, 보쌈", KOREAN_FOOD),
HOT_POT("찌개,전골", KOREAN_FOOD),
MEAT("육류,고기", KOREAN_FOOD),
SUNDAE("순대", KOREAN_FOOD),
STREET_FOOD("분식", KOREAN_FOOD),
NOODLE("면", KOREAN_FOOD),
LUNCH_BOX("도시락", KOREAN_FOOD),
KOREAN_CHICKEN("닭요리", KOREAN_FOOD),
INTESTINE("곱창,막창", KOREAN_FOOD),
TEPPAN_YAKI("철판요리", KOREAN_FOOD),

// 일식, 중식
JAPANESE_FOOD("일식"),
CHINESE_FOOD("중식"),
JAPANESE_FOOD("일식", null),
CHINESE_FOOD("중식", null),

// 양식
WESTERN_FOOD("양식"),
HAMBURGER("햄버거"),
WESTERN_SEAFOOD("해산물"),
PIZZA("피자"),
FRENCH_FOOD("프랑스음식"),
FAST_FOOD("패스트푸드"),
FAMILY_RESTAURANT("패밀리레스토랑"),
CHICKEN("치킨"),
ITALIAN_FOOD("이탈리안"),
SPANISH_FOOD("스페인"),
SALAD("샐러드"),
LATIN("멕시칸,브라질"),
WESTERN_FOOD("양식", null),
HAMBURGER("햄버거", WESTERN_FOOD),
WESTERN_SEAFOOD("해산물", WESTERN_FOOD),
PIZZA("피자", WESTERN_FOOD),
FRENCH_FOOD("프랑스음식", WESTERN_FOOD),
FAST_FOOD("패스트푸드", WESTERN_FOOD),
FAMILY_RESTAURANT("패밀리레스토랑", WESTERN_FOOD),
CHICKEN("치킨", WESTERN_FOOD),
ITALIAN_FOOD("이탈리안", WESTERN_FOOD),
SPANISH_FOOD("스페인", WESTERN_FOOD),
SALAD("샐러드", WESTERN_FOOD),
LATIN("멕시칸,브라질", WESTERN_FOOD),

// 기타
ASIAN_FOOD("아시아음식"),
BAR("술집"),
BUFFET("뷔페"),
DESSERT("디저트");
ASIAN_FOOD("아시아음식", null),
BAR("술집", null),
BUFFET("뷔페", null),
DESSERT("디저트", null);

private final String title;
@Getter
private final FoodType parent;

@JsonValue
public String getTitle() {
return title;
}

// 최상위 Enum 목록을 반환하는 메서드
public static List<FoodType> getAncestor() {
return Arrays.stream(FoodType.values())
.filter(foodType -> foodType.getParent() == null)
.toList();
}
}
19 changes: 16 additions & 3 deletions src/main/java/modu/menu/place/api/PlaceController.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import modu.menu.core.response.ApiFailResponse;
import modu.menu.core.response.ApiSuccessResponse;
import modu.menu.food.domain.FoodType;
import modu.menu.place.api.response.CategoryResponse;
import modu.menu.place.api.response.SearchPlaceResponse;
import modu.menu.place.service.PlaceService;
import modu.menu.vibe.domain.VibeType;
Expand All @@ -32,11 +33,23 @@ public class PlaceController {

private final PlaceService placeService;

@Operation(summary = "카테고리 목록 조회", description = """
음식 카테고리와 분위기의 전체 목록을 조회합니다.
""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "조회가 성공한 경우"),
@ApiResponse(responseCode = "500", description = "그 외 서버에서 처리하지 못한 에러가 발생했을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class)))
})
@GetMapping("/api/category")
public ResponseEntity<ApiSuccessResponse<CategoryResponse>> getCategory() {

return ResponseEntity.ok()
.body(new ApiSuccessResponse<>(placeService.getCategory()));
}

@Operation(summary = "음식점 후보 검색", description = """
투표에 포함시킬 음식점 후보를 사용자의 입력 값을 바탕으로 검색합니다. 조건을 만족하는 음식점이 없을 경우 null을 반환합니다.\n
다음 Query String의 경우 대소문자를 구분하지 않습니다. 괄호 안의 값들은 참고를 위한 값으로 요청에는 포함되지 않아야 합니다.\n
food: KOREAN_FOOD(한식), KOREAN_SEAFOOD(한식 > 해물,생선), CONGEE(한식 > 죽), JOKBAL(한식 > 족발, 보쌈), HOT_POP(한식 > 찌개,전골), MEAT(한식 > 육류,고기), SUNDAE(한식 > 순대), STREET_FOOD(한식 > 분식), NOODLE(한식 > 면), LUNCH_BOX(한식 > 도시락), KOREAN_CHICKEN(한식 > 닭요리), INTESTINE(한식 > 곱창,막창), TEPPAN_YAKI(한식 > 철판요리), JAPANESE_FOOD(일식), CHINESE_FOOD(중식), WESTERN_FOOD(양식), HAMBURGER(양식 > 햄버거), WESTERN_SEAFOOD(양식 > 해산물), PIZZA(양식 > 피자), FRENCH_FOOD(양식 > 프랑스음식), FAST_FOOD(양식 > 패스트푸드), FAMILY_RESTAURANT(양식 > 패밀리레스토랑), CHICKEN(양식 > 치킨), ITALIAN_FOOD(양식 > 이탈리안), SPANISH_FOOD(양식 > 스페인), SALAD(양식 > 샐러드), LATIN(양식 > 멕시칸,브라질), ASIAN_FOOD(아시아음식), BAR(술집), BUFFET(뷔페), DESSERT(디저트)\n
vibe: NOISY(시끌벅적해요), TRENDY(트렌디해요), GOOD_SERVICE(서비스가 좋아요), QUIET(조용해요), MODERN(모던해요), NICE_VIEW(뷰맛집이에요)""")
Query String의 경우 대소문자를 구분하지 않습니다.\n""")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "검색이 성공한 경우"),
@ApiResponse(responseCode = "400", description = "Query String이 형식에 맞지 않을 경우", content = @Content(schema = @Schema(implementation = ApiFailResponse.class))),
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/modu/menu/place/api/response/CategoryResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package modu.menu.place.api.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import modu.menu.place.service.dto.FoodTypeServiceResponse;
import modu.menu.place.service.dto.VibeTypeServiceResponse;

import java.util.List;

@Schema(description = "카테고리 조회 결과 DTO")
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class CategoryResponse {

@Schema(description = "음식점 카테고리 목록")
private List<FoodTypeServiceResponse> foods;

@Schema(description = "분위기 목록")
private List<VibeTypeServiceResponse> vibes;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import modu.menu.food.domain.FoodType;
import modu.menu.place.service.dto.SearchResultServiceResponse;
import modu.menu.vibe.domain.VibeType;

import java.util.List;

Expand All @@ -18,22 +16,6 @@
@NoArgsConstructor
public class SearchPlaceResponse {

@Schema(description = "검색에 사용한 음식 조건 목록", examples = {
"멕시칸,브라질",
"양식",
"육류,고기요리",
"오뎅바"})
private List<FoodType> foods;

@Schema(description = "검색에 사용한 분위기 조건 목록", examples = {
"시끌벅적해요",
"트렌디해요",
"조용해요",
"모던해요",
"뷰맛집이에요",
"서비스가 좋아요"})
private List<VibeType> vibes;

@Schema(description = "검색 결과 목록")
private List<SearchResultServiceResponse> results;

Expand Down
29 changes: 15 additions & 14 deletions src/main/java/modu/menu/place/service/PlaceService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@

import lombok.RequiredArgsConstructor;
import modu.menu.core.util.DistanceCalculator;
import modu.menu.food.domain.Food;
import modu.menu.food.domain.FoodType;
import modu.menu.place.api.response.CategoryResponse;
import modu.menu.place.api.response.SearchPlaceResponse;
import modu.menu.place.domain.Place;
import modu.menu.place.reposiotry.PlaceQueryRepository;
import modu.menu.place.service.dto.FoodTypeServiceResponse;
import modu.menu.place.service.dto.SearchResultServiceResponse;
import modu.menu.placefood.domain.PlaceFood;
import modu.menu.placevibe.domain.PlaceVibe;
import modu.menu.vibe.domain.Vibe;
import modu.menu.place.service.dto.VibeTypeServiceResponse;
import modu.menu.vibe.domain.VibeType;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Transactional(readOnly = true)
@RequiredArgsConstructor
Expand All @@ -26,6 +24,14 @@ public class PlaceService {

private final PlaceQueryRepository placeQueryRepository;

// 카테고리 목록 조회
public CategoryResponse getCategory() {
return CategoryResponse.builder()
.foods(FoodTypeServiceResponse.getFoodTypeHierarchy())
.vibes(VibeTypeServiceResponse.toList())
.build();
}

// 음식점 후보 검색
public SearchPlaceResponse searchPlace(
Double latitude,
Expand All @@ -42,8 +48,6 @@ public SearchPlaceResponse searchPlace(
}

return SearchPlaceResponse.builder()
.foods(foods)
.vibes(vibes)
.results(places.getContent().stream()
.map(place -> {
double distance = DistanceCalculator.calculate(
Expand All @@ -56,14 +60,11 @@ public SearchPlaceResponse searchPlace(
return SearchResultServiceResponse.builder()
.id(place.getId())
.name(place.getName())
.food(place.getPlaceFoods().stream()
.map(PlaceFood::getFood)
.map(Food::getType)
.map(FoodType::getDetail)
.collect(Collectors.joining()))
.foods(place.getPlaceFoods().stream()
.map(placeFood -> placeFood.getFood().getType())
.toList())
.vibes(place.getPlaceVibes().stream()
.map(PlaceVibe::getVibe)
.map(Vibe::getType)
.map(placeVibe -> placeVibe.getVibe().getType())
.toList())
.address(place.getAddress())
.distance(distance >= 1000.0 ? String.format("%.1f", distance / 1000.0) + "km" : Math.round(distance) + "m")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package modu.menu.place.service.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import modu.menu.food.domain.FoodType;

import java.util.Arrays;
import java.util.List;

@Schema(description = "음식점 카테고리 DTO")
@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class FoodTypeServiceResponse {

@Schema(description = "음식점 카테고리 key")
private String key;

@Schema(description = "음식점 카테고리 value")
private String value;

@Schema(description = "하위 카테고리 목록")
private List<FoodTypeServiceResponse> children;

// 최상위 카테고리부터 시작하여 FoodType의 전체 계층 구조를 반환하는 메서드
public static List<FoodTypeServiceResponse> getFoodTypeHierarchy() {
return FoodType.getAncestor().stream()
.map(FoodTypeServiceResponse::toFoodTypeServiceResponse)
.toList();
}

private static FoodTypeServiceResponse toFoodTypeServiceResponse(FoodType foodType) {
return FoodTypeServiceResponse.builder()
.key(foodType.name())
.value(foodType.getTitle())
.children(Arrays.stream(FoodType.values())
.filter(f -> f.getParent() == foodType)
.map(FoodTypeServiceResponse::toFoodTypeServiceResponse)
.toList())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import modu.menu.food.domain.FoodType;
import modu.menu.vibe.domain.VibeType;

import java.util.List;
Expand All @@ -22,16 +23,15 @@ public class SearchResultServiceResponse {
@Schema(description = "음식점 이름", example = "타코벨")
private String name;

@Schema(description = "음식 종류", example = "멕시칸, 브라질")
private String food;
@Schema(description = "검색에 사용한 음식 조건 목록", examples = {
"면",
"양식"})
private List<FoodType> foods;

@Schema(description = "음식점 분위기 목록", examples = {
@Schema(description = "검색에 사용한 분위기 조건 목록", examples = {
"시끌벅적해요",
"트렌디해요",
"조용해요",
"모던해요",
"뷰맛집이에요",
"서비스가 좋아요"})
"조용해요"})
private List<VibeType> vibes;

@Schema(description = "음식점 도로명 주소", example = "서울 서초구 서초대로74길 11 삼성전자 강남사옥 지하1층 B108호")
Expand Down
Loading

0 comments on commit dd794ee

Please sign in to comment.