Skip to content

Commit 6a1b9d4

Browse files
authored
Merge pull request #32 from rbqks529/feat/#26_login_screen_api
[FEAT] 로그인 화면 API 연결
2 parents cb97b91 + 8623ced commit 6a1b9d4

33 files changed

Lines changed: 881 additions & 118 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ xcuserdata
66
!src/**/build/
77
local.properties
88
.idea
9+
**/network_security_config.xml
910
.DS_Store
1011
captures
1112
.externalNativeBuild

composeApp/src/commonMain/kotlin/org/whosin/client/App.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ package org.whosin.client
33
import androidx.compose.foundation.layout.fillMaxSize
44
import androidx.compose.foundation.layout.statusBarsPadding
55
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.LaunchedEffect
7+
import androidx.compose.runtime.collectAsState
8+
import androidx.compose.runtime.getValue
69
import androidx.compose.ui.Modifier
710
import androidx.navigation.compose.rememberNavController
811
import org.jetbrains.compose.ui.tooling.preview.Preview
12+
import org.whosin.client.core.auth.TokenExpiredManager
13+
import org.whosin.client.core.navigation.Route
914
import org.whosin.client.core.navigation.WhosInNavGraph
10-
import org.whosin.client.presentation.dummy.DummyScreen
11-
import org.whosin.client.presentation.dummy.TokenTestScreen
1215
import ui.theme.WhosInTheme
1316

1417

