Skip to content

Commit

Permalink
Merge pull request #279 from wafflestudio/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
Hank-Choi authored Oct 2, 2024
2 parents a73f649 + ef909e2 commit cc7a193
Show file tree
Hide file tree
Showing 8 changed files with 326 additions and 12 deletions.
55 changes: 55 additions & 0 deletions api/src/main/kotlin/handler/UserHandler.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.auth.SocialProvider
import com.wafflestudio.snu4t.common.dto.OkResponse
import com.wafflestudio.snu4t.common.extension.toZonedDateTime
import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware
Expand All @@ -8,6 +9,8 @@ import com.wafflestudio.snu4t.users.dto.EmailVerificationResultDto
import com.wafflestudio.snu4t.users.dto.LocalLoginRequest
import com.wafflestudio.snu4t.users.dto.PasswordChangeRequest
import com.wafflestudio.snu4t.users.dto.SendEmailRequest
import com.wafflestudio.snu4t.users.dto.SocialLoginRequest
import com.wafflestudio.snu4t.users.dto.SocialProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.UserDto
import com.wafflestudio.snu4t.users.dto.UserLegacyDto
import com.wafflestudio.snu4t.users.dto.UserPatchRequest
Expand Down Expand Up @@ -99,6 +102,58 @@ class UserHandler(
userService.attachLocal(user, body)
}

suspend fun attachFacebook(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.FACEBOOK)
}

suspend fun attachGoogle(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.GOOGLE)
}

suspend fun attachKakao(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
val socialLoginRequest: SocialLoginRequest = req.awaitBody()
userService.attachSocial(user, socialLoginRequest, SocialProvider.KAKAO)
}

suspend fun detachFacebook(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.FACEBOOK)
}

suspend fun detachGoogle(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.GOOGLE)
}

suspend fun detachKakao(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
userService.detachSocial(user, SocialProvider.KAKAO)
}

suspend fun checkSocialProviders(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!

SocialProvidersCheckDto(
local = user.credential.localId != null,
facebook = user.credential.fbName != null,
google = user.credential.googleSub != null,
kakao = user.credential.kakaoSub != null,
apple = user.credential.appleSub != null,
)
}

suspend fun changePassword(req: ServerRequest): ServerResponse =
handle(req) {
val user = req.getContext().user!!
Expand Down
7 changes: 7 additions & 0 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,17 @@ class MainRouter(
POST("/email/verification/code", userHandler::confirmEmailVerification)
POST("/password", userHandler::attachLocal)
PUT("/password", userHandler::changePassword)
POST("/facebook", userHandler::attachFacebook)
POST("/google", userHandler::attachGoogle)
POST("/kakao", userHandler::attachKakao)
DELETE("/facebook", userHandler::detachFacebook)
DELETE("/google", userHandler::detachGoogle)
DELETE("/kakao", userHandler::detachKakao)
}
"/users".nest {
GET("/me", userHandler::getUserMe)
PATCH("/me", userHandler::patchUserInfo)
GET("/me/social_providers", userHandler::checkSocialProviders)
}
}

Expand Down
134 changes: 134 additions & 0 deletions api/src/main/kotlin/router/docs/UserDocs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.wafflestudio.snu4t.users.dto.EmailVerificationResultDto
import com.wafflestudio.snu4t.users.dto.LocalLoginRequest
import com.wafflestudio.snu4t.users.dto.PasswordChangeRequest
import com.wafflestudio.snu4t.users.dto.SendEmailRequest
import com.wafflestudio.snu4t.users.dto.SocialLoginRequest
import com.wafflestudio.snu4t.users.dto.SocialProvidersCheckDto
import com.wafflestudio.snu4t.users.dto.TokenResponse
import com.wafflestudio.snu4t.users.dto.UserDto
import com.wafflestudio.snu4t.users.dto.UserLegacyDto
Expand Down Expand Up @@ -78,6 +80,21 @@ import org.springframework.web.bind.annotation.RequestMethod
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = UserDto::class))])],
),
),
RouterOperation(
path = "/v1/users/me/social_providers",
method = [RequestMethod.GET],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "getSocialProviders",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = SocialProvidersCheckDto::class))],
),
],
),
),
RouterOperation(
path = "/v1/user/info",
method = [RequestMethod.GET],
Expand Down Expand Up @@ -230,5 +247,122 @@ import org.springframework.web.bind.annotation.RequestMethod
],
),
),
RouterOperation(
path = "/v1/user/facebook",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "attachFacebook",
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/google",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "attachGoogle",
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/kakao",
method = [RequestMethod.POST],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "attachKakao",
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],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "detachFacebook",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = TokenResponse::class))],
),
],
),
),
RouterOperation(
path = "/v1/user/google",
method = [RequestMethod.DELETE],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "detachGoogle",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = TokenResponse::class))],
),
],
),
),
RouterOperation(
path = "/v1/user/kakao",
method = [RequestMethod.DELETE],
produces = [MediaType.APPLICATION_JSON_VALUE],
operation =
Operation(
operationId = "detachKakao",
responses = [
ApiResponse(
responseCode = "200",
content = [Content(schema = Schema(implementation = TokenResponse::class))],
),
],
),
),
)
annotation class UserDocs
2 changes: 2 additions & 0 deletions core/src/main/kotlin/common/exception/ErrorType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ enum class ErrorType(
TOO_MANY_VERIFICATION_CODE_REQUEST(HttpStatus.BAD_REQUEST, 40017, "인증 코드 요청이 너무 많습니다.", "인증 코드 요청이 너무 많습니다."),
INVALID_VERIFICATION_CODE(HttpStatus.BAD_REQUEST, 40018, "인증 코드가 유효하지 않습니다.", "인증 코드가 유효하지 않습니다."),
ALREADY_LOCAL_ACCOUNT(HttpStatus.BAD_REQUEST, 40019, "이미 로컬 계정이 존재합니다.", "이미 로컬 계정이 존재합니다."),
ALREADY_SOCIAL_ACCOUNT(HttpStatus.BAD_REQUEST, 40020, "이미 소셜 계정이 존재합니다.", "이미 소셜 계정이 존재합니다."),

SOCIAL_CONNECT_FAIL(HttpStatus.UNAUTHORIZED, 40100, "소셜 로그인에 실패했습니다.", "소셜 로그인에 실패했습니다."),

Expand All @@ -72,6 +73,7 @@ enum class ErrorType(
INVALID_THEME_TYPE(HttpStatus.CONFLICT, 40905, "적절하지 않은 유형의 테마입니다.", "적절하지 않은 유형의 테마입니다."),
DUPLICATE_POPUP_KEY(HttpStatus.CONFLICT, 40906, "중복된 팝업 키입니다.", "중복된 팝업 키입니다."),
ALREADY_DOWNLOADED_THEME(HttpStatus.CONFLICT, 40907, "이미 다운로드한 테마입니다.", "이미 다운로드한 테마입니다."),
DUPLICATE_SOCIAL_ACCOUNT(HttpStatus.CONFLICT, 40908, "이미 연결된 소셜 계정입니다.", "이미 연결된 소셜 계정입니다."),

DYNAMIC_LINK_GENERATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, 50001, "링크 생성 실패", "링크 생성에 실패했습니다. 잠시 후 다시 시도해주세요."),
}
4 changes: 4 additions & 0 deletions core/src/main/kotlin/common/exception/Snu4tException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ object InvalidVerificationCodeException : Snu4tException(ErrorType.INVALID_VERIF

object AlreadyLocalAccountException : Snu4tException(ErrorType.ALREADY_LOCAL_ACCOUNT)

object AlreadySocialAccountException : Snu4tException(ErrorType.ALREADY_SOCIAL_ACCOUNT)

object SocialConnectFailException : Snu4tException(ErrorType.SOCIAL_CONNECT_FAIL)

object NoUserFcmKeyException : Snu4tException(ErrorType.NO_USER_FCM_KEY)
Expand Down Expand Up @@ -135,4 +137,6 @@ object DuplicatePopupKeyException : Snu4tException(ErrorType.DUPLICATE_POPUP_KEY

object AlreadyDownloadedThemeException : Snu4tException(ErrorType.ALREADY_DOWNLOADED_THEME)

object DuplicateSocialAccountException : Snu4tException(ErrorType.DUPLICATE_SOCIAL_ACCOUNT)

object DynamicLinkGenerationFailedException : Snu4tException(ErrorType.DYNAMIC_LINK_GENERATION_FAILED)
9 changes: 9 additions & 0 deletions core/src/main/kotlin/users/dto/SocialProvidersCheckDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wafflestudio.snu4t.users.dto

data class SocialProvidersCheckDto(
val local: Boolean,
val facebook: Boolean,
val google: Boolean,
val kakao: Boolean,
val apple: Boolean,
)
8 changes: 6 additions & 2 deletions core/src/main/kotlin/users/repository/UserRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ interface UserRepository : CoroutineCrudRepository<User, String> {

suspend fun findAllByIdInAndActiveTrue(ids: List<String>): List<User>

suspend fun findByEmailAndActiveTrue(email: String): User?

suspend fun existsByEmailAndIsEmailVerifiedTrueAndActiveTrue(email: String): Boolean

suspend fun findByEmailAndIsEmailVerifiedTrueAndActiveTrue(email: String): User?

suspend fun existsByCredentialFbIdAndActiveTrue(fbId: String): Boolean

suspend fun existsByCredentialGoogleSubAndActiveTrue(googleSub: String): Boolean

suspend fun existsByCredentialKakaoSubAndActiveTrue(kakaoSub: String): Boolean

fun findAllByNicknameStartingWith(nickname: String): Flow<User>
}
Loading

0 comments on commit cc7a193

Please sign in to comment.