diff --git a/api/src/main/kotlin/handler/UserHandler.kt b/api/src/main/kotlin/handler/UserHandler.kt index 45304c25..9f95d62d 100644 --- a/api/src/main/kotlin/handler/UserHandler.kt +++ b/api/src/main/kotlin/handler/UserHandler.kt @@ -123,6 +123,13 @@ class UserHandler( userService.attachSocial(user, socialLoginRequest, AuthProvider.KAKAO) } + suspend fun attachApple(req: ServerRequest): ServerResponse = + handle(req) { + val user = req.getContext().user!! + val socialLoginRequest: SocialLoginRequest = req.awaitBody() + userService.attachSocial(user, socialLoginRequest, AuthProvider.APPLE) + } + suspend fun detachFacebook(req: ServerRequest): ServerResponse = handle(req) { val user = req.getContext().user!! @@ -141,6 +148,12 @@ class UserHandler( userService.detachSocial(user, AuthProvider.KAKAO) } + suspend fun detachApple(req: ServerRequest): ServerResponse = + handle(req) { + val user = req.getContext().user!! + userService.detachSocial(user, AuthProvider.APPLE) + } + suspend fun checkAuthProviders(req: ServerRequest): ServerResponse = handle(req) { val user = req.getContext().user!! diff --git a/api/src/main/kotlin/router/MainRouter.kt b/api/src/main/kotlin/router/MainRouter.kt index e9bd14d5..c69606fe 100644 --- a/api/src/main/kotlin/router/MainRouter.kt +++ b/api/src/main/kotlin/router/MainRouter.kt @@ -114,9 +114,11 @@ class MainRouter( POST("/facebook", userHandler::attachFacebook) POST("/google", userHandler::attachGoogle) POST("/kakao", userHandler::attachKakao) + POST("/apple", userHandler::attachApple) DELETE("/facebook", userHandler::detachFacebook) DELETE("/google", userHandler::detachGoogle) DELETE("/kakao", userHandler::detachKakao) + DELETE("/apple", userHandler::detachApple) } "/users".nest { GET("/me", userHandler::getUserMe) diff --git a/api/src/main/kotlin/router/docs/UserDocs.kt b/api/src/main/kotlin/router/docs/UserDocs.kt index 59801e29..b5b28cf0 100644 --- a/api/src/main/kotlin/router/docs/UserDocs.kt +++ b/api/src/main/kotlin/router/docs/UserDocs.kt @@ -319,6 +319,30 @@ import org.springframework.web.bind.annotation.RequestMethod ], ), ), + RouterOperation( + path = "/v1/user/apple", + method = [RequestMethod.POST], + produces = [MediaType.APPLICATION_JSON_VALUE], + operation = + Operation( + operationId = "attachApple", + requestBody = + RequestBody( + content = [ + Content( + schema = Schema(implementation = SocialLoginRequest::class), + mediaType = MediaType.APPLICATION_JSON_VALUE, + ), + ], + ), + responses = [ + ApiResponse( + responseCode = "200", + content = [Content(schema = Schema(implementation = TokenResponse::class))], + ), + ], + ), + ), RouterOperation( path = "/v1/user/facebook", method = [RequestMethod.DELETE], @@ -364,5 +388,20 @@ import org.springframework.web.bind.annotation.RequestMethod ], ), ), + RouterOperation( + path = "/v1/user/apple", + method = [RequestMethod.DELETE], + produces = [MediaType.APPLICATION_JSON_VALUE], + operation = + Operation( + operationId = "detachApple", + responses = [ + ApiResponse( + responseCode = "200", + content = [Content(schema = Schema(implementation = TokenResponse::class))], + ), + ], + ), + ), ) annotation class UserDocs diff --git a/core/src/main/kotlin/users/dto/SocialLoginRequest.kt b/core/src/main/kotlin/users/dto/SocialLoginRequest.kt index 8a4d2606..1f15f3b1 100644 --- a/core/src/main/kotlin/users/dto/SocialLoginRequest.kt +++ b/core/src/main/kotlin/users/dto/SocialLoginRequest.kt @@ -3,6 +3,6 @@ package com.wafflestudio.snu4t.users.dto import com.fasterxml.jackson.annotation.JsonAlias data class SocialLoginRequest( - @JsonAlias("fb_token") + @JsonAlias("fb_token", "apple_token") val token: String, ) diff --git a/core/src/main/kotlin/users/repository/UserRepository.kt b/core/src/main/kotlin/users/repository/UserRepository.kt index 5f71cecb..2d3c0509 100644 --- a/core/src/main/kotlin/users/repository/UserRepository.kt +++ b/core/src/main/kotlin/users/repository/UserRepository.kt @@ -40,6 +40,8 @@ interface UserRepository : CoroutineCrudRepository { suspend fun existsByCredentialKakaoSubAndActiveTrue(kakaoSub: String): Boolean + suspend fun existsByCredentialAppleSubAndActiveTrue(appleSub: String): Boolean + fun findAllByNicknameStartingWith(nickname: String): Flow suspend fun findByCredentialAppleTransferSubAndActiveTrue(appleTransferSub: String): User? diff --git a/core/src/main/kotlin/users/service/UserService.kt b/core/src/main/kotlin/users/service/UserService.kt index 4caf4486..6d603f61 100644 --- a/core/src/main/kotlin/users/service/UserService.kt +++ b/core/src/main/kotlin/users/service/UserService.kt @@ -485,7 +485,18 @@ class UserServiceImpl( kakaoEmail = kakaoCredential.kakaoEmail } } - AuthProvider.APPLE -> throw IllegalStateException("Apple login is not supported") + AuthProvider.APPLE -> { + if (user.credential.appleSub != null) throw AlreadySocialAccountException + if (userRepository.existsByCredentialAppleSubAndActiveTrue(oauth2UserResponse.socialId)) { + throw DuplicateSocialAccountException + } + val appleCredential = authService.buildAppleCredential(oauth2UserResponse) + user.credential.apply { + appleSub = appleCredential.appleSub + appleEmail = appleCredential.appleEmail + appleTransferSub = appleCredential.appleTransferSub + } + } AuthProvider.LOCAL -> throw IllegalStateException("Cannot attach local account") }