Skip to content

Commit

Permalink
Merge pull request #107 from modu-menu/develop
Browse files Browse the repository at this point in the history
main에 develop merge
  • Loading branch information
eelseungmin authored May 31, 2024
2 parents d649037 + dd794ee commit e73d1bd
Show file tree
Hide file tree
Showing 40 changed files with 808 additions and 399 deletions.
17 changes: 17 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ repositories {
mavenCentral()
}

def queryDslVersion = '5.0.0'

dependencies {
compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.0.0'
implementation 'org.springframework.boot:spring-boot-starter'
Expand All @@ -37,6 +39,21 @@ dependencies {
testImplementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-api', version: '2.3.0'
implementation group: 'com.auth0', name: 'java-jwt', version: '4.4.0'
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"
implementation group: 'com.github.maricn', name: 'logback-slack-appender', version: '1.6.0'
// Querydsl 추가
// 필수
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta"
implementation "com.querydsl:querydsl-core:${queryDslVersion}"
// QueryDsl 쿼리 타입 생성 (QClass 생성 시 @Entity 탐색)
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta"
// java.lang.NoClassDefFoundError:javax/persistence/Entity 에러 방지
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}

// Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
delete file('src/main/generated')
}

test {
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/modu/menu/SlackErrorLogTestController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package modu.menu;

import io.swagger.v3.oas.annotations.Hidden;
import modu.menu.core.exception.Exception500;
import modu.menu.core.response.ErrorMessage;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Hidden
@RestController
public class SlackErrorLogTestController {

@GetMapping("/api/slack")
public String getSlackErrorLog() {
throw new Exception500(ErrorMessage.ERROR_LOG_TEST_MESSAGE);
}
}
19 changes: 19 additions & 0 deletions src/main/java/modu/menu/core/config/QuerydslConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package modu.menu.core.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@RequiredArgsConstructor
public class QuerydslConfig {

private final EntityManager em;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
3 changes: 2 additions & 1 deletion src/main/java/modu/menu/core/config/WebMvcConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public void addInterceptors(InterceptorRegistry registry) {
"/api/health-check",
"/api/user",
"/api/user/login",
"/api/place{?*}"
"/api/place{?*}",
"/api/slack"
);
}
}
21 changes: 11 additions & 10 deletions src/main/java/modu/menu/core/exception/handler/ExceptionAdvice.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package modu.menu.core.exception.handler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -19,7 +20,7 @@ public class ExceptionAdvice {

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiFailResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.warn("400: " + e.getMessage());
log.warn("Exception: 400, " + e.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ApiFailResponse(
Expand All @@ -31,7 +32,7 @@ public ResponseEntity<ApiFailResponse> handleMethodArgumentNotValidException(Met

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ApiFailResponse> handleConstraintViolationException(ConstraintViolationException e) {
log.warn("400: " + e.getMessage());
log.warn("Exception: 400, " + e.getMessage());
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ApiFailResponse(
Expand All @@ -48,47 +49,47 @@ public ResponseEntity<ApiFailResponse> handleConstraintViolationException(Constr

@ExceptionHandler(Exception400.class)
public ResponseEntity<ApiFailResponse> badRequest(Exception400 e) {
log.warn("400: " + e.getMessage());
log.warn("Exception: 400, " + e.getMessage());
return ResponseEntity
.status(e.status())
.body(e.body());
}

@ExceptionHandler(Exception401.class)
public ResponseEntity<ApiFailResponse> unauthorized(Exception401 e) {
log.warn("401: " + e.getMessage());
log.warn("Exception: 401, " + e.getMessage());
return ResponseEntity
.status(e.status())
.body(e.body());
}

@ExceptionHandler(Exception403.class)
public ResponseEntity<ApiFailResponse> forbidden(Exception403 e) {
log.warn("403: " + e.getMessage());
log.warn("Exception: 403, " + e.getMessage());
return ResponseEntity
.status(e.status())
.body(e.body());
}

@ExceptionHandler(Exception404.class)
public ResponseEntity<ApiFailResponse> notFound(Exception404 e) {
log.warn("404: " + e.getMessage());
log.warn("Exception: 404, " + e.getMessage());
return ResponseEntity
.status(e.status())
.body(e.body());
}

@ExceptionHandler(Exception500.class)
public ResponseEntity<ApiFailResponse> serverError(Exception500 e) {
log.error("500: " + e.getMessage(), e);
public ResponseEntity<ApiFailResponse> serverError(Exception500 e, HttpServletRequest request) {
log.error("Exception: 500, " + request.getMethod() + ": " + request.getRequestURI(), e);
return ResponseEntity
.status(e.status())
.body(e.body());
}

@ExceptionHandler(Exception.class)
public ResponseEntity<ApiFailResponse> unknownServerError(Exception e) {
log.error("500: " + e.getMessage(), e);
public ResponseEntity<ApiFailResponse> unknownServerError(Exception e, HttpServletRequest request) {
log.error("Exception: 500, " + request.getMethod() + ": " + request.getRequestURI(), e);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ApiFailResponse(
Expand Down
1 change: 1 addition & 0 deletions src/main/java/modu/menu/core/response/ErrorMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
@Getter
public enum ErrorMessage {

ERROR_LOG_TEST_MESSAGE("Slack 에러 로그 테스트"),
ADD_REQUEST_BODY_ERROR("RestTemplate RequestBody 생성에 실패했습니다."),
MAPPER_READ_TREE_ERROR("ObjectMapper readTree 실행에 실패했습니다."),
CANT_CONVERT_STRING_TO_ENUM("주어진 문자열을 적절한 Enum으로 변환할 수 없습니다."),
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/modu/menu/core/util/DistanceCalculator.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
// ref: https://www.baeldung.com/java-find-distance-between-points#calculate-the-distance-using-vincentys-formula
// 정확도가 가장 높은 Vincenty의 공식을 사용해서 두 지점 간 거리를 구하는 클래스
public class DistanceCalculator {

private DistanceCalculator() {
}

private static final double SEMI_MAJOR_AXIS_MT = 6378137;
private static final double SEMI_MINOR_AXIS_MT = 6356752.314245;
private static final double FLATTENING = 1 / 298.257223563;
private static final double ERROR_TOLERANCE = 1e-12;

public static double calculateDistance(double latitude1, double longitude1, double latitude2, double longitude2) {
public static double calculate(double latitude1, double longitude1, double latitude2, double longitude2) {
double U1 = Math.atan((1 - FLATTENING) * Math.tan(Math.toRadians(latitude1)));
double U2 = Math.atan((1 - FLATTENING) * Math.tan(Math.toRadians(latitude2)));

Expand Down
66 changes: 50 additions & 16 deletions src/main/java/modu/menu/food/domain/FoodType.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,66 @@

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

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

@AllArgsConstructor
public enum FoodType {

MEAT("한식", "육류,고기요리"),
SEAFOOD("한식", "해물,생선요리"),
WESTERN_FOOD("양식", "양식"),
LATIN("양식", "멕시칸,브라질"),
INDOOR_BAR("술집", "실내 포장마차"),
FISHCAKE_BAR("술집", "오뎅바"),
WINE_BAR("술집", "와인바"),
COCKTAIL_BAR("술집", "칵테일바"),
IZAKAYA("술집", "일본식주점"),
HOF("술집", "호프,요리주점"),
FUSION("퓨전요리", "퓨전요리"),
CHICKEN("치킨", "치킨");
// 한식
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("일식", null),
CHINESE_FOOD("중식", null),

// 양식
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("아시아음식", null),
BAR("술집", null),
BUFFET("뷔페", null),
DESSERT("디저트", null);

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

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

@JsonValue
public String getDetail() {
return detail;
// 최상위 Enum 목록을 반환하는 메서드
public static List<FoodType> getAncestor() {
return Arrays.stream(FoodType.values())
.filter(foodType -> foodType.getParent() == null)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ public interface ParticipantRepository extends JpaRepository<Participant, Long>
where p.user.id = :userId
""")
Optional<Participant> findByUserId(@Param("userId") Long userId);

@Query("""
select p
from Participant p
where p.user.id = :userId and p.vote.id = :voteId
""")
Optional<Participant> findByUserIdAndVoteId(@Param("userId") Long userId, @Param("voteId") Long voteId);
}
22 changes: 18 additions & 4 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,10 +33,23 @@ public class PlaceController {

private final PlaceService placeService;

@Operation(summary = "음식점 후보 검색", description = "투표에 포함시킬 음식점 후보를 사용자의 입력 값을 바탕으로 검색합니다. 조건을 만족하는 음식점이 없을 경우 null을 반환합니다.\n\n" +
"다음 Query String의 경우 대소문자를 구분하지 않습니다.\n\n" +
"food: MEAT(육류,고기요리), SEAFOOD(해물,생선요리), WESTERN_FOOD(양식), LATIN(멕시칸,브라질), INDOOR_BAR(실내 포장마차), FISHCAKE_BAR(오뎅바), WINE_BAR(와인바), COCKTAIL_BAR(칵테일바), IZAKAYA(일본식주점), HOF(호프,요리주점), FUSION(퓨전요리), CHICKEN(치킨)\n\n" +
"vibe: NOISY(시끌벅적해요), TRENDY(트렌디해요), GOOD_SERVICE(서비스가 좋아요), QUIET(조용해요), MODERN(모던해요), NICE_VIEW(뷰맛집이에요)")
@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""")
@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
Loading

0 comments on commit e73d1bd

Please sign in to comment.