-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
회원가입 시 쿠폰 발행 기능 추가
Showing
19 changed files
with
270 additions
and
236 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 0 additions & 43 deletions
43
src/main/kotlin/com/routebox/routebox/application/auth/AppleLoginUseCase.kt
This file was deleted.
Oops, something went wrong.
43 changes: 0 additions & 43 deletions
43
src/main/kotlin/com/routebox/routebox/application/auth/KakaoLoginUseCase.kt
This file was deleted.
Oops, something went wrong.
77 changes: 77 additions & 0 deletions
77
src/main/kotlin/com/routebox/routebox/application/auth/OAuthLoginUseCase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package com.routebox.routebox.application.auth | ||
|
||
import com.routebox.routebox.application.auth.dto.LoginResult | ||
import com.routebox.routebox.application.auth.dto.OAuthLoginCommand | ||
import com.routebox.routebox.domain.auth.AuthService | ||
import com.routebox.routebox.domain.coupon.Coupon | ||
import com.routebox.routebox.domain.coupon.constant.CouponStatus | ||
import com.routebox.routebox.domain.coupon.constant.CouponType | ||
import com.routebox.routebox.domain.coupon.event.CouponsIssuedEvent | ||
import com.routebox.routebox.domain.user.UserService | ||
import jakarta.validation.Valid | ||
import org.springframework.context.ApplicationEventPublisher | ||
import org.springframework.stereotype.Component | ||
import org.springframework.transaction.annotation.Transactional | ||
import java.time.LocalDateTime | ||
|
||
@Component | ||
class OAuthLoginUseCase( | ||
private val userService: UserService, | ||
private val authService: AuthService, | ||
private val eventPublisher: ApplicationEventPublisher, | ||
) { | ||
/** | ||
* OAuth(Kakao, Apple) 로그인. | ||
* | ||
* Social login uid를 조회한 후, 다음 로직을 수행한다. | ||
* - 신규 유저라면: 유저 데이터 생성 및 저장 | ||
* - 기존 유저라면: 유저 데이터 조회 | ||
* 만약 유저 데이터를 생성했을 경우, 회원가입 기념 쿠폰을 세 장 지급할 수 있도록 이벤트를 발행한다. | ||
* | ||
* 이후 생성 또는 조회한 유저 정보로 access token과 refresh token을 생성하여 반환한다. | ||
* | ||
* @param command | ||
* @return 로그인 결과로 신규 유저인지에 대한 정보, access token 정보, refresh token 정보를 응답한다. | ||
*/ | ||
@Transactional | ||
operator fun invoke(@Valid command: OAuthLoginCommand): LoginResult { | ||
val oAuthUserInfo = authService.getUserInfo(command.loginType, command.token) | ||
|
||
var isSignUpProceeded = false | ||
val user = userService.findUserBySocialLoginUid(oAuthUserInfo.uid) | ||
?: userService.createNewUser(command.loginType, oAuthUserInfo.uid) | ||
.also { isSignUpProceeded = true } | ||
|
||
val result = LoginResult( | ||
isNew = user.isOnboardingComplete(), | ||
loginType = command.loginType, | ||
accessToken = authService.issueAccessToken(user), | ||
refreshToken = authService.issueRefreshToken(user), | ||
) | ||
|
||
if (isSignUpProceeded) { | ||
issueSingUpCoupons(userId = user.id) | ||
} | ||
|
||
return result | ||
} | ||
|
||
/** | ||
* 회원 가입 시 3개의 쿠폰 발행 | ||
* | ||
* @param userId 쿠폰을 발행할 대상(사용자)의 id | ||
*/ | ||
private fun issueSingUpCoupons(userId: Long) { | ||
val coupons = List(3) { | ||
Coupon( | ||
userId = userId, | ||
title = "회원가입 감사 쿠폰", | ||
type = CouponType.BUY_ROUTE, | ||
status = CouponStatus.AVAILABLE, | ||
startedAt = LocalDateTime.now(), | ||
endedAt = null, | ||
) | ||
} | ||
eventPublisher.publishEvent(CouponsIssuedEvent(coupons)) | ||
} | ||
} |
3 changes: 0 additions & 3 deletions
3
src/main/kotlin/com/routebox/routebox/application/auth/dto/KakaoLoginCommand.kt
This file was deleted.
Oops, something went wrong.
13 changes: 13 additions & 0 deletions
13
src/main/kotlin/com/routebox/routebox/application/auth/dto/OAuthLoginCommand.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.routebox.routebox.application.auth.dto | ||
|
||
import com.routebox.routebox.domain.user.constant.LoginType | ||
|
||
/** | ||
* `token` | ||
* - Kakao: access token | ||
* - Apple: identity token | ||
*/ | ||
data class OAuthLoginCommand( | ||
val loginType: LoginType, | ||
val token: String, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
57 changes: 57 additions & 0 deletions
57
src/main/kotlin/com/routebox/routebox/domain/coupon/Coupon.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.routebox.routebox.domain.coupon | ||
|
||
import com.routebox.routebox.domain.common.TimeTrackedBaseEntity | ||
import com.routebox.routebox.domain.coupon.constant.CouponStatus | ||
import com.routebox.routebox.domain.coupon.constant.CouponType | ||
import jakarta.persistence.Column | ||
import jakarta.persistence.Entity | ||
import jakarta.persistence.EnumType | ||
import jakarta.persistence.Enumerated | ||
import jakarta.persistence.GeneratedValue | ||
import jakarta.persistence.GenerationType | ||
import jakarta.persistence.Id | ||
import jakarta.persistence.Table | ||
import java.time.LocalDateTime | ||
|
||
@Table(name = "coupon") | ||
@Entity | ||
class Coupon( | ||
id: Long = 0, | ||
userId: Long, | ||
title: String, | ||
type: CouponType, | ||
status: CouponStatus, | ||
startedAt: LocalDateTime, | ||
endedAt: LocalDateTime?, | ||
expiredAt: LocalDateTime? = null, | ||
) : TimeTrackedBaseEntity() { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
@Column(name = "coupon_id") | ||
var id: Long = id | ||
private set | ||
|
||
var userId: Long = userId | ||
private set | ||
|
||
var title: String = title | ||
private set | ||
|
||
@Enumerated(EnumType.STRING) | ||
var type: CouponType = type | ||
private set | ||
|
||
@Enumerated(EnumType.STRING) | ||
var status: CouponStatus = status | ||
private set | ||
|
||
var startedAt: LocalDateTime = startedAt | ||
private set | ||
|
||
var endedAt: LocalDateTime? = endedAt | ||
private set | ||
|
||
var expiredAt: LocalDateTime? = expiredAt | ||
private set | ||
} |
8 changes: 8 additions & 0 deletions
8
src/main/kotlin/com/routebox/routebox/domain/coupon/constant/CouponStatus.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.routebox.routebox.domain.coupon.constant | ||
|
||
enum class CouponStatus { | ||
READY, // 대기 상태. 아직 쿠폰을 사용할 수 없음 | ||
AVAILABLE, // 쿠폰 사용 가능 | ||
USED, // 사용된 쿠폰 | ||
EXPIRED, // 만료된 쿠폰 | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/com/routebox/routebox/domain/coupon/constant/CouponType.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.routebox.routebox.domain.coupon.constant | ||
|
||
enum class CouponType { | ||
BUY_ROUTE, | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/kotlin/com/routebox/routebox/domain/coupon/event/CouponIssuedEventListener.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package com.routebox.routebox.domain.coupon.event | ||
|
||
import com.routebox.routebox.infrastructure.coupon.CouponRepository | ||
import org.springframework.retry.annotation.Backoff | ||
import org.springframework.retry.annotation.Retryable | ||
import org.springframework.stereotype.Component | ||
import org.springframework.transaction.annotation.Propagation | ||
import org.springframework.transaction.annotation.Transactional | ||
import org.springframework.transaction.event.TransactionPhase | ||
import org.springframework.transaction.event.TransactionalEventListener | ||
|
||
@Component | ||
class CouponIssuedEventListener(private val couponRepository: CouponRepository) { | ||
|
||
/* | ||
TODO: 쿠폰 발행 기능은 이벤트를 발행한 기능과 별도의 transaction에서 동작하므로 쿠폰 발행에 실패했을 때에 대한 복구/대응 방법을 고려해야 한다. | ||
쿠폰 발행이 실패했을 때 조치 방법 후보 | ||
- Slack 연동을 통해 관리자에게 알림 보내기 | ||
- Scheduler를 통해 회원가입을 진행한 유저에게 쿠폰이 정상적으로 발행되었는지 확인 | ||
이를 위해 outbox pattern 등 부가적인 기법을 사용하는 것 까지는 적절하지 않아보임. | ||
`handle()`의 기능 자체가 단순하고, DB가 정상 동작하지 않는 경우를 제외하면 쿠폰 발행에 실패하는 경우가 거의 없을 것으로 보이기 때문. | ||
*/ | ||
|
||
@Retryable( | ||
value = [Exception::class], | ||
maxAttempts = 3, | ||
backoff = Backoff(delay = 1500), | ||
) | ||
@Transactional(propagation = Propagation.REQUIRES_NEW) | ||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) | ||
fun handle(event: CouponsIssuedEvent) { | ||
couponRepository.saveAll(event.coupons) | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
src/main/kotlin/com/routebox/routebox/domain/coupon/event/CouponsIssuedEvent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.routebox.routebox.domain.coupon.event | ||
|
||
import com.routebox.routebox.domain.coupon.Coupon | ||
|
||
data class CouponsIssuedEvent(val coupons: Collection<Coupon>) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/main/kotlin/com/routebox/routebox/infrastructure/coupon/CouponRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.routebox.routebox.infrastructure.coupon | ||
|
||
import com.routebox.routebox.domain.coupon.Coupon | ||
import org.springframework.data.jpa.repository.JpaRepository | ||
|
||
interface CouponRepository : JpaRepository<Coupon, Long> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 0 additions & 113 deletions
113
src/test/kotlin/com/routebox/routebox/application/auth/AppleLoginUseCaseTest.kt
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters