Skip to content

Commit

Permalink
✨ 회원 가입 API 구현 (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
waterfogSW authored Oct 6, 2024
1 parent 40f3d4a commit afeb0a0
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.threedays.application.auth.port.inbound

import com.threedays.domain.auth.entity.AccessToken
import com.threedays.domain.auth.entity.RefreshToken
import com.threedays.domain.user.entity.User

interface IssueLoginTokens {

fun invoke(command: Command): Result

data class Command(val user: User)

data class Result(
val accessToken: AccessToken,
val refreshToken: RefreshToken,
val accessTokenExpiresIn: Long,
val refreshTokenExpiresIn: Long
)

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.threedays.application.auth.service

import com.threedays.application.auth.config.AuthProperties
import com.threedays.application.auth.port.inbound.IssueLoginTokens
import com.threedays.application.auth.port.inbound.SendAuthCode
import com.threedays.application.auth.port.inbound.VerifyExistingUserAuthCode
import com.threedays.application.auth.port.inbound.VerifyNewUserAuthCode
Expand All @@ -21,7 +22,8 @@ class AuthCodeService(
private val userRepository: UserRepository,
private val authCodeRepository: AuthCodeRepository,
private val authCodeSmsSender: AuthCodeSmsSender,
private val authProperties: AuthProperties,
private val issueLoginTokens: IssueLoginTokens,
private val authProperties: AuthProperties
) : SendAuthCode,
VerifyNewUserAuthCode,
VerifyExistingUserAuthCode {
Expand Down Expand Up @@ -58,6 +60,7 @@ class AuthCodeService(

val registerToken: RegisterToken = RegisterToken.generate(
secret = authProperties.tokenSecret,
phoneNumber = authCode.phoneNumber,
expirationSeconds = authProperties.registerTokenExpirationSeconds
)

Expand All @@ -74,17 +77,8 @@ class AuthCodeService(

val user: User = userRepository.getByPhoneNumber(authCode.phoneNumber)

val accessToken: AccessToken = AccessToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.accessTokenExpirationSeconds,
userId = user.id
)

val refreshToken: RefreshToken = RefreshToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.refreshTokenExpirationSeconds,
userId = user.id
)
val (accessToken: AccessToken, refreshToken: RefreshToken) =
issueLoginTokens.invoke(IssueLoginTokens.Command(user))

return VerifyExistingUserAuthCode.Result(
accessToken = accessToken,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.threedays.application.auth.service

import com.threedays.application.auth.config.AuthProperties
import com.threedays.application.auth.port.inbound.IssueLoginTokens
import com.threedays.domain.auth.entity.AccessToken
import com.threedays.domain.auth.entity.RefreshToken
import com.threedays.domain.user.entity.User
import org.springframework.stereotype.Service

@Service
class AuthTokenService(
private val authProperties: AuthProperties,
) : IssueLoginTokens {

override fun invoke(command: IssueLoginTokens.Command): IssueLoginTokens.Result {
val user: User = command.user

val accessToken: AccessToken = AccessToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.accessTokenExpirationSeconds,
userId = user.id
)

val refreshToken: RefreshToken = RefreshToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.refreshTokenExpirationSeconds,
userId = user.id
)

return IssueLoginTokens.Result(
accessToken = accessToken,
refreshToken = refreshToken,
accessTokenExpiresIn = authProperties.accessTokenExpirationSeconds,
refreshTokenExpiresIn = authProperties.refreshTokenExpirationSeconds
)
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
package com.threedays.application.user.port.inbound

import com.threedays.domain.auth.entity.AccessToken
import com.threedays.domain.auth.entity.RefreshToken
import com.threedays.domain.auth.vo.PhoneNumber
import com.threedays.domain.user.entity.Company
import com.threedays.domain.user.entity.Job
import com.threedays.domain.user.entity.Location
import com.threedays.domain.user.entity.User
import com.threedays.domain.user.entity.User.Name
import com.threedays.domain.user.entity.UserDesiredPartner.PreferDistance
import com.threedays.domain.user.vo.Gender
import java.time.Year

interface RegisterUser {

fun invoke(command: Command): User
fun invoke(command: Command): Result

/**
* 회원 가입 요청
* @param name 이름
* @param phoneNumber 전화번호
* @param userGender 성별
* @param userBirthYear 출생년도
* @param userCompanyId 회사 ID
* @param userJobId 직업 ID
* @param userLocationIds 지역 ID 목록
* @param partnerBirthYearRange 파트너 출생년도 범위, null이면 제한 없음
* @param partnerJobOccupations 파트너 직업 목록
* @param partnerPreferDistance 파트너 선호 거리
* @return 회원
*/
data class Command(
val name: Name,
val phoneNumber: PhoneNumber,
Expand All @@ -22,9 +37,21 @@ interface RegisterUser {
val userCompanyId: Company.Id,
val userJobId: Job.Id,
val userLocationIds: List<Location.Id>,
val partnerBirthYearRange: ClosedRange<Year>,
val partnerBirthYearRange: ClosedRange<Year>? = null,
val partnerJobOccupations: List<Job.Occupation>,
val partnerPreferDistance: PreferDistance
)

/**
* 회원 가입 결과
* @param accessToken 액세스 토큰
* @param refreshToken 리프레시 토큰
* @param expiresIn 액세스 토큰 만료 시간
*/
data class Result(
val accessToken: AccessToken,
val refreshToken: RefreshToken,
val expiresIn: Long
)

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.threedays.application.user.service

import com.threedays.application.auth.config.AuthProperties
import com.threedays.application.auth.port.inbound.IssueLoginTokens
import com.threedays.application.user.port.inbound.RegisterUser
import com.threedays.domain.user.entity.Company
import com.threedays.domain.user.entity.Job
Expand All @@ -18,16 +20,18 @@ class UserService(
private val locationQueryRepository: LocationQueryRepository,
private val jobQueryRepository: JobQueryRepository,
private val companyQueryRepository: CompanyQueryRepository,
private val issueLoginTokens: IssueLoginTokens,
private val authProperties: AuthProperties,
) : RegisterUser {

@Transactional
override fun invoke(command: RegisterUser.Command): User {
override fun invoke(command: RegisterUser.Command): RegisterUser.Result {
val userCompany: Company = companyQueryRepository.get(command.userCompanyId)
val userJob: Job = jobQueryRepository.get(command.userJobId)
val userLocations: List<Location> =
command.userLocationIds.map { locationQueryRepository.get(it) }

return User.create(
val user: User = User.create(
name = command.name,
phoneNumber = command.phoneNumber,
userGender = command.userGender,
Expand All @@ -41,6 +45,15 @@ class UserService(
).also {
userRepository.save(it)
}

val result: IssueLoginTokens.Result = IssueLoginTokens.Command(user)
.let { issueLoginTokens.invoke(it) }

return RegisterUser.Result(
accessToken = result.accessToken,
refreshToken = result.refreshToken,
expiresIn = authProperties.accessTokenExpirationSeconds,
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ class AuthCodeServiceTest : DescribeSpec({
accessTokenExpirationSeconds = expirationSeconds,
refreshTokenExpirationSeconds = expirationSeconds,
)
val issueLoginTokens = IssueLoginTokensStub(authProperties)
val authCodeService = AuthCodeService(
userRepository = userRepository,
authCodeRepository = authCodeRepository,
authCodeSmsSender = authCodeSmsSender,
authProperties = authProperties
issueLoginTokens = issueLoginTokens,
authProperties = authProperties,
)

beforeEach {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.threedays.application.auth.service

import com.threedays.application.auth.config.AuthProperties
import com.threedays.application.auth.port.inbound.IssueLoginTokens
import com.threedays.domain.auth.entity.AccessToken
import com.threedays.domain.auth.entity.RefreshToken

class IssueLoginTokensStub(
private val authProperties: AuthProperties
) : IssueLoginTokens {

override fun invoke(command: IssueLoginTokens.Command): IssueLoginTokens.Result {
val accessToken: AccessToken = AccessToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.accessTokenExpirationSeconds,
userId = command.user.id
)

val refreshToken: RefreshToken = RefreshToken.generate(
secret = authProperties.tokenSecret,
expirationSeconds = authProperties.refreshTokenExpirationSeconds,
userId = command.user.id
)

return IssueLoginTokens.Result(
accessToken = accessToken,
refreshToken = refreshToken,
accessTokenExpiresIn = authProperties.accessTokenExpirationSeconds,
refreshTokenExpiresIn = authProperties.refreshTokenExpirationSeconds
)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.threedays.bootstrap.api.user

import com.threedays.application.user.port.inbound.RegisterUser
import com.threedays.domain.auth.vo.PhoneNumber
import com.threedays.domain.user.entity.Job
import com.threedays.domain.user.entity.User
import com.threedays.domain.user.entity.UserDesiredPartner
import com.threedays.domain.user.vo.Gender
import com.threedays.oas.api.UsersApi
import com.threedays.oas.model.RegisterUserRequest
import com.threedays.oas.model.TokenResponse
import com.threedays.support.common.base.domain.UUIDTypeId
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import java.time.Year

@RestController
class UserController(
private val registerUser: RegisterUser
) : UsersApi {

override fun registerUser(
xRegisterToken: String,
registerUserRequest: RegisterUserRequest
): ResponseEntity<TokenResponse> {
val result: RegisterUser.Result = RegisterUser.Command(
name = User.Name(registerUserRequest.name),
phoneNumber = PhoneNumber(registerUserRequest.phoneNumber),
userGender = Gender.valueOf(registerUserRequest.profile.gender.name),
userBirthYear = Year.of(registerUserRequest.profile.birthYear),
userCompanyId = UUIDTypeId.from(registerUserRequest.profile.companyId),
userJobId = UUIDTypeId.from(registerUserRequest.profile.jobId),
userLocationIds = registerUserRequest.profile.locationIds.map(UUIDTypeId::from),
partnerJobOccupations = registerUserRequest.desiredPartner.jobOccupations
.let { requestData -> requestData.map { Job.Occupation.valueOf(it.name) } },
partnerBirthYearRange = registerUserRequest.desiredPartner.birthYearRange
?.let { Year.of(it.start)..Year.of(it.end) },
partnerPreferDistance = UserDesiredPartner
.PreferDistance
.valueOf(registerUserRequest.desiredPartner.preferDistance.name),
).let {
registerUser.invoke(it)
}

return TokenResponse(
result.accessToken.value,
result.refreshToken.value,
result.expiresIn.toInt()
).let {
ResponseEntity.ok(it)
}
}

}
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ subprojects {
testImplementation(rootProject.libs.fixture.monkey)

testImplementation(kotlin("test"))

testFixturesImplementation(rootProject.libs.fixture.monkey)
}

java {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package com.threedays.domain.auth.entity

import com.threedays.domain.auth.exception.AuthException
import com.threedays.domain.auth.vo.PhoneNumber
import com.threedays.support.common.security.jwt.JwtClaims
import com.threedays.support.common.security.jwt.JwtException
import com.threedays.support.common.security.jwt.JwtTokenProvider
import java.time.Instant
import java.util.*

data class RegisterToken(override val value: String) : AuthToken {
data class RegisterToken(
override val value: String,
val phoneNumber: PhoneNumber,
) : AuthToken {

companion object {

private const val REGISTER_TOKEN_SUBJECT = "register"
private const val PHONE_NUMBER_CLAIM = "phoneNumber"

fun generate(
secret: String,
expirationSeconds: Long
expirationSeconds: Long,
phoneNumber: PhoneNumber,
) = JwtClaims {
registeredClaims {
sub = REGISTER_TOKEN_SUBJECT
Expand All @@ -24,10 +30,13 @@ data class RegisterToken(override val value: String) : AuthToken {
.plusSeconds(expirationSeconds)
.let { Date.from(it) }
}
customClaims {
this[PHONE_NUMBER_CLAIM] = phoneNumber.value
}
}.let {
JwtTokenProvider.createToken(it, secret)
}.let {
RegisterToken(it)
RegisterToken(it, phoneNumber)
}

fun verify(
Expand All @@ -48,7 +57,11 @@ data class RegisterToken(override val value: String) : AuthToken {
throw AuthException.InvalidRegisterTokenException()
}

return RegisterToken(token)
val phoneNumber: PhoneNumber = (result.customClaims[PHONE_NUMBER_CLAIM] as? String)
?.let { PhoneNumber(it) }
?: throw AuthException.InvalidRegisterTokenException()

return RegisterToken(token, phoneNumber)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ data class User(
userCompany: Company,
userJob: Job,
userLocations: List<Location>,
partnerBirthYearRange: ClosedRange<Year>,
partnerBirthYearRange: ClosedRange<Year>?,
partnerJobOccupations: List<Job.Occupation>,
partnerPreferDistance: PreferDistance
): User {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import java.time.Year

data class UserDesiredPartner(
override val id: User.Id,
val birthYearRange: ClosedRange<Year>,
val birthYearRange: ClosedRange<Year>? = null,
val jobOccupations: List<Job.Occupation>,
val preferDistance: PreferDistance
) : DomainEntity<UserDesiredPartner, User.Id>() {
Expand Down
Loading

0 comments on commit afeb0a0

Please sign in to comment.