Merge pull request #27 from beaniejoy/feature/26
애플리케이션 응답에 대한 전체 규격화 (ApplicationResponse 적용)
This commit is contained in:
@@ -1,11 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.common.security.model
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import org.springframework.security.core.GrantedAuthority
|
||||
|
||||
data class AuthenticationResult(
|
||||
val email: String,
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
val authorities: Collection<GrantedAuthority> = listOf(),
|
||||
val msg: String
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.controller
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import io.beaniejoy.dongnecafe.security.JwtTokenUtils
|
||||
import io.beaniejoy.dongnecafe.domain.member.model.request.SignInRequest
|
||||
import io.beaniejoy.dongnecafe.model.TokenResponse
|
||||
@@ -16,7 +17,7 @@ class AuthController(
|
||||
private val jwtTokenUtils: JwtTokenUtils
|
||||
) {
|
||||
@PostMapping("/authenticate")
|
||||
fun signIn(@RequestBody signInRequest: SignInRequest): TokenResponse {
|
||||
fun signIn(@RequestBody signInRequest: SignInRequest): ApplicationResponse<TokenResponse> {
|
||||
val authentication = authService.signIn(
|
||||
email = signInRequest.email,
|
||||
password = signInRequest.password
|
||||
@@ -24,6 +25,8 @@ class AuthController(
|
||||
|
||||
val accessToken = jwtTokenUtils.createToken(authentication)
|
||||
|
||||
return TokenResponse(accessToken)
|
||||
return ApplicationResponse
|
||||
.success("success authenticate")
|
||||
.data(TokenResponse(accessToken))
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.controller
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import io.beaniejoy.dongnecafe.domain.member.model.request.MemberRegisterRequest
|
||||
import io.beaniejoy.dongnecafe.service.MemberService
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -13,7 +14,11 @@ class MemberController(
|
||||
private val memberService: MemberService
|
||||
) {
|
||||
@PostMapping("/sign-up")
|
||||
fun signUp(@RequestBody resource: MemberRegisterRequest): Long {
|
||||
return memberService.registerMember(resource)
|
||||
fun signUp(@RequestBody resource: MemberRegisterRequest): ApplicationResponse<Long> {
|
||||
val registerMemberId = memberService.registerMember(resource)
|
||||
|
||||
return ApplicationResponse
|
||||
.success("success sign up")
|
||||
.data(registerMemberId)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error
|
||||
|
||||
class MemberExistedException(email: String): RuntimeException("Member[$email] is already existed")
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error
|
||||
|
||||
class MemberDeactivatedException(email: String): RuntimeException("Member[$email] is deactivated")
|
||||
@@ -1,27 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.handler
|
||||
|
||||
import io.beaniejoy.dongnecafe.error.ErrorResponse
|
||||
import mu.KLogging
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.core.AuthenticationException
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
||||
// TODO 통합된 에러 핸들링 필요(ErrorResponse 규격화)
|
||||
@RestControllerAdvice
|
||||
class CommonControllerAdvice {
|
||||
|
||||
companion object : KLogging()
|
||||
|
||||
@ExceptionHandler(AuthenticationException::class)
|
||||
fun handleAuthenticationException(e: AuthenticationException): ResponseEntity<ErrorResponse> {
|
||||
logger.error { "AuthenticationException: ${e.message}" }
|
||||
return ResponseEntity.ok().body(
|
||||
ErrorResponse(
|
||||
code = HttpStatus.BAD_REQUEST.value(),
|
||||
message = "계정 혹은 비밀번호가 일치하지 않습니다."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package io.beaniejoy.dongnecafe.common.security
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.security.SecurityUser
|
||||
import mu.KLogging
|
||||
import org.springframework.security.authentication.AuthenticationProvider
|
||||
import org.springframework.security.authentication.BadCredentialsException
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
|
||||
import org.springframework.security.core.Authentication
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
@@ -29,7 +30,7 @@ class ApiAuthenticationProvider(
|
||||
|
||||
val user = userDetailsService.loadUserByUsername(email) as SecurityUser
|
||||
if (!passwordEncoder.matches(password, user.password)) {
|
||||
throw BadCredentialsException("Input password does not match stored password")
|
||||
throw BusinessException(ErrorCode.AUTH_PASSWORD_NOT_VALID)
|
||||
}
|
||||
|
||||
logger.info { "User password ${user.password}" }
|
||||
@@ -1,13 +1,13 @@
|
||||
package io.beaniejoy.dongnecafe.common.security
|
||||
package io.beaniejoy.dongnecafe.security
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.member.entity.Member
|
||||
import io.beaniejoy.dongnecafe.domain.member.repository.MemberRepository
|
||||
import io.beaniejoy.dongnecafe.error.MemberDeactivatedException
|
||||
import io.beaniejoy.dongnecafe.security.SecurityUser
|
||||
import mu.KLogging
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority
|
||||
import org.springframework.security.core.userdetails.UserDetailsService
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
|
||||
@@ -22,12 +22,12 @@ class UserDetailsServiceImpl(
|
||||
return memberRepository.findByEmail(email)?.let {
|
||||
logger.info { "[LOAD MEMBER] email: ${it.email}, role: ${it.roleType}, activated: ${it.activated}" }
|
||||
createSecurityUser(it)
|
||||
} ?: throw UsernameNotFoundException("${email} is not found")
|
||||
} ?: throw BusinessException(ErrorCode.AUTH_MEMBER_NOT_FOUND)
|
||||
}
|
||||
|
||||
private fun createSecurityUser(member: Member): SecurityUser {
|
||||
if (member.activated.not()) {
|
||||
throw MemberDeactivatedException(member.email)
|
||||
throw BusinessException(ErrorCode.AUTH_MEMBER_DEACTIVATED)
|
||||
}
|
||||
|
||||
return SecurityUser(
|
||||
@@ -1,9 +1,10 @@
|
||||
package io.beaniejoy.dongnecafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.member.entity.Member
|
||||
import io.beaniejoy.dongnecafe.domain.member.model.request.MemberRegisterRequest
|
||||
import io.beaniejoy.dongnecafe.domain.member.repository.MemberRepository
|
||||
import io.beaniejoy.dongnecafe.error.MemberExistedException
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.transaction.annotation.Transactional
|
||||
@@ -16,7 +17,7 @@ class MemberService(
|
||||
) {
|
||||
fun registerMember(resource: MemberRegisterRequest): Long {
|
||||
memberRepository.findByEmail(resource.email!!)?.also {
|
||||
throw MemberExistedException(resource.email!!)
|
||||
throw BusinessException(ErrorCode.MEMBER_EXISTED)
|
||||
}
|
||||
|
||||
val registeredMember = memberRepository.save(
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.beaniejoy.dongnecafe.common.error.advice
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import mu.KLogging
|
||||
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
|
||||
|
||||
@RestControllerAdvice
|
||||
class BasicControllerAdvice {
|
||||
|
||||
companion object : KLogging()
|
||||
|
||||
/**
|
||||
* 예외 상황 (500 시스템 오류 처리)
|
||||
* @param e Exception
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(Exception::class)
|
||||
fun handleException(e: Exception): ApplicationResponse<Nothing> {
|
||||
logger.error { "[COMMON][${e.javaClass.simpleName}] $e" }
|
||||
return ApplicationResponse.fail(errorCode = ErrorCode.COMMON_SERVER_ERROR).build()
|
||||
}
|
||||
|
||||
/**
|
||||
* 비즈니스 로직 상 에러 처리(예상 가능한 예외 처리)
|
||||
* @param e BusinessException
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@ExceptionHandler(BusinessException::class)
|
||||
fun handleBusinessException(e: BusinessException): ApplicationResponse<Nothing> {
|
||||
logger.error { "[${BusinessException::class.simpleName}] <ErrorCode>: ${e.errorCode.name}, <ErrorMessage>: ${e.message}" }
|
||||
return ApplicationResponse.fail(errorCode = e.errorCode).build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package io.beaniejoy.dongnecafe.common.error.constant
|
||||
|
||||
enum class Domain {
|
||||
COMMON,
|
||||
|
||||
AUTH,
|
||||
MEMBER,
|
||||
|
||||
CAFE,
|
||||
CAFE_MENU,
|
||||
MENU_OPTION,
|
||||
OPTION_DETAIL
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.beaniejoy.dongnecafe.common.error.constant
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.Domain.*
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.SubCategory.*
|
||||
|
||||
enum class ErrorCode(
|
||||
val domain: Domain,
|
||||
val subCategory: SubCategory
|
||||
) {
|
||||
// COMMON
|
||||
COMMON_SERVER_ERROR(COMMON, SERVER_ERROR),
|
||||
|
||||
// AUTH(security 관련)
|
||||
AUTH_MEMBER_NOT_FOUND(AUTH, INVALID_AUTHENTICATE_REQUEST),
|
||||
AUTH_PASSWORD_NOT_VALID(AUTH, INVALID_AUTHENTICATE_REQUEST),
|
||||
AUTH_MEMBER_DEACTIVATED(AUTH, DEACTIVATED),
|
||||
|
||||
// MEMBER
|
||||
MEMBER_EXISTED(MEMBER, EXISTED),
|
||||
|
||||
// CAFE
|
||||
CAFE_NOT_FOUND(CAFE, NOT_FOUND),
|
||||
CAFE_EXISTED(CAFE, EXISTED),
|
||||
|
||||
// CAFE_MENU
|
||||
CAFE_MENU_NOT_FOUND(CAFE_MENU, NOT_FOUND),
|
||||
|
||||
// MENU_OPTION
|
||||
MENU_OPTION_NOT_FOUND(MENU_OPTION, NOT_FOUND),
|
||||
|
||||
// OPTION_DETAIL
|
||||
OPTION_DETAIL_NOT_FOUND(OPTION_DETAIL, NOT_FOUND)
|
||||
|
||||
;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.beaniejoy.dongnecafe.common.error.constant
|
||||
|
||||
enum class SubCategory {
|
||||
SERVER_ERROR,
|
||||
INVALID_AUTHENTICATE_REQUEST,
|
||||
NOT_FOUND,
|
||||
EXISTED,
|
||||
DEACTIVATED
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package io.beaniejoy.dongnecafe.common.error.exception
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
|
||||
/*
|
||||
* Business Logic 상 발생 가능한 Exception
|
||||
* - 로직상 개발자가 예측 가능한 예외
|
||||
* - front 측면에서 해당 에러에 대해서 error code(4xx, 5xx)보다 success code(2xx)를 응답받게 설계
|
||||
* - front에서 해당 예외 응답에 대해서 ErrorResponse의 Result field로 따로 구분해서 처리가능
|
||||
*/
|
||||
class BusinessException : RuntimeException {
|
||||
var errorCode: ErrorCode
|
||||
private set
|
||||
|
||||
constructor(errorCode: ErrorCode) : super(errorCode.name){
|
||||
this.errorCode = errorCode
|
||||
}
|
||||
|
||||
constructor(errorCode: ErrorCode, message: String): super(message) {
|
||||
this.errorCode = errorCode
|
||||
}
|
||||
|
||||
constructor(errorCode: ErrorCode, cause: Throwable) : super(cause) {
|
||||
this.errorCode = errorCode
|
||||
}
|
||||
|
||||
constructor(errorCode: ErrorCode, message: String, cause: Throwable) : super(message, cause) {
|
||||
this.errorCode = errorCode
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package io.beaniejoy.dongnecafe.common.response
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
class ApplicationResponse<T>(
|
||||
val result: ResultCode,
|
||||
val message: String? = null,
|
||||
val errorCode: String? = null,
|
||||
val data: T? = null
|
||||
) {
|
||||
companion object {
|
||||
fun success(message: String? = null): ApplicationResponseBuilder {
|
||||
return ApplicationResponseBuilder(
|
||||
result = ResultCode.SUCCESS,
|
||||
message = message
|
||||
)
|
||||
}
|
||||
|
||||
fun fail(errorCode: ErrorCode, message: String? = null): ApplicationResponseBuilder {
|
||||
return ApplicationResponseBuilder(
|
||||
result = ResultCode.FAIL,
|
||||
message = message,
|
||||
errorCode = errorCode.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ApplicationResponseBuilder(
|
||||
var result: ResultCode,
|
||||
var message: String? = null,
|
||||
var errorCode: String? = null
|
||||
) {
|
||||
fun build(): ApplicationResponse<Nothing> {
|
||||
return data(null)
|
||||
}
|
||||
|
||||
fun <T> data(data: T?): ApplicationResponse<T> {
|
||||
return ApplicationResponse(
|
||||
result = this.result,
|
||||
message = this.message,
|
||||
data = data,
|
||||
errorCode = errorCode
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.common.response
|
||||
|
||||
enum class ResultCode {
|
||||
SUCCESS,
|
||||
FAIL;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error
|
||||
|
||||
data class ErrorResponse(
|
||||
val code: Int,
|
||||
val message: String?
|
||||
)
|
||||
@@ -6,10 +6,10 @@ import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.autoconfigure.security.servlet.PathRequest
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.http.HttpMethod
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer
|
||||
import org.springframework.security.config.http.SessionCreationPolicy
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
|
||||
@Configuration
|
||||
@@ -22,9 +22,15 @@ class SecurityConfig {
|
||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
return http
|
||||
.csrf().disable()
|
||||
.formLogin().disable()
|
||||
|
||||
// FIXME 임시 permitAll 설정
|
||||
.authorizeRequests()
|
||||
.anyRequest().authenticated()
|
||||
.anyRequest().permitAll()
|
||||
|
||||
.and()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 토큰 방식(세션 불필요)
|
||||
|
||||
.and()
|
||||
.also { jwtAuthenticationConfigurer(it) }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.controller
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeRegisterRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeUpdateRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeDetailedInfo
|
||||
@@ -20,14 +21,18 @@ class CafeController(
|
||||
* 신규 카페 생성
|
||||
*/
|
||||
@PostMapping
|
||||
fun createNewCafe(@RequestBody resource: CafeRegisterRequest): Long {
|
||||
return cafeService.createNew(
|
||||
fun createNewCafe(@RequestBody resource: CafeRegisterRequest): ApplicationResponse<Long> {
|
||||
val newCafeId = cafeService.createNew(
|
||||
name = resource.name!!,
|
||||
address = resource.address!!,
|
||||
phoneNumber = resource.phoneNumber!!,
|
||||
description = resource.description!!,
|
||||
cafeMenuRequestList = resource.cafeMenuList
|
||||
)
|
||||
|
||||
return ApplicationResponse
|
||||
.success("OK")
|
||||
.data(newCafeId)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,16 +41,24 @@ class CafeController(
|
||||
@GetMapping
|
||||
fun searchCafeList(
|
||||
@PageableDefault(sort = ["name"], direction = Sort.Direction.ASC, page = 0, size = 10) pageable: Pageable
|
||||
): Page<CafeSearchInfo> {
|
||||
return cafeService.searchCafeList(pageable)
|
||||
): ApplicationResponse<Page<CafeSearchInfo>> {
|
||||
val searchCafes = cafeService.searchCafeList(pageable)
|
||||
|
||||
return ApplicationResponse
|
||||
.success()
|
||||
.data(searchCafes)
|
||||
}
|
||||
|
||||
/**
|
||||
* 단일 카페 상세 조회
|
||||
*/
|
||||
@GetMapping("/{id}")
|
||||
fun getDetailedInfo(@PathVariable("id") id: Long): CafeDetailedInfo {
|
||||
return cafeService.getDetailedInfoByCafeId(id)
|
||||
fun getDetailedInfo(@PathVariable("id") id: Long): ApplicationResponse<CafeDetailedInfo> {
|
||||
val cafeDetailedInfo = cafeService.getDetailedInfoByCafeId(id)
|
||||
|
||||
return ApplicationResponse
|
||||
.success()
|
||||
.data(cafeDetailedInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +69,7 @@ class CafeController(
|
||||
fun updateInfo(
|
||||
@PathVariable("id") id: Long,
|
||||
@RequestBody resource: CafeUpdateRequest
|
||||
): String {
|
||||
): ApplicationResponse<Nothing> {
|
||||
cafeService.updateInfo(
|
||||
id = id,
|
||||
name = resource.name!!,
|
||||
@@ -65,6 +78,8 @@ class CafeController(
|
||||
description = resource.description!!
|
||||
)
|
||||
|
||||
return "Successfully Cafe[$id] Info Updated"
|
||||
return ApplicationResponse
|
||||
.success("Successfully Cafe[$id] Info Updated")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.controller
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.response.ApplicationResponse
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuBulkDeleteRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuUpdateRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeMenuDetailedInfo
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.service.CafeMenuService
|
||||
import org.springframework.web.bind.annotation.DeleteMapping
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PatchMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.bind.annotation.*
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/cafes/{cafeId}/menus")
|
||||
@@ -24,11 +19,15 @@ class CafeMenuController(
|
||||
fun getDetailedInfo(
|
||||
@PathVariable("cafeId") cafeId: Long,
|
||||
@PathVariable("menuId") menuId: Long
|
||||
): CafeMenuDetailedInfo {
|
||||
return cafeMenuService.getDetailedInfoByMenuId(
|
||||
): ApplicationResponse<CafeMenuDetailedInfo> {
|
||||
val cafeMenuDetailedInfo = cafeMenuService.getDetailedInfoByMenuId(
|
||||
menuId = menuId,
|
||||
cafeId = cafeId
|
||||
)
|
||||
|
||||
return ApplicationResponse
|
||||
.success()
|
||||
.data(cafeMenuDetailedInfo)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,14 +41,16 @@ class CafeMenuController(
|
||||
@PathVariable("cafeId") cafeId: Long,
|
||||
@PathVariable("menuId") menuId: Long,
|
||||
@RequestBody cafeMenuUpdateRequest: CafeMenuUpdateRequest
|
||||
): String {
|
||||
): ApplicationResponse<Nothing> {
|
||||
cafeMenuService.updateInfoAndBulkUpdate(
|
||||
menuId = menuId,
|
||||
cafeId = cafeId,
|
||||
resource = cafeMenuUpdateRequest
|
||||
)
|
||||
|
||||
return "Success Update Cafe[$cafeId]'s CafeMenu[$menuId]"
|
||||
return ApplicationResponse
|
||||
.success("Success Update Cafe[$cafeId]'s CafeMenu[$menuId]")
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,13 +60,15 @@ class CafeMenuController(
|
||||
fun delete(
|
||||
@PathVariable("cafeId") cafeId: Long,
|
||||
@PathVariable("menuId") menuId: Long
|
||||
): String {
|
||||
): ApplicationResponse<Nothing> {
|
||||
cafeMenuService.deleteByCafeMenuId(
|
||||
menuId = menuId,
|
||||
cafeId = cafeId
|
||||
)
|
||||
|
||||
return "Success Delete Cafe[$cafeId]'s CafeMenu[$menuId]"
|
||||
return ApplicationResponse
|
||||
.success("Success Delete Cafe[$cafeId]'s CafeMenu[$menuId]")
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,9 +78,11 @@ class CafeMenuController(
|
||||
fun bulkDelete(
|
||||
@PathVariable("cafeId") cafeId: Long,
|
||||
@RequestBody resource: CafeMenuBulkDeleteRequest
|
||||
): String {
|
||||
): ApplicationResponse<Nothing> {
|
||||
cafeMenuService.bulkDelete(cafeId, resource.cafeMenuIdList)
|
||||
|
||||
return "Success Delete Cafe[$cafeId]'s CafeMenu List"
|
||||
return ApplicationResponse
|
||||
.success("Success Delete Cafe[$cafeId]'s CafeMenu List")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeMenuDetailedInfo
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeMenuNotFoundException
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuUpdateRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeMenuDetailedInfo
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeMenuRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -20,14 +21,14 @@ class CafeMenuService(
|
||||
@Transactional(readOnly = true)
|
||||
fun getDetailedInfoByMenuId(menuId: Long, cafeId: Long): CafeMenuDetailedInfo {
|
||||
val cafeMenu = cafeMenuRepository.findByIdOrNull(menuId)
|
||||
?: throw CafeMenuNotFoundException(menuId = menuId, cafeId = cafeId)
|
||||
?: throw BusinessException(ErrorCode.CAFE_MENU_NOT_FOUND)
|
||||
|
||||
return CafeMenuDetailedInfo.of(cafeMenu)
|
||||
}
|
||||
|
||||
fun updateInfoAndBulkUpdate(menuId: Long, cafeId: Long, resource: CafeMenuUpdateRequest) {
|
||||
val cafeMenu = cafeMenuRepository.findByIdOrNull(menuId)
|
||||
?: throw CafeMenuNotFoundException(menuId = menuId, cafeId = cafeId)
|
||||
?: throw BusinessException(ErrorCode.CAFE_MENU_NOT_FOUND)
|
||||
|
||||
cafeMenu.updateInfo(name = resource.name!!, price = resource.price)
|
||||
|
||||
@@ -36,7 +37,7 @@ class CafeMenuService(
|
||||
|
||||
fun deleteByCafeMenuId(menuId: Long, cafeId: Long) {
|
||||
val cafeMenu = cafeMenuRepository.findByIdOrNull(menuId)
|
||||
?: throw CafeMenuNotFoundException(menuId = menuId, cafeId = cafeId)
|
||||
?: throw BusinessException(ErrorCode.CAFE_MENU_NOT_FOUND)
|
||||
|
||||
cafeMenuRepository.delete(cafeMenu)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuRegisterRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeDetailedInfo
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.response.CafeSearchInfo
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.CafeMenuRegisterRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeExistedException
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeNotFoundException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import mu.KLogging
|
||||
import org.springframework.data.domain.Page
|
||||
import org.springframework.data.domain.Pageable
|
||||
@@ -55,7 +55,7 @@ class CafeService(
|
||||
private fun checkCafeExistedByName(name: String) {
|
||||
val findCafe = cafeRepository.findByName(name)
|
||||
if (findCafe != null) {
|
||||
throw CafeExistedException(name)
|
||||
throw BusinessException(ErrorCode.CAFE_EXISTED)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class CafeService(
|
||||
|
||||
fun getDetailedInfoByCafeId(id: Long): CafeDetailedInfo {
|
||||
val cafe = cafeRepository.findByIdOrNull(id)
|
||||
?: throw CafeNotFoundException(id)
|
||||
?: throw BusinessException(ErrorCode.CAFE_NOT_FOUND)
|
||||
|
||||
return CafeDetailedInfo.of(cafe)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ class CafeService(
|
||||
description: String,
|
||||
) {
|
||||
val cafe = cafeRepository.findByIdOrNull(id)
|
||||
?: throw CafeNotFoundException(id)
|
||||
?: throw BusinessException(ErrorCode.CAFE_NOT_FOUND)
|
||||
|
||||
cafe.updateInfo(
|
||||
name = name,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.error.exception.MenuOptionNotFoundException
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.MenuOptionUpdateRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.MenuOptionRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
@@ -16,7 +17,7 @@ class MenuOptionService(
|
||||
fun bulkUpdate(resources: List<MenuOptionUpdateRequest>) {
|
||||
resources.forEach {
|
||||
val menuOption = menuOptionRepository.findByIdOrNull(it.menuOptionId)
|
||||
?: throw MenuOptionNotFoundException(it.menuOptionId)
|
||||
?: throw BusinessException(ErrorCode.MENU_OPTION_NOT_FOUND)
|
||||
|
||||
if (it.isDelete) {
|
||||
menuOptionRepository.delete(menuOption)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.error.exception.OptionDetailNotFoundException
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.model.request.OptionDetailUpdateRequest
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.OptionDetailRepository
|
||||
import org.springframework.data.repository.findByIdOrNull
|
||||
@@ -15,7 +16,7 @@ class OptionDetailService(
|
||||
fun bulkUpdate(resources: List<OptionDetailUpdateRequest>) {
|
||||
resources.forEach {
|
||||
val optionDetail = optionDetailRepository.findByIdOrNull(it.optionDetailId)
|
||||
?: throw OptionDetailNotFoundException(it.optionDetailId)
|
||||
?: throw BusinessException(ErrorCode.OPTION_DETAIL_NOT_FOUND)
|
||||
|
||||
if (it.isDelete) {
|
||||
optionDetailRepository.delete(optionDetail)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error
|
||||
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeNotFoundException
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice
|
||||
|
||||
@RestControllerAdvice
|
||||
class CafeExceptionHandler {
|
||||
// TODO: error 규격화
|
||||
@ExceptionHandler(CafeNotFoundException::class)
|
||||
fun handleNotFound(exception: CafeNotFoundException): ResponseEntity<String> {
|
||||
return ResponseEntity.badRequest().body(exception.message)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.exception
|
||||
|
||||
class CafeExistedException(name: String) : RuntimeException("Cafe[$name] is already existed")
|
||||
@@ -1,4 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.exception
|
||||
|
||||
class CafeMenuNotFoundException(menuId: Long, cafeId: Long) :
|
||||
RuntimeException("Cafe[${cafeId}]의 Menu[${menuId}]는 존재하지 않는 메뉴입니다.")
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.exception
|
||||
|
||||
class CafeNotFoundException(cafeId: Long) : RuntimeException("Cafe[$cafeId] is not found")
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.exception
|
||||
|
||||
class MenuOptionNotFoundException(menuOptionId: Long) : RuntimeException("MenuOption[$menuOptionId] is not found")
|
||||
@@ -1,3 +0,0 @@
|
||||
package io.beaniejoy.dongnecafe.error.exception
|
||||
|
||||
class OptionDetailNotFoundException(optionDetailId: Long) : RuntimeException("OptionDetail[$optionDetailId] is not found")
|
||||
@@ -1,7 +1,8 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.CafeMenu
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeMenuNotFoundException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeMenuRepository
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.MenuOptionRepository
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.OptionDetailRepository
|
||||
@@ -81,12 +82,12 @@ internal class CafeMenuServiceTest {
|
||||
`when`(mockCafeMenuRepository.findById(findCafeMenuId)).thenReturn(Optional.empty())
|
||||
|
||||
// then
|
||||
val exception = assertThrows<CafeMenuNotFoundException> {
|
||||
val exception = assertThrows<BusinessException> {
|
||||
// when
|
||||
mockCafeMenuService.getDetailedInfoByMenuId(findCafeMenuId, findCafeId)
|
||||
}
|
||||
|
||||
assertEquals("Cafe[${findCafeId}]의 Menu[${findCafeMenuId}]는 존재하지 않는 메뉴입니다.", exception.message)
|
||||
assertEquals(ErrorCode.CAFE_MENU_NOT_FOUND, exception.errorCode)
|
||||
|
||||
verify(mockCafeMenuRepository).findById(findCafeMenuId)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package io.beaniejoy.dongnecafe.domain.cafe.service
|
||||
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.entity.Cafe
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeExistedException
|
||||
import io.beaniejoy.dongnecafe.error.exception.CafeNotFoundException
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.repository.CafeRepository
|
||||
import io.beaniejoy.dongnecafe.domain.cafe.utils.CafeTestUtils
|
||||
import io.beaniejoy.dongnecafe.common.error.constant.ErrorCode
|
||||
import io.beaniejoy.dongnecafe.common.error.exception.BusinessException
|
||||
import org.junit.jupiter.api.*
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.extension.ExtendWith
|
||||
@@ -68,7 +68,7 @@ internal class CafeServiceTest {
|
||||
`when`(mockCafeRepository.findByName(name)).thenReturn(cafe)
|
||||
|
||||
// then
|
||||
assertThrows<CafeExistedException> {
|
||||
val exception = assertThrows<BusinessException> {
|
||||
// when
|
||||
mockCafeService.createNew(
|
||||
name = name,
|
||||
@@ -78,8 +78,8 @@ internal class CafeServiceTest {
|
||||
cafeMenuRequestList = cafeMenuList
|
||||
)
|
||||
}
|
||||
|
||||
verify(mockCafeRepository).findByName(name)
|
||||
assertEquals(ErrorCode.CAFE_EXISTED, exception.errorCode)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -134,7 +134,7 @@ internal class CafeServiceTest {
|
||||
|
||||
`when`(mockCafeRepository.findById(cafeId)).thenReturn(Optional.empty())
|
||||
|
||||
assertThrows<CafeNotFoundException> {
|
||||
val exception = assertThrows<BusinessException> {
|
||||
mockCafeService.updateInfo(
|
||||
id = cafeId,
|
||||
name = "",
|
||||
@@ -143,5 +143,7 @@ internal class CafeServiceTest {
|
||||
description = "",
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(ErrorCode.CAFE_NOT_FOUND, exception.errorCode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user