diff --git a/src/main/kotlin/roomescape/admin/infrastructure/persistence/AdminEntity.kt b/src/main/kotlin/roomescape/admin/infrastructure/persistence/AdminEntity.kt index 5f98745e..3de6097f 100644 --- a/src/main/kotlin/roomescape/admin/infrastructure/persistence/AdminEntity.kt +++ b/src/main/kotlin/roomescape/admin/infrastructure/persistence/AdminEntity.kt @@ -1,10 +1,6 @@ package roomescape.admin.infrastructure.persistence -import jakarta.persistence.Entity -import jakarta.persistence.EntityListeners -import jakarta.persistence.EnumType -import jakarta.persistence.Enumerated -import jakarta.persistence.Table +import jakarta.persistence.* import org.springframework.data.jpa.domain.support.AuditingEntityListener import roomescape.common.entity.AuditingBaseEntity diff --git a/src/main/kotlin/roomescape/auth/business/AuthService.kt b/src/main/kotlin/roomescape/auth/business/AuthService.kt index e2f23227..c4548244 100644 --- a/src/main/kotlin/roomescape/auth/business/AuthService.kt +++ b/src/main/kotlin/roomescape/auth/business/AuthService.kt @@ -4,55 +4,111 @@ import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import roomescape.admin.business.AdminService import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthException -import roomescape.auth.infrastructure.jwt.JwtHandler -import roomescape.auth.web.LoginCheckResponse -import roomescape.auth.web.LoginRequest -import roomescape.auth.web.LoginResponse -import roomescape.member.implement.MemberFinder -import roomescape.member.infrastructure.persistence.MemberEntity +import roomescape.auth.infrastructure.jwt.JwtUtils +import roomescape.auth.web.LoginContext +import roomescape.auth.web.LoginRequestV2 +import roomescape.auth.web.LoginSuccessResponse +import roomescape.common.dto.CurrentUserContext +import roomescape.common.dto.LoginCredentials +import roomescape.common.dto.PrincipalType +import roomescape.member.business.UserService private val log: KLogger = KotlinLogging.logger {} +const val CLAIM_PERMISSION_KEY = "permission" +const val CLAIM_TYPE_KEY = "principal_type" + @Service class AuthService( - private val memberFinder: MemberFinder, - private val jwtHandler: JwtHandler, + private val adminService: AdminService, + private val userService: UserService, + private val loginHistoryService: LoginHistoryService, + private val jwtUtils: JwtUtils, ) { @Transactional(readOnly = true) - fun login(request: LoginRequest): LoginResponse { - val params = "email=${request.email}, password=${request.password}" - log.debug { "[AuthService.login] 시작: $params" } + fun login( + request: LoginRequestV2, + context: LoginContext + ): LoginSuccessResponse { + log.info { "[AuthService.login] 로그인 시작: account=${request.account}, type=${request.principalType}, context=${context}" } - val member: MemberEntity = fetchOrThrow(AuthErrorCode.LOGIN_FAILED) { - memberFinder.findByEmailAndPassword(request.email, request.password) + val (credentials, extraClaims) = getCredentials(request) + + try { + verifyPasswordOrThrow(request, credentials) + + val accessToken = jwtUtils.createToken(subject = credentials.id.toString(), claims = extraClaims) + + loginHistoryService.createSuccessHistory(credentials.id, request.principalType, context) + + return LoginSuccessResponse(accessToken).also { + log.info { "[AuthService.login] 로그인 완료: account=${request.account}, context=${context}" } + } + + } catch (e: Exception) { + loginHistoryService.createFailureHistory(credentials.id, request.principalType, context) + + when (e) { + is AuthException -> { + log.info { "[AuthService.login] 로그인 실패: account = ${request.account}" } + throw e + } + + else -> { + log.warn { "[AuthService.login] 로그인 실패: message=${e.message} account = ${request.account}" } + throw AuthException(AuthErrorCode.TEMPORARY_AUTH_ERROR) + } + } } - val accessToken: String = jwtHandler.createToken(member.id!!) - - return LoginResponse(accessToken) - .also { log.info { "[AuthService.login] 완료: email=${request.email}, memberId=${member.id}" } } } @Transactional(readOnly = true) - fun checkLogin(memberId: Long): LoginCheckResponse { - log.debug { "[AuthService.checkLogin] 시작: memberId=$memberId" } + fun findContextById(id: Long, type: PrincipalType): CurrentUserContext { + log.info { "[AuthService.checkLogin] 로그인 확인 시작: id=${id}, type=${type}" } - val member: MemberEntity = fetchOrThrow(AuthErrorCode.MEMBER_NOT_FOUND) { memberFinder.findById(memberId) } + return when (type) { + PrincipalType.ADMIN -> { + adminService.findContextById(id) + } - return LoginCheckResponse(member.name, member.role.name) - .also { log.info { "[AuthService.checkLogin] 완료: memberId=$memberId, role=${it.role}" } } - } - - private fun fetchOrThrow(errorCode: AuthErrorCode, block: () -> MemberEntity): MemberEntity { - try { - return block() - } catch (e: Exception) { - throw AuthException(errorCode, e.message ?: errorCode.message) + PrincipalType.USER -> { + userService.findContextById(id) + } + }.also { + log.info { "[AuthService.checkLogin] 로그인 확인 완료: id=${id}, type=${type}" } } } - fun logout(memberId: Long) { - log.info { "[AuthService.logout] 로그아웃: memberId=$memberId" } + private fun verifyPasswordOrThrow( + request: LoginRequestV2, + credentials: LoginCredentials + ) { + if (credentials.password != request.password) { + log.info { "[AuthService.login] 비밀번호 불일치로 인한 로그인 실패: account = ${request.account}" } + throw AuthException(AuthErrorCode.LOGIN_FAILED) + } + } + + private fun getCredentials(request: LoginRequestV2): Pair> { + val extraClaims: MutableMap = mutableMapOf() + val credentials: LoginCredentials = when (request.principalType) { + PrincipalType.ADMIN -> { + adminService.findCredentialsByAccount(request.account).also { + extraClaims.put(CLAIM_PERMISSION_KEY, it.permissionLevel) + extraClaims.put(CLAIM_TYPE_KEY, PrincipalType.ADMIN) + } + } + + PrincipalType.USER -> { + userService.findCredentialsByAccount(request.account).also { + extraClaims.put(CLAIM_TYPE_KEY, PrincipalType.USER) + } + } + } + + return credentials to extraClaims } } diff --git a/src/main/kotlin/roomescape/auth/business/AuthServiceV2.kt b/src/main/kotlin/roomescape/auth/business/AuthServiceV2.kt deleted file mode 100644 index 7fd04dfb..00000000 --- a/src/main/kotlin/roomescape/auth/business/AuthServiceV2.kt +++ /dev/null @@ -1,114 +0,0 @@ -package roomescape.auth.business - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import roomescape.admin.business.AdminService -import roomescape.auth.exception.AuthErrorCode -import roomescape.auth.exception.AuthException -import roomescape.auth.infrastructure.jwt.JwtUtils -import roomescape.auth.web.LoginContext -import roomescape.auth.web.LoginRequestV2 -import roomescape.auth.web.LoginSuccessResponse -import roomescape.common.dto.CurrentUserContext -import roomescape.common.dto.LoginCredentials -import roomescape.common.dto.PrincipalType -import roomescape.member.business.UserService - -private val log: KLogger = KotlinLogging.logger {} - -const val CLAIM_PERMISSION_KEY = "permission" -const val CLAIM_TYPE_KEY = "principal_type" - -@Service -class AuthServiceV2( - private val adminService: AdminService, - private val userService: UserService, - private val loginHistoryService: LoginHistoryService, - private val jwtUtils: JwtUtils, -) { - @Transactional(readOnly = true) - fun login( - request: LoginRequestV2, - context: LoginContext - ): LoginSuccessResponse { - log.info { "[AuthService.login] 로그인 시작: account=${request.account}, type=${request.principalType}, context=${context}" } - - val (credentials, extraClaims) = getCredentials(request) - - try { - verifyPasswordOrThrow(request, credentials) - - val accessToken = jwtUtils.createToken(subject = credentials.id.toString(), claims = extraClaims) - - loginHistoryService.createSuccessHistory(credentials.id, request.principalType, context) - - return LoginSuccessResponse(accessToken).also { - log.info { "[AuthService.login] 로그인 완료: account=${request.account}, context=${context}" } - } - - } catch (e: Exception) { - loginHistoryService.createFailureHistory(credentials.id, request.principalType, context) - - when (e) { - is AuthException -> { - log.info { "[AuthService.login] 로그인 실패: account = ${request.account}" } - throw e - } - - else -> { - log.warn { "[AuthService.login] 로그인 실패: message=${e.message} account = ${request.account}" } - throw AuthException(AuthErrorCode.TEMPORARY_AUTH_ERROR) - } - } - } - } - - @Transactional(readOnly = true) - fun findContextById(id: Long, type: PrincipalType): CurrentUserContext { - log.info { "[AuthService.checkLogin] 로그인 확인 시작: id=${id}, type=${type}" } - - return when (type) { - PrincipalType.ADMIN -> { - adminService.findContextById(id) - } - - PrincipalType.USER -> { - userService.findContextById(id) - } - }.also { - log.info { "[AuthService.checkLogin] 로그인 확인 완료: id=${id}, type=${type}" } - } - } - - private fun verifyPasswordOrThrow( - request: LoginRequestV2, - credentials: LoginCredentials - ) { - if (credentials.password != request.password) { - log.info { "[AuthService.login] 비밀번호 불일치로 인한 로그인 실패: account = ${request.account}" } - throw AuthException(AuthErrorCode.LOGIN_FAILED) - } - } - - private fun getCredentials(request: LoginRequestV2): Pair> { - val extraClaims: MutableMap = mutableMapOf() - val credentials: LoginCredentials = when (request.principalType) { - PrincipalType.ADMIN -> { - adminService.findCredentialsByAccount(request.account).also { - extraClaims.put(CLAIM_PERMISSION_KEY, it.permissionLevel) - extraClaims.put(CLAIM_TYPE_KEY, PrincipalType.ADMIN) - } - } - - PrincipalType.USER -> { - userService.findCredentialsByAccount(request.account).also { - extraClaims.put(CLAIM_TYPE_KEY, PrincipalType.USER) - } - } - } - - return credentials to extraClaims - } -} diff --git a/src/main/kotlin/roomescape/auth/docs/AuthAPI.kt b/src/main/kotlin/roomescape/auth/docs/AuthAPI.kt index d9ada882..3eea8f6a 100644 --- a/src/main/kotlin/roomescape/auth/docs/AuthAPI.kt +++ b/src/main/kotlin/roomescape/auth/docs/AuthAPI.kt @@ -1,46 +1,52 @@ package roomescape.auth.docs import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import io.swagger.v3.oas.annotations.tags.Tag +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RequestBody -import roomescape.auth.web.LoginCheckResponse -import roomescape.auth.web.LoginRequest -import roomescape.auth.web.LoginResponse -import roomescape.auth.web.support.LoginRequired -import roomescape.auth.web.support.MemberId +import roomescape.auth.web.LoginRequestV2 +import roomescape.auth.web.LoginSuccessResponse +import roomescape.auth.web.support.CurrentUser +import roomescape.auth.web.support.Public +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse @Tag(name = "1. 인증 / 인가 API", description = "로그인, 로그아웃 및 로그인 상태를 확인합니다") interface AuthAPI { + + @Public @Operation(summary = "로그인") @ApiResponses( ApiResponse(responseCode = "200", description = "로그인 성공시 토큰을 반환합니다."), ) fun login( - @Valid @RequestBody loginRequest: LoginRequest - ): ResponseEntity> + @Valid @RequestBody loginRequest: LoginRequestV2, + servletRequest: HttpServletRequest + ): ResponseEntity> @Operation(summary = "로그인 상태 확인") @ApiResponses( ApiResponse( responseCode = "200", - description = "로그인 상태이며, 로그인된 회원의 이름 / 권한을 반환합니다.", + description = "입력된 ID / 결과(Boolean)을 반환합니다.", useReturnTypeSchema = true ), ) fun checkLogin( - @MemberId @Parameter(hidden = true) memberId: Long - ): ResponseEntity> + @CurrentUser user: CurrentUserContext + ): ResponseEntity> - @LoginRequired @Operation(summary = "로그아웃", tags = ["로그인이 필요한 API"]) @ApiResponses( - ApiResponse(responseCode = "200", description = "로그아웃 성공시 쿠키에 저장된 토큰 정보를 삭제합니다."), + ApiResponse(responseCode = "200"), ) - fun logout(@MemberId memberId: Long): ResponseEntity> + fun logout( + @CurrentUser user: CurrentUserContext, + servletResponse: HttpServletResponse + ): ResponseEntity> } diff --git a/src/main/kotlin/roomescape/auth/docs/AuthAPIV2.kt b/src/main/kotlin/roomescape/auth/docs/AuthAPIV2.kt deleted file mode 100644 index 9cacbdd1..00000000 --- a/src/main/kotlin/roomescape/auth/docs/AuthAPIV2.kt +++ /dev/null @@ -1,52 +0,0 @@ -package roomescape.auth.docs - -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import jakarta.validation.Valid -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestBody -import roomescape.auth.web.LoginRequestV2 -import roomescape.auth.web.LoginSuccessResponse -import roomescape.auth.web.support.CurrentUser -import roomescape.auth.web.support.Public -import roomescape.common.dto.CurrentUserContext -import roomescape.common.dto.response.CommonApiResponse - -@Tag(name = "1. 인증 / 인가 API", description = "로그인, 로그아웃 및 로그인 상태를 확인합니다") -interface AuthAPIV2 { - - @Public - @Operation(summary = "로그인") - @ApiResponses( - ApiResponse(responseCode = "200", description = "로그인 성공시 토큰을 반환합니다."), - ) - fun login( - @Valid @RequestBody loginRequest: LoginRequestV2, - servletRequest: HttpServletRequest - ): ResponseEntity> - - @Operation(summary = "로그인 상태 확인") - @ApiResponses( - ApiResponse( - responseCode = "200", - description = "입력된 ID / 결과(Boolean)을 반환합니다.", - useReturnTypeSchema = true - ), - ) - fun checkLogin( - @CurrentUser user: CurrentUserContext - ): ResponseEntity> - - @Operation(summary = "로그아웃", tags = ["로그인이 필요한 API"]) - @ApiResponses( - ApiResponse(responseCode = "200"), - ) - fun logout( - @CurrentUser user: CurrentUserContext, - servletResponse: HttpServletResponse - ): ResponseEntity> -} diff --git a/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt b/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt deleted file mode 100644 index 42ef933c..00000000 --- a/src/main/kotlin/roomescape/auth/infrastructure/jwt/JwtHandler.kt +++ /dev/null @@ -1,64 +0,0 @@ -package roomescape.auth.infrastructure.jwt - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import io.jsonwebtoken.ExpiredJwtException -import io.jsonwebtoken.Jwts -import io.jsonwebtoken.security.Keys -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Component -import roomescape.auth.exception.AuthErrorCode -import roomescape.auth.exception.AuthException -import java.util.* -import javax.crypto.SecretKey - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class JwtHandler( - @Value("\${security.jwt.token.secret-key}") - private val secretKeyString: String, - - @Value("\${security.jwt.token.ttl-seconds}") - private val tokenTtlSeconds: Long -) { - private val secretKey: SecretKey = Keys.hmacShaKeyFor(secretKeyString.toByteArray()) - - fun createToken(memberId: Long): String { - log.debug { "[JwtHandler.createToken] 시작: memberId=$memberId" } - val date = Date() - val accessTokenExpiredAt = Date(date.time + (tokenTtlSeconds * 1_000)) - - return Jwts.builder() - .claim(MEMBER_ID_CLAIM_KEY, memberId) - .issuedAt(date) - .expiration(accessTokenExpiredAt) - .signWith(secretKey) - .compact() - .also { log.debug { "[JwtHandler.createToken] 완료. memberId=$memberId, token=$it" } } - } - - fun getMemberIdFromToken(token: String?): Long { - try { - log.debug { "[JwtHandler.getMemberIdFromToken] 시작: token=$token" } - return Jwts.parser() - .verifyWith(secretKey) - .build() - .parseSignedClaims(token) - .payload - .get(MEMBER_ID_CLAIM_KEY, Number::class.java) - .toLong() - .also { log.debug { "[JwtHandler.getMemberIdFromToken] 완료. memberId=$it, token=$token" } } - } catch (_: IllegalArgumentException) { - throw AuthException(AuthErrorCode.TOKEN_NOT_FOUND) - } catch (_: ExpiredJwtException) { - throw AuthException(AuthErrorCode.EXPIRED_TOKEN) - } catch (_: Exception) { - throw AuthException(AuthErrorCode.INVALID_TOKEN) - } - } - - companion object { - private const val MEMBER_ID_CLAIM_KEY = "memberId" - } -} diff --git a/src/main/kotlin/roomescape/auth/web/AuthController.kt b/src/main/kotlin/roomescape/auth/web/AuthController.kt index 5b9184f7..7c6abc44 100644 --- a/src/main/kotlin/roomescape/auth/web/AuthController.kt +++ b/src/main/kotlin/roomescape/auth/web/AuthController.kt @@ -1,44 +1,46 @@ package roomescape.auth.web -import io.swagger.v3.oas.annotations.Parameter -import jakarta.validation.Valid +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController import roomescape.auth.business.AuthService import roomescape.auth.docs.AuthAPI -import roomescape.auth.web.support.MemberId +import roomescape.auth.web.support.CurrentUser +import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse @RestController +@RequestMapping("/auth") class AuthController( - private val authService: AuthService + private val authService: AuthService, ) : AuthAPI { @PostMapping("/login") override fun login( - @Valid @RequestBody loginRequest: LoginRequest, - ): ResponseEntity> { - val response: LoginResponse = authService.login(loginRequest) + loginRequest: LoginRequestV2, + servletRequest: HttpServletRequest + ): ResponseEntity> { + val response = authService.login(request = loginRequest, context = servletRequest.toLoginContext()) return ResponseEntity.ok(CommonApiResponse(response)) } @GetMapping("/login/check") override fun checkLogin( - @MemberId @Parameter(hidden = true) memberId: Long - ): ResponseEntity> { - val response: LoginCheckResponse = authService.checkLogin(memberId) - - return ResponseEntity.ok(CommonApiResponse(response)) + @CurrentUser user: CurrentUserContext, + ): ResponseEntity> { + return ResponseEntity.ok(CommonApiResponse(user)) } @PostMapping("/logout") - override fun logout(@MemberId memberId: Long): ResponseEntity> { - authService.logout(memberId) - - return ResponseEntity.noContent().build() + override fun logout( + @CurrentUser user: CurrentUserContext, + servletResponse: HttpServletResponse + ): ResponseEntity> { + return ResponseEntity.ok().build() } } diff --git a/src/main/kotlin/roomescape/auth/web/AuthControllerV2.kt b/src/main/kotlin/roomescape/auth/web/AuthControllerV2.kt deleted file mode 100644 index d0085688..00000000 --- a/src/main/kotlin/roomescape/auth/web/AuthControllerV2.kt +++ /dev/null @@ -1,46 +0,0 @@ -package roomescape.auth.web - -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import roomescape.auth.business.AuthServiceV2 -import roomescape.auth.docs.AuthAPIV2 -import roomescape.auth.web.support.CurrentUser -import roomescape.common.dto.CurrentUserContext -import roomescape.common.dto.response.CommonApiResponse - -@RestController -@RequestMapping("/auth") -class AuthControllerV2( - private val authService: AuthServiceV2, -) : AuthAPIV2 { - - @PostMapping("/login") - override fun login( - loginRequest: LoginRequestV2, - servletRequest: HttpServletRequest - ): ResponseEntity> { - val response = authService.login(request = loginRequest, context = servletRequest.toLoginContext()) - - return ResponseEntity.ok(CommonApiResponse(response)) - } - - @GetMapping("/login/check") - override fun checkLogin( - @CurrentUser user: CurrentUserContext, - ): ResponseEntity> { - return ResponseEntity.ok(CommonApiResponse(user)) - } - - @PostMapping("/logout") - override fun logout( - @CurrentUser user: CurrentUserContext, - servletResponse: HttpServletResponse - ): ResponseEntity> { - return ResponseEntity.ok().build() - } -} diff --git a/src/main/kotlin/roomescape/auth/web/AuthDTO.kt b/src/main/kotlin/roomescape/auth/web/AuthDTO.kt index f413c439..4bf06274 100644 --- a/src/main/kotlin/roomescape/auth/web/AuthDTO.kt +++ b/src/main/kotlin/roomescape/auth/web/AuthDTO.kt @@ -1,24 +1,24 @@ package roomescape.auth.web -import io.swagger.v3.oas.annotations.media.Schema -import jakarta.validation.constraints.Email -import jakarta.validation.constraints.NotBlank +import jakarta.servlet.http.HttpServletRequest +import roomescape.common.dto.PrincipalType -data class LoginResponse( +data class LoginContext( + val ipAddress: String, + val userAgent: String, +) + +fun HttpServletRequest.toLoginContext() = LoginContext( + ipAddress = this.remoteAddr, + userAgent = this.getHeader("User-Agent") +) + +data class LoginRequestV2( + val account: String, + val password: String, + val principalType: PrincipalType +) + +data class LoginSuccessResponse( val accessToken: String ) - -data class LoginCheckResponse( - @Schema(description = "로그인된 회원의 이름") - val name: String, - @Schema(description = "회원(MEMBER) / 관리자(ADMIN)") - val role: String, -) - -data class LoginRequest( - @Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com") - val email: String, - - @NotBlank(message = "비밀번호는 공백일 수 없습니다.") - val password: String -) diff --git a/src/main/kotlin/roomescape/auth/web/AuthDTOV2.kt b/src/main/kotlin/roomescape/auth/web/AuthDTOV2.kt deleted file mode 100644 index 4bf06274..00000000 --- a/src/main/kotlin/roomescape/auth/web/AuthDTOV2.kt +++ /dev/null @@ -1,24 +0,0 @@ -package roomescape.auth.web - -import jakarta.servlet.http.HttpServletRequest -import roomescape.common.dto.PrincipalType - -data class LoginContext( - val ipAddress: String, - val userAgent: String, -) - -fun HttpServletRequest.toLoginContext() = LoginContext( - ipAddress = this.remoteAddr, - userAgent = this.getHeader("User-Agent") -) - -data class LoginRequestV2( - val account: String, - val password: String, - val principalType: PrincipalType -) - -data class LoginSuccessResponse( - val accessToken: String -) diff --git a/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt b/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt index 5cc61d1e..7d18db7a 100644 --- a/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt +++ b/src/main/kotlin/roomescape/auth/web/support/AuthAnnotations.kt @@ -2,18 +2,6 @@ package roomescape.auth.web.support import roomescape.admin.infrastructure.persistence.Privilege -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class Admin - -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class LoginRequired - -@Target(AnnotationTarget.VALUE_PARAMETER) -@Retention(AnnotationRetention.RUNTIME) -annotation class MemberId - @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class AdminOnly( diff --git a/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt b/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt deleted file mode 100644 index 6901c384..00000000 --- a/src/main/kotlin/roomescape/auth/web/support/AuthInterceptor.kt +++ /dev/null @@ -1,63 +0,0 @@ -package roomescape.auth.web.support - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import jakarta.servlet.http.HttpServletRequest -import jakarta.servlet.http.HttpServletResponse -import org.slf4j.MDC -import org.springframework.stereotype.Component -import org.springframework.web.method.HandlerMethod -import org.springframework.web.servlet.HandlerInterceptor -import roomescape.auth.exception.AuthErrorCode -import roomescape.auth.exception.AuthException -import roomescape.auth.infrastructure.jwt.JwtHandler -import roomescape.member.implement.MemberFinder -import roomescape.member.infrastructure.persistence.MemberEntity - -private val log: KLogger = KotlinLogging.logger {} - -const val MDC_MEMBER_ID_KEY: String = "member_id" - -@Component -class AuthInterceptor( - private val memberFinder: MemberFinder, - private val jwtHandler: JwtHandler -) : HandlerInterceptor { - override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { - if (handler !is HandlerMethod) { - return true - } - - val loginRequired = handler.getMethodAnnotation(LoginRequired::class.java) - val admin = handler.getMethodAnnotation(Admin::class.java) - - if (loginRequired == null && admin == null) { - return true - } - - val accessToken: String? = request.accessToken() - log.info { "[AuthInterceptor] 인증 시작. accessToken=${accessToken}" } - val member: MemberEntity = findMember(accessToken) - - if (admin != null && !member.isAdmin()) { - log.info { "[AuthInterceptor] 관리자 인증 실패. memberId=${member.id}, role=${member.role}" } - throw AuthException(AuthErrorCode.ACCESS_DENIED) - } - - MDC.put(MDC_MEMBER_ID_KEY, "${member.id}") - log.info { "[AuthInterceptor] 인증 완료. memberId=${member.id}, role=${member.role}" } - return true - } - - private fun findMember(accessToken: String?): MemberEntity { - try { - val memberId = jwtHandler.getMemberIdFromToken(accessToken) - return memberFinder.findById(memberId) - .also { MDC.put(MDC_MEMBER_ID_KEY, "$memberId") } - } catch (e: Exception) { - log.info { "[AuthInterceptor] 회원 조회 실패. accessToken = $accessToken" } - val errorCode = AuthErrorCode.MEMBER_NOT_FOUND - throw AuthException(errorCode, e.message ?: errorCode.message) - } - } -} diff --git a/src/main/kotlin/roomescape/auth/web/support/MemberIdResolver.kt b/src/main/kotlin/roomescape/auth/web/support/MemberIdResolver.kt deleted file mode 100644 index 2731f5d9..00000000 --- a/src/main/kotlin/roomescape/auth/web/support/MemberIdResolver.kt +++ /dev/null @@ -1,46 +0,0 @@ -package roomescape.auth.web.support - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import jakarta.servlet.http.HttpServletRequest -import org.slf4j.MDC -import org.springframework.core.MethodParameter -import org.springframework.stereotype.Component -import org.springframework.web.bind.support.WebDataBinderFactory -import org.springframework.web.context.request.NativeWebRequest -import org.springframework.web.method.support.HandlerMethodArgumentResolver -import org.springframework.web.method.support.ModelAndViewContainer -import roomescape.auth.exception.AuthErrorCode -import roomescape.auth.exception.AuthException -import roomescape.auth.infrastructure.jwt.JwtHandler - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class MemberIdResolver( - private val jwtHandler: JwtHandler -) : HandlerMethodArgumentResolver { - - override fun supportsParameter(parameter: MethodParameter): Boolean { - return parameter.hasParameterAnnotation(MemberId::class.java) - } - - override fun resolveArgument( - parameter: MethodParameter, - mavContainer: ModelAndViewContainer?, - webRequest: NativeWebRequest, - binderFactory: WebDataBinderFactory? - ): Any { - val request: HttpServletRequest = webRequest.nativeRequest as HttpServletRequest - val token: String? = request.accessToken() - - try { - return jwtHandler.getMemberIdFromToken(token) - .also { MDC.put("member_id", "$it") } - } catch (e: Exception) { - log.info { "[MemberIdResolver] 회원 조회 실패. message=${e.message}" } - val errorCode = AuthErrorCode.MEMBER_NOT_FOUND - throw AuthException(errorCode, e.message ?: errorCode.message) - } - } -} diff --git a/src/main/kotlin/roomescape/auth/web/support/interceptors/AuthenticatedInterceptor.kt b/src/main/kotlin/roomescape/auth/web/support/interceptors/AuthenticatedInterceptor.kt index 90ee6b45..f69629bc 100644 --- a/src/main/kotlin/roomescape/auth/web/support/interceptors/AuthenticatedInterceptor.kt +++ b/src/main/kotlin/roomescape/auth/web/support/interceptors/AuthenticatedInterceptor.kt @@ -7,7 +7,7 @@ import jakarta.servlet.http.HttpServletResponse import org.springframework.stereotype.Component import org.springframework.web.method.HandlerMethod import org.springframework.web.servlet.HandlerInterceptor -import roomescape.auth.business.AuthServiceV2 +import roomescape.auth.business.AuthService import roomescape.auth.infrastructure.jwt.JwtUtils import roomescape.auth.web.support.Authenticated import roomescape.auth.web.support.accessToken @@ -17,7 +17,7 @@ private val log: KLogger = KotlinLogging.logger {} @Component class AuthenticatedInterceptor( private val jwtUtils: JwtUtils, - private val authService: AuthServiceV2 + private val authService: AuthService ) : HandlerInterceptor { override fun preHandle( diff --git a/src/main/kotlin/roomescape/auth/web/support/resolver/CurrentUserContextResolver.kt b/src/main/kotlin/roomescape/auth/web/support/resolver/CurrentUserContextResolver.kt index a4ca2652..5ee2285f 100644 --- a/src/main/kotlin/roomescape/auth/web/support/resolver/CurrentUserContextResolver.kt +++ b/src/main/kotlin/roomescape/auth/web/support/resolver/CurrentUserContextResolver.kt @@ -9,7 +9,7 @@ import org.springframework.web.bind.support.WebDataBinderFactory import org.springframework.web.context.request.NativeWebRequest import org.springframework.web.method.support.HandlerMethodArgumentResolver import org.springframework.web.method.support.ModelAndViewContainer -import roomescape.auth.business.AuthServiceV2 +import roomescape.auth.business.AuthService import roomescape.auth.exception.AuthErrorCode import roomescape.auth.exception.AuthException import roomescape.auth.infrastructure.jwt.JwtUtils @@ -21,7 +21,7 @@ private val log: KLogger = KotlinLogging.logger {} @Component class CurrentUserContextResolver( private val jwtUtils: JwtUtils, - private val authService: AuthServiceV2 + private val authService: AuthService ) : HandlerMethodArgumentResolver { override fun supportsParameter(parameter: MethodParameter): Boolean { diff --git a/src/main/kotlin/roomescape/common/config/WebMvcConfig.kt b/src/main/kotlin/roomescape/common/config/WebMvcConfig.kt index 9d744670..6ee4e748 100644 --- a/src/main/kotlin/roomescape/common/config/WebMvcConfig.kt +++ b/src/main/kotlin/roomescape/common/config/WebMvcConfig.kt @@ -4,8 +4,6 @@ import org.springframework.context.annotation.Configuration import org.springframework.web.method.support.HandlerMethodArgumentResolver import org.springframework.web.servlet.config.annotation.InterceptorRegistry import org.springframework.web.servlet.config.annotation.WebMvcConfigurer -import roomescape.auth.web.support.AuthInterceptor -import roomescape.auth.web.support.MemberIdResolver import roomescape.auth.web.support.interceptors.AdminInterceptor import roomescape.auth.web.support.interceptors.AuthenticatedInterceptor import roomescape.auth.web.support.interceptors.UserInterceptor @@ -13,8 +11,6 @@ import roomescape.auth.web.support.resolver.CurrentUserContextResolver @Configuration class WebMvcConfig( - private val memberIdResolver: MemberIdResolver, - private val authInterceptor: AuthInterceptor, private val adminInterceptor: AdminInterceptor, private val userInterceptor: UserInterceptor, private val authenticatedInterceptor: AuthenticatedInterceptor, @@ -22,12 +18,10 @@ class WebMvcConfig( ) : WebMvcConfigurer { override fun addArgumentResolvers(resolvers: MutableList) { - resolvers.add(memberIdResolver) resolvers.add(currentUserContextResolver) } override fun addInterceptors(registry: InterceptorRegistry) { - registry.addInterceptor(authInterceptor) registry.addInterceptor(adminInterceptor) registry.addInterceptor(userInterceptor) registry.addInterceptor(authenticatedInterceptor) diff --git a/src/main/kotlin/roomescape/common/entity/BaseEntity.kt b/src/main/kotlin/roomescape/common/entity/BaseEntity.kt index 3c821aa3..707d2249 100644 --- a/src/main/kotlin/roomescape/common/entity/BaseEntity.kt +++ b/src/main/kotlin/roomescape/common/entity/BaseEntity.kt @@ -1,7 +1,9 @@ package roomescape.common.entity import jakarta.persistence.* +import org.springframework.data.annotation.CreatedBy import org.springframework.data.annotation.CreatedDate +import org.springframework.data.annotation.LastModifiedBy import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.domain.Persistable import org.springframework.data.jpa.domain.support.AuditingEntityListener @@ -10,28 +12,24 @@ import kotlin.jvm.Transient @MappedSuperclass @EntityListeners(AuditingEntityListener::class) -abstract class BaseEntity( +abstract class AuditingBaseEntity( + id: Long, +) : PersistableBaseEntity(id) { @Column(updatable = false) @CreatedDate - var createdAt: LocalDateTime? = null, + lateinit var createdAt: LocalDateTime + @Column(updatable = false) + @CreatedBy + var createdBy: Long = 0L + + @Column @LastModifiedDate - var lastModifiedAt: LocalDateTime? = null, -) : Persistable { - - @Transient - private var isNewEntity: Boolean = true - - @PostLoad - @PostPersist - fun markNotNew() { - isNewEntity = false - } - - override fun isNew(): Boolean = isNewEntity - - abstract override fun getId(): Long? + lateinit var updatedAt: LocalDateTime + @Column + @LastModifiedBy + var updatedBy: Long = 0L } @MappedSuperclass @@ -43,12 +41,13 @@ abstract class PersistableBaseEntity( @Transient private var isNewEntity: Boolean = true ) : Persistable { + @PostLoad - @PostPersist + @PrePersist fun markNotNew() { isNewEntity = false } - override fun isNew(): Boolean = isNewEntity override fun getId(): Long = _id + override fun isNew(): Boolean = isNewEntity } diff --git a/src/main/kotlin/roomescape/common/entity/BaseEntityV2.kt b/src/main/kotlin/roomescape/common/entity/BaseEntityV2.kt deleted file mode 100644 index 021f4730..00000000 --- a/src/main/kotlin/roomescape/common/entity/BaseEntityV2.kt +++ /dev/null @@ -1,53 +0,0 @@ -package roomescape.common.entity - -import jakarta.persistence.* -import org.springframework.data.annotation.CreatedBy -import org.springframework.data.annotation.CreatedDate -import org.springframework.data.annotation.LastModifiedBy -import org.springframework.data.annotation.LastModifiedDate -import org.springframework.data.domain.Persistable -import org.springframework.data.jpa.domain.support.AuditingEntityListener -import java.time.LocalDateTime -import kotlin.jvm.Transient - -@MappedSuperclass -@EntityListeners(AuditingEntityListener::class) -abstract class AuditingBaseEntity( - id: Long, -) : BaseEntityV2(id) { - @Column(updatable = false) - @CreatedDate - lateinit var createdAt: LocalDateTime - - @Column(updatable = false) - @CreatedBy - var createdBy: Long = 0L - - @Column - @LastModifiedDate - lateinit var updatedAt: LocalDateTime - - @Column - @LastModifiedBy - var updatedBy: Long = 0L -} - -@MappedSuperclass -abstract class BaseEntityV2( - @Id - @Column(name = "id") - private val _id: Long, - - @Transient - private var isNewEntity: Boolean = true -) : Persistable { - - @PostLoad - @PrePersist - fun markNotNew() { - isNewEntity = false - } - - override fun getId(): Long = _id - override fun isNew(): Boolean = isNewEntity -} diff --git a/src/main/kotlin/roomescape/member/business/MemberService.kt b/src/main/kotlin/roomescape/member/business/MemberService.kt deleted file mode 100644 index 7df26417..00000000 --- a/src/main/kotlin/roomescape/member/business/MemberService.kt +++ /dev/null @@ -1,46 +0,0 @@ -package roomescape.member.business - -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional -import roomescape.member.implement.MemberFinder -import roomescape.member.implement.MemberWriter -import roomescape.member.infrastructure.persistence.Role -import roomescape.member.web.* - -private val log = KotlinLogging.logger {} - -@Service -class MemberService( - private val memberWriter: MemberWriter, - private val memberFinder: MemberFinder, -) { - @Transactional(readOnly = true) - fun findMembers(): MemberRetrieveListResponse { - log.debug { "[MemberService.findMembers] 시작" } - - return memberFinder.findAll() - .toRetrieveListResponse() - .also { log.info { "[MemberService.findMembers] 완료. ${it.members.size}명 반환" } } - } - - @Transactional(readOnly = true) - fun findSummaryById(id: Long): MemberSummaryRetrieveResponse { - log.debug { "[MemberService.findSummaryById] 시작" } - - return memberFinder.findById(id) - .toSummaryRetrieveResponse() - .also { - log.info { "[MemberService.findSummaryById] 완료. memberId=${id}, email=${it.email}" } - } - } - - @Transactional - fun createMember(request: SignupRequest): SignupResponse { - log.debug { "[MemberService.createMember] 시작" } - - return memberWriter.create(request.name, request.email, request.password, Role.MEMBER) - .toSignupResponse() - .also { log.info { "[MemberService.create] 완료: email=${request.email} memberId=${it.id}" } } - } -} diff --git a/src/main/kotlin/roomescape/member/docs/MemberAPI.kt b/src/main/kotlin/roomescape/member/docs/MemberAPI.kt deleted file mode 100644 index 36ebfa7a..00000000 --- a/src/main/kotlin/roomescape/member/docs/MemberAPI.kt +++ /dev/null @@ -1,37 +0,0 @@ -package roomescape.member.docs - -import io.swagger.v3.oas.annotations.Operation -import io.swagger.v3.oas.annotations.responses.ApiResponse -import io.swagger.v3.oas.annotations.responses.ApiResponses -import io.swagger.v3.oas.annotations.tags.Tag -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.RequestBody -import roomescape.auth.web.support.Admin -import roomescape.common.dto.response.CommonApiResponse -import roomescape.member.web.MemberRetrieveListResponse -import roomescape.member.web.SignupRequest -import roomescape.member.web.SignupResponse - -@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.") -interface MemberAPI { - @Admin - @Operation(summary = "모든 회원 조회", tags = ["관리자 로그인이 필요한 API"]) - @ApiResponses( - ApiResponse( - responseCode = "200", - description = "성공", - useReturnTypeSchema = true - ) - ) - fun findMembers(): ResponseEntity> - - @Operation(summary = "회원 가입") - @ApiResponses( - ApiResponse( - responseCode = "201", - description = "성공", - useReturnTypeSchema = true - ) - ) - fun signup(@RequestBody request: SignupRequest): ResponseEntity> -} diff --git a/src/main/kotlin/roomescape/member/exception/MemberErrorCode.kt b/src/main/kotlin/roomescape/member/exception/MemberErrorCode.kt deleted file mode 100644 index 8185ef5b..00000000 --- a/src/main/kotlin/roomescape/member/exception/MemberErrorCode.kt +++ /dev/null @@ -1,13 +0,0 @@ -package roomescape.member.exception - -import org.springframework.http.HttpStatus -import roomescape.common.exception.ErrorCode - -enum class MemberErrorCode( - override val httpStatus: HttpStatus, - override val errorCode: String, - override val message: String -) : ErrorCode { - MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "M001", "회원을 찾을 수 없어요."), - DUPLICATE_EMAIL(HttpStatus.CONFLICT, "M002", "이미 가입된 이메일이에요.") -} diff --git a/src/main/kotlin/roomescape/member/exception/MemberException.kt b/src/main/kotlin/roomescape/member/exception/MemberException.kt deleted file mode 100644 index 2d82be5a..00000000 --- a/src/main/kotlin/roomescape/member/exception/MemberException.kt +++ /dev/null @@ -1,8 +0,0 @@ -package roomescape.member.exception - -import roomescape.common.exception.RoomescapeException - -class MemberException( - override val errorCode: MemberErrorCode, - override val message: String = errorCode.message -) : RoomescapeException(errorCode, message) diff --git a/src/main/kotlin/roomescape/member/implement/MemberFinder.kt b/src/main/kotlin/roomescape/member/implement/MemberFinder.kt deleted file mode 100644 index 4c68dfcf..00000000 --- a/src/main/kotlin/roomescape/member/implement/MemberFinder.kt +++ /dev/null @@ -1,47 +0,0 @@ -package roomescape.member.implement - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.data.repository.findByIdOrNull -import org.springframework.stereotype.Component -import roomescape.member.exception.MemberErrorCode -import roomescape.member.exception.MemberException -import roomescape.member.infrastructure.persistence.MemberEntity -import roomescape.member.infrastructure.persistence.MemberRepository - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class MemberFinder( - private val memberRepository: MemberRepository -) { - - fun findAll(): List { - log.debug { "[MemberFinder.findAll] 회원 조회 시작" } - - return memberRepository.findAll() - .also { log.debug { "[MemberFinder.findAll] 회원 ${it.size}명 조회 완료" } } - } - - fun findById(id: Long): MemberEntity { - log.debug { "[MemberFinder.findById] 조회 시작: memberId=$id" } - - return memberRepository.findByIdOrNull(id) - ?.also { log.debug { "[MemberFinder.findById] 조회 완료: memberId=$id, email=${it.email}" } } - ?: run { - log.info { "[MemberFinder.findById] 조회 실패: id=$id" } - throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND) - } - } - - fun findByEmailAndPassword(email: String, password: String): MemberEntity { - log.debug { "[MemberFinder.findByEmailAndPassword] 조회 시작: email=$email, password=$password" } - - return memberRepository.findByEmailAndPassword(email, password) - ?.also { log.debug { "[MemberFinder.findByEmailAndPassword] 조회 완료: email=${email}, memberId=${it.id}" } } - ?: run { - log.info { "[MemberFinder.findByEmailAndPassword] 조회 실패: email=${email}, password=${password}" } - throw MemberException(MemberErrorCode.MEMBER_NOT_FOUND) - } - } -} diff --git a/src/main/kotlin/roomescape/member/implement/MemberValidator.kt b/src/main/kotlin/roomescape/member/implement/MemberValidator.kt deleted file mode 100644 index 1b3c54d2..00000000 --- a/src/main/kotlin/roomescape/member/implement/MemberValidator.kt +++ /dev/null @@ -1,26 +0,0 @@ -package roomescape.member.implement - -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.stereotype.Component -import roomescape.member.exception.MemberErrorCode -import roomescape.member.exception.MemberException -import roomescape.member.infrastructure.persistence.MemberRepository - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class MemberValidator( - private val memberRepository: MemberRepository -) { - fun validateCanSignup(email: String) { - log.debug { "[MemberValidator.validateCanSignup] 시작: email=$email" } - - if (memberRepository.existsByEmail(email)) { - log.info { "[MemberValidator.validateCanSignup] 중복 이메일: email=$email" } - throw MemberException(MemberErrorCode.DUPLICATE_EMAIL) - } - - log.debug { "[MemberValidator.validateCanSignup] 완료: email=$email" } - } -} diff --git a/src/main/kotlin/roomescape/member/implement/MemberWriter.kt b/src/main/kotlin/roomescape/member/implement/MemberWriter.kt deleted file mode 100644 index 34a7b7b9..00000000 --- a/src/main/kotlin/roomescape/member/implement/MemberWriter.kt +++ /dev/null @@ -1,35 +0,0 @@ -package roomescape.member.implement - -import com.github.f4b6a3.tsid.TsidFactory -import io.github.oshai.kotlinlogging.KLogger -import io.github.oshai.kotlinlogging.KotlinLogging -import org.springframework.stereotype.Component -import roomescape.common.config.next -import roomescape.member.infrastructure.persistence.MemberEntity -import roomescape.member.infrastructure.persistence.MemberRepository -import roomescape.member.infrastructure.persistence.Role - -private val log: KLogger = KotlinLogging.logger {} - -@Component -class MemberWriter( - private val tsidFactory: TsidFactory, - private val memberValidator: MemberValidator, - private val memberRepository: MemberRepository -) { - fun create(name: String, email: String, password: String, role: Role): MemberEntity { - log.debug { "[MemberWriter.create] 시작: email=$email" } - memberValidator.validateCanSignup(email) - - val member = MemberEntity( - _id = tsidFactory.next(), - name = name, - email = email, - password = password, - role = role - ) - - return memberRepository.save(member) - .also { log.debug { "[MemberWriter.create] 완료: email=$email, memberId=${it.id}" } } - } -} diff --git a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt deleted file mode 100644 index 2b287722..00000000 --- a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberEntity.kt +++ /dev/null @@ -1,34 +0,0 @@ -package roomescape.member.infrastructure.persistence - -import jakarta.persistence.* -import roomescape.common.entity.BaseEntity - -@Entity -@Table(name = "members") -class MemberEntity( - @Id - @Column(name = "member_id") - private var _id: Long?, - - @Column(name = "name", nullable = false) - var name: String, - - @Column(name = "email", nullable = false) - var email: String, - - @Column(name = "password", nullable = false) - var password: String, - - @Column(name = "role", nullable = false, length = 20) - @Enumerated(value = EnumType.STRING) - var role: Role -) : BaseEntity() { - override fun getId(): Long? = _id - - fun isAdmin(): Boolean = role == Role.ADMIN -} - -enum class Role { - MEMBER, - ADMIN, -} diff --git a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberRepository.kt b/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberRepository.kt deleted file mode 100644 index 5b5fd828..00000000 --- a/src/main/kotlin/roomescape/member/infrastructure/persistence/MemberRepository.kt +++ /dev/null @@ -1,9 +0,0 @@ -package roomescape.member.infrastructure.persistence - -import org.springframework.data.jpa.repository.JpaRepository - -interface MemberRepository : JpaRepository { - fun existsByEmail(email: String): Boolean - - fun findByEmailAndPassword(email: String, password: String): MemberEntity? -} diff --git a/src/main/kotlin/roomescape/member/web/MemberController.kt b/src/main/kotlin/roomescape/member/web/MemberController.kt deleted file mode 100644 index 00859ca2..00000000 --- a/src/main/kotlin/roomescape/member/web/MemberController.kt +++ /dev/null @@ -1,31 +0,0 @@ -package roomescape.member.web - -import org.springframework.http.ResponseEntity -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PostMapping -import org.springframework.web.bind.annotation.RequestBody -import org.springframework.web.bind.annotation.RestController -import roomescape.common.dto.response.CommonApiResponse -import roomescape.member.business.MemberService -import roomescape.member.docs.MemberAPI -import java.net.URI - -@RestController -class MemberController( - private val memberService: MemberService -) : MemberAPI { - - @PostMapping("/members") - override fun signup(@RequestBody request: SignupRequest): ResponseEntity> { - val response: SignupResponse = memberService.createMember(request) - return ResponseEntity.created(URI.create("/members/${response.id}")) - .body(CommonApiResponse(response)) - } - - @GetMapping("/members") - override fun findMembers(): ResponseEntity> { - val response: MemberRetrieveListResponse = memberService.findMembers() - - return ResponseEntity.ok(CommonApiResponse(response)) - } -} diff --git a/src/main/kotlin/roomescape/member/web/MemberDTO.kt b/src/main/kotlin/roomescape/member/web/MemberDTO.kt deleted file mode 100644 index c1a77927..00000000 --- a/src/main/kotlin/roomescape/member/web/MemberDTO.kt +++ /dev/null @@ -1,56 +0,0 @@ -package roomescape.member.web - -import io.swagger.v3.oas.annotations.media.Schema -import roomescape.member.infrastructure.persistence.MemberEntity -import roomescape.member.infrastructure.persistence.Role - -fun MemberEntity.toRetrieveResponse(): MemberRetrieveResponse = MemberRetrieveResponse( - id = id!!, - name = name -) - -data class MemberRetrieveResponse( - @Schema(description = "회원 식별자") - val id: Long, - - @Schema(description = "회원 이름") - val name: String -) - -fun List.toRetrieveListResponse(): MemberRetrieveListResponse = MemberRetrieveListResponse( - members = this.map { it.toRetrieveResponse() } -) - -data class MemberRetrieveListResponse( - val members: List -) - -data class SignupRequest( - val email: String, - val password: String, - val name: String -) - -data class SignupResponse( - val id: Long, - val name: String, -) - -fun MemberEntity.toSignupResponse(): SignupResponse = SignupResponse( - id = this.id!!, - name = this.name -) - -data class MemberSummaryRetrieveResponse( - val id: Long, - val name: String, - val email: String, - val role: Role -) - -fun MemberEntity.toSummaryRetrieveResponse() = MemberSummaryRetrieveResponse( - id = this.id!!, - name = this.name, - email = this.email, - role = this.role -) diff --git a/src/main/kotlin/roomescape/payment/web/PaymentController.kt b/src/main/kotlin/roomescape/payment/web/PaymentController.kt index 3923a39c..3edc2293 100644 --- a/src/main/kotlin/roomescape/payment/web/PaymentController.kt +++ b/src/main/kotlin/roomescape/payment/web/PaymentController.kt @@ -1,6 +1,5 @@ package roomescape.payment.web -import io.swagger.v3.oas.annotations.Parameter import jakarta.validation.Valid import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PostMapping @@ -8,7 +7,6 @@ import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import roomescape.auth.web.support.CurrentUser -import roomescape.auth.web.support.MemberId import roomescape.common.dto.CurrentUserContext import roomescape.common.dto.response.CommonApiResponse import roomescape.payment.business.PaymentService diff --git a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt index 7d8f7d0b..84ac475e 100644 --- a/src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt +++ b/src/main/kotlin/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt @@ -4,7 +4,7 @@ import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Table -import roomescape.common.entity.BaseEntityV2 +import roomescape.common.entity.PersistableBaseEntity import java.time.LocalDateTime @Entity @@ -19,8 +19,7 @@ class CanceledReservationEntity( @Enumerated(value = EnumType.STRING) val status: CanceledReservationStatus, - - ) : BaseEntityV2(id) +) : PersistableBaseEntity(id) enum class CanceledReservationStatus { PROCESSING, FAILED, COMPLETED diff --git a/src/main/kotlin/roomescape/schedule/business/ScheduleService.kt b/src/main/kotlin/roomescape/schedule/business/ScheduleService.kt index 9c5efbb5..7328635b 100644 --- a/src/main/kotlin/roomescape/schedule/business/ScheduleService.kt +++ b/src/main/kotlin/roomescape/schedule/business/ScheduleService.kt @@ -9,7 +9,6 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import roomescape.admin.business.AdminService import roomescape.common.config.next -import roomescape.member.business.MemberService import roomescape.schedule.exception.ScheduleErrorCode import roomescape.schedule.infrastructure.persistence.ScheduleEntity import roomescape.schedule.infrastructure.persistence.ScheduleRepository diff --git a/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt b/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt index 1ae6df4b..69a0816e 100644 --- a/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt +++ b/src/main/kotlin/roomescape/schedule/docs/ScheduleAPI.kt @@ -4,16 +4,13 @@ import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.responses.ApiResponses import jakarta.validation.Valid -import org.aspectj.internal.lang.annotation.ajcPrivileged import org.springframework.format.annotation.DateTimeFormat import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestParam import roomescape.admin.infrastructure.persistence.Privilege -import roomescape.auth.web.support.Admin import roomescape.auth.web.support.AdminOnly -import roomescape.auth.web.support.LoginRequired import roomescape.auth.web.support.UserOnly import roomescape.common.dto.response.CommonApiResponse import roomescape.schedule.web.* diff --git a/src/main/kotlin/roomescape/theme/business/ThemeService.kt b/src/main/kotlin/roomescape/theme/business/ThemeService.kt index 56ae0271..66778111 100644 --- a/src/main/kotlin/roomescape/theme/business/ThemeService.kt +++ b/src/main/kotlin/roomescape/theme/business/ThemeService.kt @@ -8,7 +8,6 @@ import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import roomescape.admin.business.AdminService import roomescape.common.config.next -import roomescape.member.business.MemberService import roomescape.theme.exception.ThemeErrorCode import roomescape.theme.exception.ThemeException import roomescape.theme.infrastructure.persistence.ThemeEntity diff --git a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt index 36587af1..d07cc8ce 100644 --- a/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt +++ b/src/main/kotlin/roomescape/theme/docs/ThemeApi.kt @@ -9,9 +9,7 @@ import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody import roomescape.admin.infrastructure.persistence.Privilege -import roomescape.auth.web.support.Admin import roomescape.auth.web.support.AdminOnly -import roomescape.auth.web.support.LoginRequired import roomescape.auth.web.support.UserOnly import roomescape.common.dto.response.CommonApiResponse import roomescape.theme.web.* diff --git a/src/main/resources/schema/schema-h2.sql b/src/main/resources/schema/schema-h2.sql index 874e840b..9897fffc 100644 --- a/src/main/resources/schema/schema-h2.sql +++ b/src/main/resources/schema/schema-h2.sql @@ -8,16 +8,6 @@ create table if not exists region ( dong_name varchar(20) not null ); -create table if not exists members ( - member_id bigint primary key, - email varchar(255) not null, - name varchar(255) not null, - password varchar(255) not null, - role varchar(20) not null, - created_at timestamp, - last_modified_at timestamp -); - create table if not exists users( id bigint primary key, name varchar(50) not null, diff --git a/src/test/kotlin/roomescape/payment/PaymentAPITest.kt b/src/test/kotlin/roomescape/payment/PaymentAPITest.kt index d22c1e61..ff60aaed 100644 --- a/src/test/kotlin/roomescape/payment/PaymentAPITest.kt +++ b/src/test/kotlin/roomescape/payment/PaymentAPITest.kt @@ -18,11 +18,7 @@ import roomescape.payment.infrastructure.persistence.* import roomescape.payment.web.PaymentConfirmRequest import roomescape.payment.web.PaymentCreateResponse import roomescape.reservation.infrastructure.persistence.ReservationEntity -import roomescape.util.FunSpecSpringbootTest -import roomescape.util.INVALID_PK -import roomescape.util.PaymentFixture -import roomescape.util.runExceptionTest -import roomescape.util.runTest +import roomescape.util.* class PaymentAPITest( @MockkBean diff --git a/src/test/kotlin/roomescape/util/Fixtures.kt b/src/test/kotlin/roomescape/util/Fixtures.kt index cd99607d..45ff3c10 100644 --- a/src/test/kotlin/roomescape/util/Fixtures.kt +++ b/src/test/kotlin/roomescape/util/Fixtures.kt @@ -4,8 +4,6 @@ import com.github.f4b6a3.tsid.TsidFactory import roomescape.admin.infrastructure.persistence.AdminEntity import roomescape.admin.infrastructure.persistence.AdminPermissionLevel import roomescape.common.config.next -import roomescape.member.infrastructure.persistence.MemberEntity -import roomescape.member.infrastructure.persistence.Role import roomescape.member.infrastructure.persistence.UserEntity import roomescape.member.infrastructure.persistence.UserStatus import roomescape.member.web.MIN_PASSWORD_LENGTH @@ -25,24 +23,6 @@ import java.time.OffsetDateTime const val INVALID_PK: Long = 9999L val tsidFactory = TsidFactory(0) -object MemberFixture { - val admin: MemberEntity = MemberEntity( - _id = 9304, - name = "ADMIN", - email = "admin@example.com", - password = "adminPassword", - role = Role.ADMIN - ) - - val user: MemberEntity = MemberEntity( - _id = 9305, - name = "USER", - email = "user@example.com", - password = "userPassword", - role = Role.MEMBER - ) -} - object AdminFixture { val default: AdminEntity = create() diff --git a/src/test/kotlin/roomescape/util/KotestConfig.kt b/src/test/kotlin/roomescape/util/KotestConfig.kt index 3c41abab..93a80aa5 100644 --- a/src/test/kotlin/roomescape/util/KotestConfig.kt +++ b/src/test/kotlin/roomescape/util/KotestConfig.kt @@ -14,7 +14,6 @@ import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Import import org.springframework.test.context.ActiveProfiles import roomescape.admin.infrastructure.persistence.AdminRepository -import roomescape.member.infrastructure.persistence.MemberRepository import roomescape.member.infrastructure.persistence.UserRepository import roomescape.payment.business.PaymentWriter import roomescape.payment.infrastructure.persistence.PaymentRepository @@ -32,9 +31,6 @@ object KotestConfig : AbstractProjectConfig() { abstract class FunSpecSpringbootTest : FunSpec({ extension(DatabaseCleanerExtension()) }) { - @Autowired - private lateinit var memberRepository: MemberRepository - @Autowired private lateinit var userRepository: UserRepository @@ -51,7 +47,7 @@ abstract class FunSpecSpringbootTest : FunSpec({ override suspend fun beforeSpec(spec: Spec) { RestAssured.port = port - authUtil = AuthUtil(memberRepository, userRepository, adminRepository) + authUtil = AuthUtil(userRepository, adminRepository) } } diff --git a/src/test/kotlin/roomescape/util/RestAssuredUtils.kt b/src/test/kotlin/roomescape/util/RestAssuredUtils.kt index c7cf5cf7..d2b43ac7 100644 --- a/src/test/kotlin/roomescape/util/RestAssuredUtils.kt +++ b/src/test/kotlin/roomescape/util/RestAssuredUtils.kt @@ -14,57 +14,17 @@ import org.springframework.http.HttpStatus import org.springframework.http.MediaType import roomescape.admin.infrastructure.persistence.AdminEntity import roomescape.admin.infrastructure.persistence.AdminRepository -import roomescape.auth.web.LoginRequest import roomescape.auth.web.LoginRequestV2 -import roomescape.common.config.next import roomescape.common.dto.PrincipalType import roomescape.common.exception.ErrorCode -import roomescape.member.infrastructure.persistence.* +import roomescape.member.infrastructure.persistence.UserEntity +import roomescape.member.infrastructure.persistence.UserRepository import roomescape.member.web.UserCreateRequest class AuthUtil( - private val memberRepository: MemberRepository, private val userRepository: UserRepository, private val adminRepository: AdminRepository ) { - fun login(email: String, password: String, role: Role = Role.MEMBER): String { - if (!memberRepository.existsByEmail(email)) { - memberRepository.save( - MemberEntity( - _id = tsidFactory.next(), - email = email, - password = password, - name = email.split("@").first(), - role = role - ) - ) - } - - return Given { - contentType(MediaType.APPLICATION_JSON_VALUE) - body(LoginRequest(email, password)) - } When { - post("/login") - } Then { - statusCode(200) - } Extract { - path("data.accessToken") - } - } - - fun loginAsAdmin(): String { - return login(MemberFixture.admin.email, MemberFixture.admin.password, Role.ADMIN) - } - - fun loginAsUser(): String { - return login(MemberFixture.user.email, MemberFixture.user.password) - } - - fun getUser(): MemberEntity = memberRepository.findByEmailAndPassword( - MemberFixture.user.email, - MemberFixture.user.password - ) ?: throw AssertionError("Unexpected Exception Occurred.") - fun createAdmin(admin: AdminEntity): AdminEntity { return adminRepository.save(admin) }