@@ -17,6 +20,16 @@ import ui.theme.WhosInTheme
1720
fun App() {
1821
WhosInTheme {
1922
val navController = rememberNavController()
23+
val isTokenExpired by TokenExpiredManager.isTokenExpired.collectAsState()
24+
25+
LaunchedEffect(isTokenExpired) {
26+
if (isTokenExpired) {
27+
navController.navigate(Route.Login) {
28+
popUpTo(0) { inclusive = true }
29+
}
30+
TokenExpiredManager.reset()
31+
}
32+
}
2033

2134
WhosInNavGraph(
2235
modifier = Modifier
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.whosin.client.core.auth
2+
3+
import kotlinx.coroutines.flow.MutableStateFlow
4+
import kotlinx.coroutines.flow.StateFlow
5+
import kotlinx.coroutines.flow.asStateFlow
6+
7+
object TokenExpiredManager {
8+
private val _isTokenExpired = MutableStateFlow(false)
9+
val isTokenExpired: StateFlow<Boolean> = _isTokenExpired.asStateFlow()
10+
11+
fun setTokenExpired() {
12+
_isTokenExpired.value = true
13+
}
14+
15+
fun reset() {
16+
_isTokenExpired.value = false
17+
}
18+
}

composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/Route.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ sealed interface Route {
2020
data object Signup: Route
2121

2222
@Serializable
23-
data object EmailVerification: Route
23+
data class EmailVerification(val email: String): Route
2424

2525
@Serializable
26-
data object PasswordInput: Route
26+
data class PasswordInput(val email: String): Route
2727

2828
@Serializable
29-
data object NicknameInput: Route
29+
data class NicknameInput(val email: String, val password: String): Route
3030

3131
@Serializable
3232
data class ClubCodeInput(val returnToMyPage: Boolean = false): Route

composeApp/src/commonMain/kotlin/org/whosin/client/core/navigation/WhosInNavGraph.kt

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ fun WhosInNavGraph(
3838
navController.navigate(Route.Login) {
3939
popUpTo(Route.Splash) { inclusive = true }
4040
}
41+
},
42+
onNavigateToHome = {
43+
navController.navigate(Route.Home) {
44+
popUpTo(Route.AuthGraph) { inclusive = true }
45+
}
4146
}
4247
)
4348
}
@@ -74,35 +79,43 @@ fun WhosInNavGraph(
7479
modifier = modifier,
7580
onNavigateBack = { navController.navigateUp() },
7681
onNavigateToEmailVerification = { email ->
77-
navController.navigate(Route.EmailVerification)
82+
navController.navigate(Route.EmailVerification(email))
7883
}
7984
)
8085
}
8186

8287
composable<Route.EmailVerification> { backStackEntry ->
83-
88+
val emailVerificationRoute = backStackEntry.toRoute<Route.EmailVerification>()
89+
8490
EmailVerificationScreen(
8591
modifier = modifier,
92+
email = emailVerificationRoute.email,
8693
onNavigateBack = { navController.navigateUp() },
8794
onVerificationComplete = {
88-
navController.navigate(Route.PasswordInput)
95+
navController.navigate(Route.PasswordInput(emailVerificationRoute.email))
8996
}
9097
)
9198
}
92-
93-
composable<Route.PasswordInput> {
99+
100+
composable<Route.PasswordInput> { backStackEntry ->
101+
val passwordInputRoute = backStackEntry.toRoute<Route.PasswordInput>()
102+
94103
PasswordInputScreen(
95104
modifier = modifier,
96105
onNavigateBack = { navController.navigateUp() },
97106
onPasswordComplete = { password, confirmPassword ->
98-
navController.navigate(Route.NicknameInput)
107+
navController.navigate(Route.NicknameInput(passwordInputRoute.email, password))
99108
}
100109
)
101110
}
102-
103-
composable<Route.NicknameInput> {
111+
112+
composable<Route.NicknameInput> { backStackEntry ->
113+
val nicknameInputRoute = backStackEntry.toRoute<Route.NicknameInput>()
114+
104115
NicknameInputScreen(
105116
modifier = modifier,
117+
email = nicknameInputRoute.email,
118+
password = nicknameInputRoute.password,
106119
onNavigateBack = { navController.navigateUp() },
107120
onNavigateToClubCode = {
108121
navController.navigate(Route.ClubCodeInput(returnToMyPage = false))

composeApp/src/commonMain/kotlin/org/whosin/client/core/network/HttpClientFactory.kt

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import io.ktor.http.encodedPath
2121
import io.ktor.serialization.kotlinx.json.json
2222
import kotlinx.serialization.json.Json
2323
import org.whosin.client.BuildKonfig
24+
import org.whosin.client.core.auth.TokenExpiredManager
2425
import org.whosin.client.core.datastore.TokenManager
2526
import org.whosin.client.data.dto.request.ReissueTokenRequestDto
26-
import org.whosin.client.data.dto.response.TokenDto
27+
import org.whosin.client.data.dto.response.ReissueTokenResponseDto
2728

2829
object HttpClientFactory {
2930
val BASE_URL = BuildKonfig.BASE_URL
@@ -45,10 +46,11 @@ object HttpClientFactory {
4546
socketTimeoutMillis = 20_000L
4647
requestTimeoutMillis = 20_000L
4748
}
48-
install(Auth){
49+
install(Auth) {
4950
bearer {
5051
loadTokens {
51-
val accessToken = tokenManager.getAccessToken() ?: "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlblR5cGUiOiJhY2Nlc3MiLCJ1c2VySWQiOjUsInByb3ZpZGVySWQiOiJsb2NhbGhvc3QiLCJuYW1lIjoi7Iug7KKF7JykIiwicm9sZSI6IlJPTEVfTUVNQkVSIiwiaWF0IjoxNzU5MzgyMzg3LCJleHAiOjE3NTk5ODcxODd9.kT9IH60aCA-6ByEITb-_qPAJY0Oik1bbPKqcBWXzHIk"
52+
val accessToken = tokenManager.getAccessToken()
53+
?: "eyJhbGciOiJIUzI1NiJ9.eyJ0b2tlblR5cGUiOiJhY2Nlc3MiLCJ1c2VySWQiOjUsInByb3ZpZGVySWQiOiJsb2NhbGhvc3QiLCJuYW1lIjoi7Iug7KKF7JykIiwicm9sZSI6IlJPTEVfTUVNQkVSIiwiaWF0IjoxNzU5MzgyMzg3LCJleHAiOjE3NTk5ODcxODd9.kT9IH60aCA-6ByEITb-_qPAJY0Oik1bbPKqcBWXzHIk"
5254
val refreshToken = tokenManager.getRefreshToken() ?: "no_token"
5355
BearerTokens(accessToken = accessToken, refreshToken = refreshToken)
5456
}
@@ -75,7 +77,7 @@ object HttpClientFactory {
7577
"auth/login",
7678
"auth/email",
7779
"auth/email/validation",
78-
"member/reissue" // 토큰 재발급 요청 자체에는 만료된 액세스 토큰을 보내면 안 됨
80+
"auth/reissue" // 토큰 재발급 요청
7981
)
8082

8183
val isNoAuthPath = pathWithNoAuth.any { noAuthPath ->
@@ -88,26 +90,42 @@ object HttpClientFactory {
8890
}
8991
}
9092
refreshTokens {
91-
val rt = tokenManager.getRefreshToken() ?: "no_token"
92-
val response = client.post("member/reissue"){
93-
setBody {
94-
ReissueTokenRequestDto(
95-
refreshToken = rt
93+
try {
94+
val rt = tokenManager.getRefreshToken()
95+
if (rt.isNullOrEmpty()) {
96+
tokenManager.clearToken()
97+
TokenExpiredManager.setTokenExpired()
98+
return@refreshTokens null
99+
}
100+
101+
val response = client.post("auth/reissue") {
102+
setBody(ReissueTokenRequestDto(refreshToken = rt))
103+
markAsRefreshTokenRequest()
104+
}.body<ReissueTokenResponseDto>()
105+
106+
if (response.success && response.data != null) {
107+
tokenManager.saveTokens(
108+
accessToken = response.data.accessToken,
109+
refreshToken = response.data.refreshToken
110+
)
111+
BearerTokens(
112+
accessToken = response.data.accessToken,
113+
refreshToken = response.data.refreshToken
96114
)
115+
} else {
116+
tokenManager.clearToken()
117+
TokenExpiredManager.setTokenExpired()
118+
null
97119
}
98-
markAsRefreshTokenRequest()
99-
}.body<TokenDto>()
100-
tokenManager.saveTokens(
101-
accessToken = response.accessToken,
102-
refreshToken = response.refreshToken
103-
)
104-
val accessToken = response.accessToken
105-
val refreshToken = response.refreshToken
106-
BearerTokens(accessToken,refreshToken)
120+
} catch (e: Exception) {
121+
tokenManager.clearToken()
122+
TokenExpiredManager.setTokenExpired()
123+
null
124+
}
107125
}
108126
}
109127
}
110-
install(Logging){
128+
install(Logging) {
111129
logger = object : Logger {
112130
override fun log(message: String) {
113131
println(message)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.whosin.client.data.dto.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class EmailValidationRequestDto(
8+
@SerialName("email")
9+
val email: String,
10+
@SerialName("authCode")
11+
val authCode: String
12+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.whosin.client.data.dto.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class EmailVerificationRequestDto(
8+
@SerialName("email")
9+
val email: String
10+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package org.whosin.client.data.dto.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class FindPasswordRequestDto(
8+
@SerialName("email")
9+
val email: String
10+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.whosin.client.data.dto.request
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class SignupRequestDto(
8+
@SerialName("email")
9+
val email: String,
10+
@SerialName("password")
11+
val password: String,
12+
@SerialName("nickName")
13+
val nickName: String
14+
)

0 commit comments

Comments
 (0)