diff --git a/src/main/java/kuit3/backend/common/argument_resolver/JwtAuthHandlerArgumentResolver.java b/src/main/java/kuit3/backend/common/argument_resolver/JwtAuthHandlerArgumentResolver.java index 474afa1..7a37807 100644 --- a/src/main/java/kuit3/backend/common/argument_resolver/JwtAuthHandlerArgumentResolver.java +++ b/src/main/java/kuit3/backend/common/argument_resolver/JwtAuthHandlerArgumentResolver.java @@ -1,8 +1,14 @@ package kuit3.backend.common.argument_resolver; import jakarta.servlet.http.HttpServletRequest; +import kuit3.backend.common.exception.jwt.unauthorized.JwtInvalidTokenException; +import kuit3.backend.jwt.JwtProvider; +import kuit3.backend.service.AuthService; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; @@ -10,21 +16,40 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.INVALID_TOKEN; + @Slf4j @Component +@RequiredArgsConstructor public class JwtAuthHandlerArgumentResolver implements HandlerMethodArgumentResolver { + + private final JwtProvider jwtProvider; + private final AuthService authService; + @Override public boolean supportsParameter(MethodParameter parameter) { + log.info(parameter.getParameterName() + parameter.getParameterType()); boolean hasAnnotation = parameter.hasParameterAnnotation(PreAuthorize.class); - boolean hasType = long.class.isAssignableFrom(parameter.getParameterType()); + boolean hasType = Long.class.isAssignableFrom(parameter.getParameterType()); log.info("hasAnnotation={}, hasType={}, hasAnnotation && hasType={}", hasAnnotation, hasType, hasAnnotation&&hasType); - return hasAnnotation && hasType; + return true; } @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + public Object resolveArgument(@NonNull MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) { HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); - log.info("userId={}", request.getAttribute("userId")); - return request.getAttribute("userId"); + String accessToken = (String)request.getAttribute("jwtToken"); + + String email = jwtProvider.getPrincipal(accessToken); + validatePayload(email); + + return authService.getUserIdByEmail(email); + } + + private void validatePayload(String email) { + if (email == null) { + throw new JwtInvalidTokenException(INVALID_TOKEN); + } } + } \ No newline at end of file diff --git a/src/main/java/kuit3/backend/common/exception/RestaurantException.java b/src/main/java/kuit3/backend/common/exception/RestaurantException.java new file mode 100644 index 0000000..075358c --- /dev/null +++ b/src/main/java/kuit3/backend/common/exception/RestaurantException.java @@ -0,0 +1,21 @@ +package kuit3.backend.common.exception; + +import kuit3.backend.common.response.status.ResponseStatus; +import lombok.Getter; + +@Getter +public class RestaurantException extends RuntimeException { + + private final ResponseStatus exceptionStatus; + + public RestaurantException(ResponseStatus exceptionStatus) { + super(exceptionStatus.getMessage()); + this.exceptionStatus = exceptionStatus; + } + + public RestaurantException(ResponseStatus exceptionStatus, String message) { + super(message); + this.exceptionStatus = exceptionStatus; + } + +} diff --git a/src/main/java/kuit3/backend/common/exception_handler/RestaurantExceptionControllerAdvice.java b/src/main/java/kuit3/backend/common/exception_handler/RestaurantExceptionControllerAdvice.java new file mode 100644 index 0000000..4872d87 --- /dev/null +++ b/src/main/java/kuit3/backend/common/exception_handler/RestaurantExceptionControllerAdvice.java @@ -0,0 +1,25 @@ +package kuit3.backend.common.exception_handler; + +import jakarta.annotation.Priority; +import kuit3.backend.common.exception.RestaurantException; +import kuit3.backend.common.response.BaseErrorResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.INVALID_RESTAURANT_VALUE; + +@Slf4j +@Priority(0) +@RestControllerAdvice +public class RestaurantExceptionControllerAdvice { + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(RestaurantException.class) + public BaseErrorResponse handle_RestaurantException(RestaurantException e) { + log.error("[handle_RestaurantException]", e); + return new BaseErrorResponse(INVALID_RESTAURANT_VALUE, e.getMessage()); + } + +} diff --git a/src/main/java/kuit3/backend/common/interceptor/JwtAuthInterceptor.java b/src/main/java/kuit3/backend/common/interceptor/JwtAuthInterceptor.java index 3196eaf..34aa703 100644 --- a/src/main/java/kuit3/backend/common/interceptor/JwtAuthInterceptor.java +++ b/src/main/java/kuit3/backend/common/interceptor/JwtAuthInterceptor.java @@ -8,6 +8,7 @@ import kuit3.backend.common.exception.jwt.bad_request.JwtUnsupportedTokenException; import kuit3.backend.jwt.JwtProvider; import kuit3.backend.service.AuthService; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; @@ -27,16 +28,11 @@ public class JwtAuthInterceptor implements HandlerInterceptor { private final AuthService authService; @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - + public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { String accessToken = resolveAccessToken(request); validateAccessToken(accessToken); + request.setAttribute("jwtToken", accessToken); - String email = jwtProvider.getPrincipal(accessToken); - validatePayload(email); - - long userId = authService.getUserIdByEmail(email); - request.setAttribute("userId", userId); return true; } @@ -61,10 +57,4 @@ private void validateAccessToken(String accessToken) { } } - private void validatePayload(String email) { - if (email == null) { - throw new JwtInvalidTokenException(INVALID_TOKEN); - } - } - } \ No newline at end of file diff --git a/src/main/java/kuit3/backend/common/response/status/BaseExceptionResponseStatus.java b/src/main/java/kuit3/backend/common/response/status/BaseExceptionResponseStatus.java index 3354ad9..4611daa 100644 --- a/src/main/java/kuit3/backend/common/response/status/BaseExceptionResponseStatus.java +++ b/src/main/java/kuit3/backend/common/response/status/BaseExceptionResponseStatus.java @@ -45,7 +45,10 @@ public enum BaseExceptionResponseStatus implements ResponseStatus { USER_NOT_FOUND(4003, HttpStatus.BAD_REQUEST.value(), "존재하지 않는 회원입니다."), PASSWORD_NO_MATCH(4004, HttpStatus.BAD_REQUEST.value(), "비밀번호가 일치하지 않습니다."), INVALID_USER_STATUS(4005, HttpStatus.BAD_REQUEST.value(), "잘못된 회원 status 값입니다."), - EMAIL_NOT_FOUND(4006, HttpStatus.BAD_REQUEST.value(), "존재하지 않는 이메일입니다."); + EMAIL_NOT_FOUND(4006, HttpStatus.BAD_REQUEST.value(), "존재하지 않는 이메일입니다."), + + // 6000: Restaurant 오류 + INVALID_RESTAURANT_VALUE(6000, HttpStatus.BAD_REQUEST.value(), "식당의 정보가 유효하지 않습니다."); private final int code; private final int status; diff --git a/src/main/java/kuit3/backend/config/WebConfig.java b/src/main/java/kuit3/backend/config/WebConfig.java index 2f5ebf4..be676d9 100644 --- a/src/main/java/kuit3/backend/config/WebConfig.java +++ b/src/main/java/kuit3/backend/config/WebConfig.java @@ -21,7 +21,10 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtAuthenticationInterceptor) .order(1) - .addPathPatterns("/auth/test"); + .addPathPatterns("/auth/test") + .addPathPatterns(("/users/**")) + .addPathPatterns("/restaurants") + .excludePathPatterns("/users"); } @Override diff --git a/src/main/java/kuit3/backend/controller/RestaurantController.java b/src/main/java/kuit3/backend/controller/RestaurantController.java new file mode 100644 index 0000000..11283c7 --- /dev/null +++ b/src/main/java/kuit3/backend/controller/RestaurantController.java @@ -0,0 +1,44 @@ +package kuit3.backend.controller; + +import kuit3.backend.common.argument_resolver.PreAuthorize; +import kuit3.backend.common.exception.RestaurantException; +import kuit3.backend.common.response.BaseResponse; +import kuit3.backend.dto.restaurant.GetRestaurantResponse; +import kuit3.backend.dto.restaurant.PostRestaurantRequest; +import kuit3.backend.service.RestaurantService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.BindingResult; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.INVALID_RESTAURANT_VALUE; +import static kuit3.backend.util.BindingResultUtils.getErrorMessages; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/restaurants") +public class RestaurantController { + + private final RestaurantService restaurantService; + + @PostMapping("") + public BaseResponse makeNewRestaurant(@Validated @RequestBody PostRestaurantRequest postRestaurantRequest, BindingResult bindingResult) { + if (bindingResult.hasErrors()) { + throw new RestaurantException(INVALID_RESTAURANT_VALUE, getErrorMessages(bindingResult)); + } + return new BaseResponse<>(restaurantService.makeNewRestaurant(postRestaurantRequest)); + } + + @GetMapping("") + public BaseResponse> getRestaurant( + @RequestParam(required = false) Double star, + @RequestParam(required = false) Long lastId) { + return new BaseResponse<>(restaurantService.getRestaurants(star, lastId)); + } + +} diff --git a/src/main/java/kuit3/backend/controller/UserController.java b/src/main/java/kuit3/backend/controller/UserController.java index 70b712b..f49d28f 100644 --- a/src/main/java/kuit3/backend/controller/UserController.java +++ b/src/main/java/kuit3/backend/controller/UserController.java @@ -1,6 +1,9 @@ package kuit3.backend.controller; +import kuit3.backend.common.argument_resolver.PreAuthorize; import kuit3.backend.common.exception.UserException; +import kuit3.backend.common.exception.jwt.unauthorized.JwtInvalidTokenException; +import kuit3.backend.common.exception.jwt.unauthorized.JwtUnauthorizedTokenException; import kuit3.backend.common.response.BaseResponse; import kuit3.backend.dto.user.*; import kuit3.backend.service.UserService; @@ -12,8 +15,7 @@ import java.util.List; -import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.INVALID_USER_STATUS; -import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.INVALID_USER_VALUE; +import static kuit3.backend.common.response.status.BaseExceptionResponseStatus.*; import static kuit3.backend.util.BindingResultUtils.getErrorMessages; @Slf4j @@ -38,8 +40,8 @@ public BaseResponse signUp(@Validated @RequestBody PostUserReq /** * 회원 휴면 */ - @PatchMapping("/{userId}/dormant") - public BaseResponse modifyUserStatus_dormant(@PathVariable long userId) { + @PatchMapping("/dormant") + public BaseResponse modifyUserStatus_dormant(@PreAuthorize Long userId) { userService.modifyUserStatus_dormant(userId); return new BaseResponse<>(null); } @@ -47,8 +49,8 @@ public BaseResponse modifyUserStatus_dormant(@PathVariable long userId) /** * 회원 탈퇴 */ - @PatchMapping("/{userId}/deleted") - public BaseResponse modifyUserStatus_deleted(@PathVariable long userId) { + @PatchMapping("/deleted") + public BaseResponse modifyUserStatus_deleted(@PreAuthorize long userId) { userService.modifyUserStatus_deleted(userId); return new BaseResponse<>(null); } @@ -56,8 +58,8 @@ public BaseResponse modifyUserStatus_deleted(@PathVariable long userId) /** * 닉네임 변경 */ - @PatchMapping("/{userId}/nickname") - public BaseResponse modifyNickname(@PathVariable long userId, + @PatchMapping("/nickname") + public BaseResponse modifyNickname(@PreAuthorize long userId, @Validated @RequestBody PatchNicknameRequest patchNicknameRequest, BindingResult bindingResult) { if (bindingResult.hasErrors()) { throw new UserException(INVALID_USER_VALUE, getErrorMessages(bindingResult)); diff --git a/src/main/java/kuit3/backend/dao/RestaurantDao.java b/src/main/java/kuit3/backend/dao/RestaurantDao.java new file mode 100644 index 0000000..4aecfa7 --- /dev/null +++ b/src/main/java/kuit3/backend/dao/RestaurantDao.java @@ -0,0 +1,55 @@ +package kuit3.backend.dao; + +import kuit3.backend.dto.restaurant.GetRestaurantResponse; +import kuit3.backend.dto.restaurant.PostRestaurantRequest; +import kuit3.backend.dto.user.GetUserResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; +import org.springframework.stereotype.Repository; + +import javax.sql.DataSource; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@Repository +public class RestaurantDao { + + private final NamedParameterJdbcTemplate jdbcTemplate; + + public RestaurantDao(DataSource dataSource) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + } + + public long createRestaurant(PostRestaurantRequest postRestaurantRequest) { + String sql = "insert into restaurant(address, category, latitude, longitude, min_price, name, star, picture_url) " + + "values(:address, :category, :latitude, :longitude, :min_price, :name, :star, :picture_url)"; + + SqlParameterSource param = new BeanPropertySqlParameterSource(postRestaurantRequest); + KeyHolder keyHolder = new GeneratedKeyHolder(); + jdbcTemplate.update(sql, param, keyHolder); + + return Objects.requireNonNull(keyHolder.getKey()).longValue(); + } + + public List getRestaurants(double star, Long lastId) { + String sql = "SELECT id, name FROM restaurant WHERE star >= :star AND id > :lastId " + + "ORDER BY id LIMIT 10"; + + Map param = Map.of( + "star", star, + "lastId", lastId); + + return jdbcTemplate.query(sql, param, + (rs, rowNum) -> new GetRestaurantResponse( + rs.getLong("id"), + rs.getString("name"))); + } + +} diff --git a/src/main/java/kuit3/backend/dao/UserDao.java b/src/main/java/kuit3/backend/dao/UserDao.java index 4447633..39175fc 100644 --- a/src/main/java/kuit3/backend/dao/UserDao.java +++ b/src/main/java/kuit3/backend/dao/UserDao.java @@ -68,38 +68,38 @@ public List getUsers(String nickname, String email, String stat } public long getUserIdByEmail(String email) { - String sql = "select user_id from user where email=:email and status='active'"; + String sql = "select id from user where email=:email and status='active'"; Map param = Map.of("email", email); return jdbcTemplate.queryForObject(sql, param, long.class); } public String getPasswordByUserId(long userId) { - String sql = "select password from user where user_id=:user_id and status='active'"; - Map param = Map.of("user_id", userId); + String sql = "select password from user where id=:id and status='active'"; + Map param = Map.of("id", userId); return jdbcTemplate.queryForObject(sql, param, String.class); } public int modifyUserStatus_dormant(long userId) { - String sql = "update user set status=:status where user_id=:user_id"; + String sql = "update user set status=:status where id=:id"; Map param = Map.of( "status", "dormant", - "user_id", userId); + "id", userId); return jdbcTemplate.update(sql, param); } public int modifyUserStatus_deleted(long userId) { - String sql = "update user set status=:status where user_id=:user_id"; + String sql = "update user set status=:status where id=:id"; Map param = Map.of( "status", "deleted", - "user_id", userId); + "id", userId); return jdbcTemplate.update(sql, param); } public int modifyNickname(long userId, String nickname) { - String sql = "update user set nickname=:nickname where user_id=:user_id"; + String sql = "update user set nickname=:nickname where id=:id"; Map param = Map.of( "nickname", nickname, - "user_id", userId); + "id", userId); return jdbcTemplate.update(sql, param); } } \ No newline at end of file diff --git a/src/main/java/kuit3/backend/dto/restaurant/GetRestaurantResponse.java b/src/main/java/kuit3/backend/dto/restaurant/GetRestaurantResponse.java new file mode 100644 index 0000000..fb14f0f --- /dev/null +++ b/src/main/java/kuit3/backend/dto/restaurant/GetRestaurantResponse.java @@ -0,0 +1,17 @@ +package kuit3.backend.dto.restaurant; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class GetRestaurantResponse { + + private Long id; + private String name; + +} diff --git a/src/main/java/kuit3/backend/dto/restaurant/PostRestaurantRequest.java b/src/main/java/kuit3/backend/dto/restaurant/PostRestaurantRequest.java new file mode 100644 index 0000000..73f7bf9 --- /dev/null +++ b/src/main/java/kuit3/backend/dto/restaurant/PostRestaurantRequest.java @@ -0,0 +1,44 @@ +package kuit3.backend.dto.restaurant; + +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.PositiveOrZero; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class PostRestaurantRequest { + + @NotBlank(message = "address: {NotBlank}") + private String address; + + @NotBlank(message = "category: {NotBlank}") + private String category; + + @Max(value = 90, message = "latitude: 가질 수 있는 최대 값은 90입니다.") + @Min(value = 0, message = "latitude: 가질 수 있는 최소 값은 0입니다.") + private double latitude; + + @Max(value = 180, message = "longitude: 가질 수 있는 최대 값은 180입니다.") + @Min(value = -180, message = "longitude: 가질 수 있는 최소 값은 -180입니다.") + private double longitude; + + @PositiveOrZero(message = "min_price: 최소 금액은 0 이상의 정수입니다.") + private double min_price; + + @NotBlank(message = "name: {NotBlank}") + private String name; + + @PositiveOrZero(message = "star: 별점은 0~5의 값입니다.") + @Max(value = 5, message = "star: 별점은 0~5의 값입니다.") + private double star; + + @Nullable + private String picture_url; + +} diff --git a/src/main/java/kuit3/backend/service/RestaurantService.java b/src/main/java/kuit3/backend/service/RestaurantService.java new file mode 100644 index 0000000..fc1d76a --- /dev/null +++ b/src/main/java/kuit3/backend/service/RestaurantService.java @@ -0,0 +1,27 @@ +package kuit3.backend.service; + +import kuit3.backend.dao.RestaurantDao; +import kuit3.backend.dto.restaurant.GetRestaurantResponse; +import kuit3.backend.dto.restaurant.PostRestaurantRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +@RequiredArgsConstructor +public class RestaurantService { + + private final RestaurantDao restaurantDao; + + public long makeNewRestaurant(PostRestaurantRequest postRestaurantRequest) { + return restaurantDao.createRestaurant(postRestaurantRequest); + } + + public List getRestaurants(double star, Long lastId) { + return restaurantDao.getRestaurants(star, lastId); + } + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2bf75e2..6989a56 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,5 +1,6 @@ spring: profiles: + active: local group: "local": "localDB, devPort, secret, web-mvc" "dev": "devDB, devPort, secret, web-mvc"