Skip to content

Commit ca428c2

Browse files
authored
refactor: oAuth에 연동된 유저이름이 변경되었을때 identity user 이름도 싱크한다 (#175)
1 parent 96fad1e commit ca428c2

File tree

20 files changed

+358
-41
lines changed

20 files changed

+358
-41
lines changed

src/main/kotlin/org/gitanimals/identity/app/AppleLoginFacade.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ class AppleLoginFacade(
2727
entryPoint = EntryPoint.APPLE,
2828
profileImage = profileImage,
2929
contributionPerYears = mapOf(),
30+
authenticationId = username,
3031
)
3132
}
3233
}

src/main/kotlin/org/gitanimals/identity/app/GithubLoginFacade.kt

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,65 @@ class GithubLoginFacade(
1515
fun login(code: String): String {
1616
val oauthUserResponse = oauth2Api.getOauthUsername(oauth2Api.getToken(code))
1717

18-
val user = when (userService.existsUser(oauthUserResponse.username, EntryPoint.GITHUB)) {
19-
true -> userService.getUserByNameAndEntryPoint(
20-
oauthUserResponse.username,
21-
EntryPoint.GITHUB,
22-
)
23-
24-
else -> {
25-
val contributedYears =
26-
contributionApi.getAllContributionYearsWithToken(oauthUserResponse.username)
27-
val contributionCountPerYears =
28-
contributionApi.getContributionCountWithToken(
18+
val user = when (userService.existsByEntryPointAndAuthenticationId(
19+
entryPoint = EntryPoint.GITHUB,
20+
authenticationId = oauthUserResponse.id,
21+
)) {
22+
true -> {
23+
runCatching {
24+
userService.getUserByNameAndEntryPoint(
2925
oauthUserResponse.username,
30-
contributedYears
26+
EntryPoint.GITHUB,
27+
)
28+
}.getOrElse {
29+
if (it is IllegalArgumentException) {
30+
userService.updateUsernameByEntryPointAndAuthenticationId(
31+
username = oauthUserResponse.username,
32+
entryPoint = EntryPoint.GITHUB,
33+
authenticationId = oauthUserResponse.id,
34+
)
35+
return@getOrElse userService.getUserByNameAndEntryPoint(
36+
oauthUserResponse.username,
37+
EntryPoint.GITHUB,
38+
)
39+
}
40+
throw it
41+
}
42+
}
43+
44+
false -> {
45+
if (userService.existsUser(
46+
username = oauthUserResponse.username,
47+
entryPoint = EntryPoint.GITHUB,
48+
)
49+
) {
50+
userService.updateUserAuthInfoByUsername(
51+
username = oauthUserResponse.username,
52+
entryPoint = EntryPoint.GITHUB,
53+
authenticationId = oauthUserResponse.id,
3154
)
3255

33-
userService.newUser(
34-
username = oauthUserResponse.username,
35-
entryPoint = EntryPoint.GITHUB,
36-
profileImage = oauthUserResponse.profileImage,
37-
contributionPerYears = contributionCountPerYears,
38-
)
56+
userService.getUserByNameAndEntryPoint(
57+
username = oauthUserResponse.username,
58+
entryPoint = EntryPoint.GITHUB,
59+
)
60+
} else {
61+
val contributedYears =
62+
contributionApi.getAllContributionYearsWithToken(oauthUserResponse.username)
63+
val contributionCountPerYears =
64+
contributionApi.getContributionCountWithToken(
65+
oauthUserResponse.username,
66+
contributedYears,
67+
)
68+
69+
userService.newUser(
70+
username = oauthUserResponse.username,
71+
entryPoint = EntryPoint.GITHUB,
72+
authenticationId = oauthUserResponse.id,
73+
profileImage = oauthUserResponse.profileImage,
74+
contributionPerYears = contributionCountPerYears,
75+
)
76+
}
3977
}
4078
}
4179

src/main/kotlin/org/gitanimals/identity/app/Oauth2Api.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface Oauth2Api {
88

99
class OAuthUserResponse(
1010
val username: String,
11+
val id: String,
1112
val profileImage: String,
1213
)
1314
}

src/main/kotlin/org/gitanimals/identity/controller/UserController.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package org.gitanimals.identity.controller
22

33
import org.gitanimals.identity.app.Token
44
import org.gitanimals.identity.app.UserFacade
5+
import org.gitanimals.identity.controller.request.UsernameUpdateRequest
56
import org.gitanimals.identity.controller.response.UserResponse
67
import org.gitanimals.identity.domain.EntryPoint
78
import org.gitanimals.identity.domain.UserService
@@ -101,4 +102,18 @@ class UserController(
101102
) {
102103
userService.increasePointByUsername(username, entryPoint, idempotencyKey, point)
103104
}
105+
106+
@ResponseStatus(HttpStatus.OK)
107+
@PatchMapping("/internals/users")
108+
fun updateUserByAuthInfo(
109+
@RequestParam("entry-point") entryPoint: EntryPoint,
110+
@RequestParam("authentication-id") authenticationId: String,
111+
@RequestBody request: UsernameUpdateRequest,
112+
) {
113+
userService.updateUsernameByEntryPointAndAuthenticationId(
114+
username = request.changedName,
115+
entryPoint = entryPoint,
116+
authenticationId = authenticationId,
117+
)
118+
}
104119
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package org.gitanimals.identity.controller.request
2+
3+
data class UsernameUpdateRequest(
4+
val changedName: String,
5+
)

src/main/kotlin/org/gitanimals/identity/controller/response/UserResponse.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ data class UserResponse(
1616
fun from(user: User): UserResponse {
1717
return UserResponse(
1818
id = user.id.toString(),
19-
username = user.name,
19+
username = user.getName(),
2020
points = user.getPoints().toString(),
2121
profileImage = user.profileImage,
22-
entryPoint = user.entryPoint,
22+
entryPoint = user.getEntryPoint(),
2323
)
2424
}
2525
}

src/main/kotlin/org/gitanimals/identity/domain/User.kt

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import org.gitanimals.identity.core.AggregateRoot
55
import org.gitanimals.identity.core.IdGenerator
66
import org.gitanimals.identity.core.instant
77
import org.gitanimals.identity.core.toZonedDateTime
8+
import org.slf4j.LoggerFactory
89
import kotlin.math.max
910
import kotlin.math.min
1011

1112
@AggregateRoot
1213
@Table(
1314
name = "users", indexes = [
14-
Index(columnList = "username", unique = true)
15+
Index(columnList = "username", unique = true),
16+
Index(columnList = "entry_point, authentication_id", unique = true),
1517
]
1618
)
1719
@Entity(name = "users")
@@ -21,7 +23,7 @@ class User(
2123
val id: Long,
2224

2325
@Column(name = "username", nullable = false)
24-
val name: String,
26+
private var name: String,
2527

2628
@Column(name = "points", nullable = false)
2729
private var points: Long,
@@ -36,17 +38,34 @@ class User(
3638

3739
@Column(name = "profile_image", nullable = false)
3840
val profileImage: String,
39-
40-
@Enumerated(EnumType.STRING)
41-
@Column(name = "entry_point", columnDefinition = "VARCHAR(50) DEFAULT 'GITHUB'")
42-
val entryPoint: EntryPoint,
41+
42+
@Embedded
43+
private val authInfo: UserAuthInfo,
4344

4445
@Version
4546
private val version: Long? = null,
4647
) : AbstractTime() {
4748

49+
fun getName(): String = name
50+
4851
fun getPoints(): Long = points
4952

53+
fun getEntryPoint(): EntryPoint = authInfo.entryPoint
54+
55+
fun findAuthenticationId(): String? = authInfo.authenticationId
56+
57+
fun setAuthenticationId(authenticationId: String) {
58+
if (authInfo.authenticationId != null) {
59+
check(authInfo.authenticationId == authenticationId) {
60+
val message =
61+
"Different authenticationId input. saved authenticationId: \"${authInfo.authenticationId}\", input authenticationId: \"$authenticationId\""
62+
logger.error(message)
63+
message
64+
}
65+
}
66+
authInfo.authenticationId = authenticationId
67+
}
68+
5069
fun givePoint(point: Long, reason: String) {
5170
val current = instant().toZonedDateTime()
5271
pointHistories.removeAll { it.createdAt.toZonedDateTime().isBefore(current) }
@@ -74,23 +93,30 @@ class User(
7493
this.points += point
7594
}
7695

96+
fun updateUsername(username: String) {
97+
this.name = username
98+
}
99+
77100
companion object {
78-
private val JOIN_POINT_THRESHOLD = 100_000L
79-
private val PER_DAY_GIVE_POINT_THRESHOLD = 20000L
101+
private val logger = LoggerFactory.getLogger(User::class.simpleName)
102+
103+
private const val JOIN_POINT_THRESHOLD = 100_000L
104+
private const val PER_DAY_GIVE_POINT_THRESHOLD = 20000L
80105

81106
fun newUser(
82107
name: String,
83108
points: Long,
84109
profileImage: String,
85110
entryPoint: EntryPoint,
111+
authenticationId: String,
86112
): User {
87113
return User(
88114
id = IdGenerator.generate(),
89115
name = name,
90116
points = min(points, JOIN_POINT_THRESHOLD),
91117
pointHistories = mutableListOf(),
92118
profileImage = profileImage,
93-
entryPoint = entryPoint,
119+
authInfo = UserAuthInfo(entryPoint, authenticationId),
94120
)
95121
}
96122
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.gitanimals.identity.domain
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.Embeddable
5+
import jakarta.persistence.EnumType
6+
import jakarta.persistence.Enumerated
7+
8+
@Embeddable
9+
class UserAuthInfo(
10+
@Enumerated(EnumType.STRING)
11+
@Column(name = "entry_point", columnDefinition = "VARCHAR(50) DEFAULT 'GITHUB'")
12+
val entryPoint: EntryPoint,
13+
14+
@Column(name = "authentication_id", nullable = true)
15+
var authenticationId: String?,
16+
)
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,35 @@
11
package org.gitanimals.identity.domain
22

33
import org.springframework.data.jpa.repository.JpaRepository
4+
import org.springframework.data.jpa.repository.Query
5+
import org.springframework.data.repository.query.Param
46

57
interface UserRepository : JpaRepository<User, Long> {
68

7-
fun findByNameAndEntryPoint(name: String, entryPoint: EntryPoint): User?
9+
@Query(
10+
"""
11+
select u from users as u
12+
where u.name = :name
13+
and u.authInfo.entryPoint = :entryPoint
14+
"""
15+
)
16+
fun findByNameAndEntryPoint(
17+
@Param("name") name: String,
18+
@Param("entryPoint") entryPoint: EntryPoint,
19+
): User?
20+
21+
@Query(
22+
"""
23+
select u from users as u
24+
where u.authInfo.entryPoint = :entryPoint
25+
and u.authInfo.authenticationId = :authenticationId
26+
"""
27+
)
28+
fun findByEntryPointAndAuthenticationId(
29+
@Param("entryPoint") entryPoint: EntryPoint,
30+
@Param("authenticationId") authenticationId: String,
31+
): User?
32+
33+
fun existsByNameAndAuthInfoEntryPoint(name: String, entryPoint: EntryPoint): Boolean
834

9-
fun existsByNameAndEntryPoint(name: String, entryPoint: EntryPoint): Boolean
1035
}

src/main/kotlin/org/gitanimals/identity/domain/UserService.kt

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,34 @@ class UserService(
1414
) {
1515

1616
fun existsUser(username: String, entryPoint: EntryPoint): Boolean =
17-
userRepository.existsByNameAndEntryPoint(username, entryPoint)
17+
userRepository.existsByNameAndAuthInfoEntryPoint(username, entryPoint)
18+
19+
fun existsByEntryPointAndAuthenticationId(entryPoint: EntryPoint, authenticationId: String) =
20+
findByEntryPointAndAuthenticationId(entryPoint, authenticationId) != null
21+
22+
fun findByEntryPointAndAuthenticationId(
23+
entryPoint: EntryPoint,
24+
authenticationId: String
25+
): User? {
26+
return userRepository.findByEntryPointAndAuthenticationId(
27+
entryPoint = entryPoint,
28+
authenticationId = authenticationId,
29+
)
30+
}
31+
32+
@Retryable(retryFor = [ObjectOptimisticLockingFailureException::class])
33+
@Transactional
34+
fun updateUsernameByEntryPointAndAuthenticationId(
35+
username: String,
36+
entryPoint: EntryPoint,
37+
authenticationId: String,
38+
) {
39+
userRepository.findByEntryPointAndAuthenticationId(entryPoint, authenticationId)
40+
?.updateUsername(username)
41+
?: throw IllegalArgumentException(
42+
"Cannot find user by entryPoint(\"$entryPoint\") and authenticationId(\"$authenticationId\")"
43+
)
44+
}
1845

1946
@Retryable(retryFor = [ObjectOptimisticLockingFailureException::class])
2047
@Transactional
@@ -26,17 +53,29 @@ class UserService(
2653
fun newUser(
2754
username: String,
2855
entryPoint: EntryPoint,
56+
authenticationId: String,
2957
profileImage: String,
3058
contributionPerYears: Map<Int, Int>
3159
): User {
3260
if (existsUser(username, entryPoint)) {
3361
return getUserByNameAndEntryPoint(username, entryPoint)
3462
}
63+
64+
if (existsByEntryPointAndAuthenticationId(entryPoint, authenticationId)) {
65+
updateUsernameByEntryPointAndAuthenticationId(
66+
username = username,
67+
entryPoint = entryPoint,
68+
authenticationId = authenticationId,
69+
)
70+
return getUserByNameAndEntryPoint(username, entryPoint)
71+
}
72+
3573
val user = User.newUser(
3674
name = username,
3775
points = contributionPerYears.toPoint(),
3876
profileImage = profileImage,
3977
entryPoint = entryPoint,
78+
authenticationId = authenticationId,
4079
)
4180
return userRepository.save(user)
4281
}
@@ -76,6 +115,18 @@ class UserService(
76115
return user
77116
}
78117

118+
@Transactional
119+
@Retryable(ObjectOptimisticLockingFailureException::class)
120+
fun updateUserAuthInfoByUsername(
121+
username: String,
122+
entryPoint: EntryPoint,
123+
authenticationId: String,
124+
) {
125+
val user = getUserByNameAndEntryPoint(username, entryPoint)
126+
127+
user.setAuthenticationId(authenticationId)
128+
}
129+
79130
private fun requireIdempotency(idempotencyKey: String) {
80131
val userIdempotency =
81132
userIdempotencyRepository.findByIdOrNull(idempotencyKey)

0 commit comments

Comments
 (0)