Skip to content

Commit

Permalink
Merge pull request #6 from ASAP-Lettering/ASAP-60
Browse files Browse the repository at this point in the history
ASAP-60 feat: 소셜 로그인 및 회원가입 추가
  • Loading branch information
tlarbals824 authored Aug 28, 2024
2 parents 9f23b7f + 04d2340 commit 1e156bc
Show file tree
Hide file tree
Showing 44 changed files with 1,092 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .deploy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM amazoncorretto:17-alpine-jdk

ARG TARGET_JAR=/app/build/libs/Bootstrap-Module.jar
ARG TARGET_JAR=/Bootstrap-Module/build/libs/Bootstrap-Module.jar

COPY ${TARGET_JAR} /application.jar

Expand Down
27 changes: 27 additions & 0 deletions Application-Module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Application 모듈

## 역할

* `Lettering` 서비스의 핵심 비즈니스 로직을 처리한다.


## 패키지 구조

```markdown
.
└── {domain}/
├── port/
│ ├── in
│ └── out
├── service
├── vo
└── exception
```

* `{domain}`: 도메인 이름을 의미합니다. 예를 들어, `user` 등이 될 수 있습니다.
* `port`: 외부와의 통신을 위한 인터페이스를 정의합니다.
* `in`: 외부에서 들어오는 요청을 처리하는 인터페이스를 정의합니다.
* `out`: 외부로 나가는 응답을 처리하는 인터페이스를 정의합니다.
* `service`: 비즈니스 로직을 처리하는 구현체입니다.
* `exception`: 비즈니스 로직에서 발생하는 예외를 정의합니다.
* `vo`: 비즈니스 로직에서 사용되는 값 객체를 정의합니다.
4 changes: 4 additions & 0 deletions Application-Module/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dependencies{
implementation(project(":Domain-Module"))
implementation(project(":Common-Module"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.asap.application

import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration


@Configuration
@ComponentScan("com.asap.application")
class ApplicationConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.asap.application.user.exception

import com.asap.common.exception.BusinessException

sealed class UserException(
codePrefix: String = CODE_PREFIX,
errorCode: Int,
httpStatus: Int = 400,
message: String = DEFAULT_ERROR_MESSAGE
): BusinessException(codePrefix, errorCode, httpStatus, message) {

class UserAlreadyRegisteredException: UserException(
errorCode = 1,
message = "이미 가입된 사용자입니다."
)


class UserAuthNotFoundException: UserException(
errorCode = 2,
message = "사용자 인증 정보를 찾을 수 없습니다."
)

class UserNotFoundException: UserException(
errorCode = 3,
message = "사용자를 찾을 수 없습니다."
)


companion object{
const val CODE_PREFIX = "USER"
const val DEFAULT_ERROR_MESSAGE = "사용자와 관련된 예외가 발생했습니다."
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.asap.application.user.port.`in`

import java.time.LocalDate

interface RegisterUserUsecase {

fun registerUser(command: Command): Response


data class Command(
val registerToken: String,
val servicePermission: Boolean,
val privatePermission: Boolean,
val marketingPermission: Boolean,
val birthday: LocalDate?
)

data class Response(
val accessToken: String,
val refreshToken: String
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.asap.application.user.port.`in`

interface SocialLoginUsecase {

fun login(command: Command): Response

data class Command(
val provider: String,
val accessToken: String,
)

sealed class Response {
}
data class Success(
val accessToken: String,
val refreshToken: String
) : Response()

data class NonRegistered(
val registerToken: String
) : Response()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.asap.application.user.port.out

import com.asap.application.user.exception.UserException
import com.asap.application.user.vo.AuthInfo
import com.asap.domain.user.enums.SocialLoginProvider

interface AuthInfoRetrievePort {

@Throws(UserException.UserAuthNotFoundException::class)
fun getAuthInfo(provider: SocialLoginProvider, accessToken: String): AuthInfo
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.asap.application.user.port.out

import com.asap.domain.user.entity.UserAuth
import com.asap.domain.user.enums.SocialLoginProvider

interface UserAuthManagementPort {

fun getUserAuth(
socialId: String,
socialLoginProvider: SocialLoginProvider
): UserAuth?


fun isExistsUserAuth(
socialId: String,
socialLoginProvider: SocialLoginProvider
): Boolean


fun saveUserAuth(
userAuth: UserAuth
): UserAuth

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.asap.application.user.port.out

import com.asap.domain.common.DomainId
import com.asap.domain.user.entity.User

interface UserManagementPort {
fun saveUser(user: User): User

fun getUser(userId: DomainId): User?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.asap.application.user.port.out

import com.asap.application.user.vo.UserClaims
import com.asap.domain.user.entity.User

interface UserTokenManagementPort {
fun resolveRegisterToken(token: String): UserClaims.Register

fun generateRegisterToken(
socialId: String,
socialLoginProvider: String,
username: String
): String

fun generateAccessToken(user: User): String

fun generateRefreshToken(user: User): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.asap.application.user.port.out.memory

import com.asap.application.user.exception.UserException
import com.asap.application.user.port.out.AuthInfoRetrievePort
import com.asap.application.user.vo.AuthInfo
import com.asap.domain.user.enums.SocialLoginProvider
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component

@Component
@Primary
class MemoryAuthInfoRetrieveAdapter: AuthInfoRetrievePort {

private val authInfos = mutableMapOf<Pair<String, SocialLoginProvider>, AuthInfo>().apply {
put(Pair("registered", SocialLoginProvider.KAKAO), AuthInfo(SocialLoginProvider.KAKAO, "socialId", "username"))
put(Pair("nonRegistered", SocialLoginProvider.KAKAO), AuthInfo(SocialLoginProvider.KAKAO, "nonRegisteredId", "username"))
}

override fun getAuthInfo(provider: SocialLoginProvider, accessToken: String): AuthInfo {
return authInfos[Pair(accessToken, provider)] ?: throw UserException.UserAuthNotFoundException()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.asap.application.user.port.out.memory

import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.domain.common.DomainId
import com.asap.domain.user.entity.UserAuth
import com.asap.domain.user.enums.SocialLoginProvider
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component

@Component
@Primary
class MemoryAuthManagementAdapter: UserAuthManagementPort{

private val userAuths = mutableMapOf<Pair<String, SocialLoginProvider>, UserAuth>().apply {
put(Pair("socialId", SocialLoginProvider.KAKAO), UserAuth(userId = DomainId("registered"), socialId = "socialId", socialLoginProvider = SocialLoginProvider.KAKAO))
}

override fun getUserAuth(socialId: String, socialLoginProvider: SocialLoginProvider): UserAuth? {
return userAuths[Pair(socialId, socialLoginProvider)]
}

override fun isExistsUserAuth(socialId: String, socialLoginProvider: SocialLoginProvider): Boolean {
return socialId == "duplicate"
}

override fun saveUserAuth(userAuth: UserAuth): UserAuth {
userAuths[Pair(userAuth.socialId, userAuth.socialLoginProvider)] = userAuth
return userAuth
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.asap.application.user.port.out.memory

import com.asap.application.user.port.out.UserManagementPort
import com.asap.domain.common.DomainId
import com.asap.domain.user.entity.User
import com.asap.domain.user.vo.UserPermission
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component

@Component
@Primary
class MemoryUserManagementAdapter: UserManagementPort {
private val users = mutableMapOf<DomainId, User>().apply {
put(DomainId("registered"), User(DomainId("registered"), "username", UserPermission(true,true,true)))
}

override fun saveUser(user: User): User {
users[user.id] = user
return user
}

override fun getUser(userId: DomainId): User? {
return users[userId]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.asap.application.user.port.out.memory

import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.application.user.vo.UserClaims
import com.asap.common.exception.DefaultException
import com.asap.domain.user.entity.User
import com.asap.domain.user.enums.SocialLoginProvider
import org.springframework.context.annotation.Primary
import org.springframework.stereotype.Component

@Component
@Primary
class MemoryUserTokenManagementAdapter(

) : UserTokenManagementPort{
override fun resolveRegisterToken(token: String): UserClaims.Register {
return when(token){
"valid" -> UserClaims.Register(
socialId = "123",
socialLoginProvider = SocialLoginProvider.KAKAO,
username = "test"
)
"duplicate" -> UserClaims.Register(
socialId = "duplicate",
socialLoginProvider = SocialLoginProvider.KAKAO,
username = "test"
)
else -> throw DefaultException.InvalidArgumentException() // TODO: jwt 구현할 때 수정
}
}

override fun generateRegisterToken(socialId: String, socialLoginProvider: String, username: String): String {
return "registerToken"
}

override fun generateAccessToken(user: User): String {
return "accessToken"
}

override fun generateRefreshToken(user: User): String {
return "refreshToken"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.asap.application.user.service

import com.asap.application.user.exception.UserException
import com.asap.application.user.port.`in`.RegisterUserUsecase
import com.asap.application.user.port.out.UserAuthManagementPort
import com.asap.application.user.port.out.UserManagementPort
import com.asap.application.user.port.out.UserTokenManagementPort
import com.asap.domain.user.entity.User
import com.asap.domain.user.entity.UserAuth
import com.asap.domain.user.vo.UserPermission
import org.springframework.stereotype.Service

@Service
class RegisterUserService(
private val userTokenManagementPort: UserTokenManagementPort,
private val userAuthManagementPort: UserAuthManagementPort,
private val userManagementPort: UserManagementPort
) : RegisterUserUsecase {

/**
* 1. register token으로부터 사용자 정보 추출 -> 토큰이 이미 사용됐으면 에러
* 2. 추출한 사용자가 이미 존재하는지 확인 -> 이미 존재하면 에러
* 3. 추출한 사용자 정보와 함께 사용자 동의 검증 -> 동의하지 않으면 에러
* 4. 사용자 정보 저장 및 jwt 토큰 반환
*/

override fun registerUser(command: RegisterUserUsecase.Command): RegisterUserUsecase.Response {
val userClaims = userTokenManagementPort.resolveRegisterToken(command.registerToken)
if (userAuthManagementPort.isExistsUserAuth(userClaims.socialId, userClaims.socialLoginProvider)) {
throw UserException.UserAlreadyRegisteredException()
}
val registerUser = User(
nickname = userClaims.username,
permission = UserPermission(
command.servicePermission,
command.privatePermission,
command.marketingPermission
)
)
val userAuth = UserAuth(
userId = registerUser.id,
socialId = userClaims.socialId,
socialLoginProvider = userClaims.socialLoginProvider
)

userManagementPort.saveUser(registerUser)
userAuthManagementPort.saveUserAuth(userAuth)

return RegisterUserUsecase.Response(
userTokenManagementPort.generateAccessToken(registerUser),
userTokenManagementPort.generateRefreshToken(registerUser)
)
}
}
Loading

0 comments on commit 1e156bc

Please sign in to comment.