diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt deleted file mode 100644 index b5498f1..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/model/AuthenticationResult.kt +++ /dev/null @@ -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 = listOf(), - val msg: String -) \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt index 37dcfce..92ab5d8 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/AuthController.kt @@ -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 { 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)) } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/MemberController.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/MemberController.kt index c367131..a61b158 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/MemberController.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/controller/MemberController.kt @@ -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 { + val registerMemberId = memberService.registerMember(resource) + + return ApplicationResponse + .success("success sign up") + .data(registerMemberId) } } \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberExistedException.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberExistedException.kt deleted file mode 100644 index 31d58e1..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberExistedException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error - -class MemberExistedException(email: String): RuntimeException("Member[$email] is already existed") \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt deleted file mode 100644 index 97bedd5..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/MemberNotActivatedException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error - -class MemberDeactivatedException(email: String): RuntimeException("Member[$email] is deactivated") \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/handler/CommonControllerAdvice.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/handler/CommonControllerAdvice.kt deleted file mode 100644 index 83d901c..0000000 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/handler/CommonControllerAdvice.kt +++ /dev/null @@ -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 { - logger.error { "AuthenticationException: ${e.message}" } - return ResponseEntity.ok().body( - ErrorResponse( - code = HttpStatus.BAD_REQUEST.value(), - message = "계정 혹은 비밀번호가 일치하지 않습니다." - ) - ) - } -} \ No newline at end of file diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/ApiAuthenticationProvider.kt similarity index 85% rename from dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt rename to dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/ApiAuthenticationProvider.kt index 6990e3d..18df2f0 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/ApiAuthenticationProvider.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/ApiAuthenticationProvider.kt @@ -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}" } diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/UserDetailsServiceImpl.kt similarity index 79% rename from dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt rename to dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/UserDetailsServiceImpl.kt index b0259bb..d1ef467 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/security/UserDetailsServiceImpl.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/security/UserDetailsServiceImpl.kt @@ -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( diff --git a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/MemberService.kt b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/MemberService.kt index f4e11d7..3882b99 100644 --- a/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/MemberService.kt +++ b/dongne-account-api/src/main/kotlin/io/beaniejoy/dongnecafe/service/MemberService.kt @@ -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( diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/advice/BasicControllerAdvice.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/advice/BasicControllerAdvice.kt new file mode 100644 index 0000000..e5748fb --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/advice/BasicControllerAdvice.kt @@ -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 { + 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 { + logger.error { "[${BusinessException::class.simpleName}] : ${e.errorCode.name}, : ${e.message}" } + return ApplicationResponse.fail(errorCode = e.errorCode).build() + } +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/Domain.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/Domain.kt new file mode 100644 index 0000000..8aef3f6 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/Domain.kt @@ -0,0 +1,13 @@ +package io.beaniejoy.dongnecafe.common.error.constant + +enum class Domain { + COMMON, + + AUTH, + MEMBER, + + CAFE, + CAFE_MENU, + MENU_OPTION, + OPTION_DETAIL +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/ErrorCode.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/ErrorCode.kt new file mode 100644 index 0000000..b2422ad --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/ErrorCode.kt @@ -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) + + ; +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/SubCategory.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/SubCategory.kt new file mode 100644 index 0000000..c70d169 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/constant/SubCategory.kt @@ -0,0 +1,9 @@ +package io.beaniejoy.dongnecafe.common.error.constant + +enum class SubCategory { + SERVER_ERROR, + INVALID_AUTHENTICATE_REQUEST, + NOT_FOUND, + EXISTED, + DEACTIVATED +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/exception/BusinessException.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/exception/BusinessException.kt new file mode 100644 index 0000000..c945882 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/error/exception/BusinessException.kt @@ -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 + } +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ApplicationResponse.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ApplicationResponse.kt new file mode 100644 index 0000000..5f4bae9 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ApplicationResponse.kt @@ -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( + 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 { + return data(null) + } + + fun data(data: T?): ApplicationResponse { + return ApplicationResponse( + result = this.result, + message = this.message, + data = data, + errorCode = errorCode + ) + } + +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ResultCode.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ResultCode.kt new file mode 100644 index 0000000..df7f271 --- /dev/null +++ b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/common/response/ResultCode.kt @@ -0,0 +1,6 @@ +package io.beaniejoy.dongnecafe.common.response + +enum class ResultCode { + SUCCESS, + FAIL; +} \ No newline at end of file diff --git a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/error/ErrorResponse.kt b/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/error/ErrorResponse.kt deleted file mode 100644 index a6e8abd..0000000 --- a/dongne-common/src/main/kotlin/io/beaniejoy/dongnecafe/error/ErrorResponse.kt +++ /dev/null @@ -1,6 +0,0 @@ -package io.beaniejoy.dongnecafe.error - -data class ErrorResponse( - val code: Int, - val message: String? -) \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt index 7bf46f1..50252a0 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/common/config/SecurityConfig.kt @@ -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) } diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt index f9b9bc1..39341e1 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeController.kt @@ -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 { + 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 { - return cafeService.searchCafeList(pageable) + ): ApplicationResponse> { + 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 { + 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 { 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() } } \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeMenuController.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeMenuController.kt index 4e41697..3219804 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeMenuController.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/controller/CafeMenuController.kt @@ -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 { + 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 { 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 { 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 { cafeMenuService.bulkDelete(cafeId, resource.cafeMenuIdList) - return "Success Delete Cafe[$cafeId]'s CafeMenu List" + return ApplicationResponse + .success("Success Delete Cafe[$cafeId]'s CafeMenu List") + .build() } } \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuService.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuService.kt index 72733c6..f00f628 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuService.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuService.kt @@ -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) } diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt index e311c5d..cc8092c 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeService.kt @@ -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, diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/MenuOptionService.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/MenuOptionService.kt index a34440e..6954744 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/MenuOptionService.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/MenuOptionService.kt @@ -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) { 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) diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/OptionDetailService.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/OptionDetailService.kt index 94e5753..43cde2f 100644 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/OptionDetailService.kt +++ b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/OptionDetailService.kt @@ -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) { 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) diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/CafeExceptionHandler.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/CafeExceptionHandler.kt deleted file mode 100644 index 1d869ea..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/CafeExceptionHandler.kt +++ /dev/null @@ -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 { - return ResponseEntity.badRequest().body(exception.message) - } -} \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeExistedException.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeExistedException.kt deleted file mode 100644 index e88e84c..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeExistedException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error.exception - -class CafeExistedException(name: String) : RuntimeException("Cafe[$name] is already existed") \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeMenuNotFoundException.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeMenuNotFoundException.kt deleted file mode 100644 index bb60691..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeMenuNotFoundException.kt +++ /dev/null @@ -1,4 +0,0 @@ -package io.beaniejoy.dongnecafe.error.exception - -class CafeMenuNotFoundException(menuId: Long, cafeId: Long) : - RuntimeException("Cafe[${cafeId}]의 Menu[${menuId}]는 존재하지 않는 메뉴입니다.") \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeNotFoundException.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeNotFoundException.kt deleted file mode 100644 index e8a0c09..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/CafeNotFoundException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error.exception - -class CafeNotFoundException(cafeId: Long) : RuntimeException("Cafe[$cafeId] is not found") \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/MenuOptionNotFoundException.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/MenuOptionNotFoundException.kt deleted file mode 100644 index cb42fe7..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/MenuOptionNotFoundException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error.exception - -class MenuOptionNotFoundException(menuOptionId: Long) : RuntimeException("MenuOption[$menuOptionId] is not found") \ No newline at end of file diff --git a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/OptionDetailNotFoundException.kt b/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/OptionDetailNotFoundException.kt deleted file mode 100644 index 69df3ac..0000000 --- a/dongne-service-api/src/main/kotlin/io/beaniejoy/dongnecafe/error/exception/OptionDetailNotFoundException.kt +++ /dev/null @@ -1,3 +0,0 @@ -package io.beaniejoy.dongnecafe.error.exception - -class OptionDetailNotFoundException(optionDetailId: Long) : RuntimeException("OptionDetail[$optionDetailId] is not found") \ No newline at end of file diff --git a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuServiceTest.kt b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuServiceTest.kt index f9789a2..aae2eb1 100644 --- a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuServiceTest.kt +++ b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeMenuServiceTest.kt @@ -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 { + val exception = assertThrows { // when mockCafeMenuService.getDetailedInfoByMenuId(findCafeMenuId, findCafeId) } - assertEquals("Cafe[${findCafeId}]의 Menu[${findCafeMenuId}]는 존재하지 않는 메뉴입니다.", exception.message) + assertEquals(ErrorCode.CAFE_MENU_NOT_FOUND, exception.errorCode) verify(mockCafeMenuRepository).findById(findCafeMenuId) } diff --git a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt index 7914fe0..42d3315 100644 --- a/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt +++ b/dongne-service-api/src/test/kotlin/io/beaniejoy/dongnecafe/domain/cafe/service/CafeServiceTest.kt @@ -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 { + val exception = assertThrows { // 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 { + val exception = assertThrows { mockCafeService.updateInfo( id = cafeId, name = "", @@ -143,5 +143,7 @@ internal class CafeServiceTest { description = "", ) } + + assertEquals(ErrorCode.CAFE_NOT_FOUND, exception.errorCode) } } \ No newline at end of file