Skip to content

Commit 087eda1

Browse files
authored
Merge pull request DDD-Community#10 from Minsu-Lee/feature/auth-api
feat(login) : 회원가입 화면 구현 및 로그인/로그아웃/회원가입 기능 연동
2 parents 6415472 + 81aa348 commit 087eda1

82 files changed

Lines changed: 1793 additions & 496 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ dependencies {
6060
implementation(project(":feature:splash"))
6161
implementation(project(":feature:login"))
6262
implementation(project(":feature:main"))
63+
implementation(project(":feature:signup"))
6364

6465
implementation(project(":data"))
6566

app/src/main/kotlin/com/anddd/nevera/MainActivity.kt

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import com.anddd.nevera.feature.main.home.navigation.homeScreen
1414
import com.anddd.nevera.feature.login.main.navigation.LOGIN_ROUTE
1515
import com.anddd.nevera.feature.login.main.navigation.loginScreen
1616
import com.anddd.nevera.feature.main.home.navigation.HOME_ROUTE
17+
import com.anddd.nevera.feature.signup.main.navigation.SIGNUP_ROUTE
18+
import com.anddd.nevera.feature.signup.main.navigation.signupScreen
1719
import com.anddd.nevera.feature.splash.main.navigation.SPLASH_ROUTE
1820
import com.anddd.nevera.feature.splash.main.navigation.splashScreen
1921
import dagger.hilt.android.AndroidEntryPoint
@@ -45,19 +47,30 @@ class MainActivity : ComponentActivity() {
4547
}
4648
)
4749
loginScreen(
48-
onLoginSuccess = {
50+
onNavigateToHome = {
4951
navController.navigate(HOME_ROUTE) {
5052
popUpTo(LOGIN_ROUTE) { inclusive = true }
5153
}
5254
},
55+
onNavigateToSignup = {
56+
navController.navigate(SIGNUP_ROUTE)
57+
},
5358
onGoogleLoginClick = {
5459
// TODO: Google SDK 연동 후 LoginViewModel.snsLogin 호출
55-
},
56-
onKakaoLoginClick = {
57-
// TODO: Kakao SDK 연동 후 LoginViewModel.snsLogin 호출
5860
}
5961
)
60-
homeScreen()
62+
signupScreen(
63+
onNavigateToLogin = {
64+
navController.popBackStack()
65+
}
66+
)
67+
homeScreen(
68+
onNavigateToLogin = {
69+
navController.navigate(LOGIN_ROUTE) {
70+
popUpTo(HOME_ROUTE) { inclusive = true }
71+
}
72+
}
73+
)
6174
}
6275
}
6376
}

core/network/src/main/kotlin/com/anddd/nevera/core/network/auth/TokenProvider.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.

core/network/src/main/kotlin/com/anddd/nevera/core/network/di/AuthNetworkModule.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ internal object AuthNetworkModule {
3333

3434
@Provides
3535
@Singleton
36-
@AuthOkHttpClient
37-
fun provideAuthOkHttpClient(): OkHttpClient =
36+
@RefreshOkHttpClient
37+
fun provideRefreshOkHttpClient(): OkHttpClient =
3838
OkHttpClient.Builder()
3939
.addInterceptor(loggingInterceptor)
4040
.connectTimeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
@@ -44,14 +44,14 @@ internal object AuthNetworkModule {
4444

4545
@Provides
4646
@Singleton
47-
@AuthOkHttpClient
48-
fun provideAuthRetrofit(
49-
@AuthOkHttpClient okHttpClient: OkHttpClient,
47+
@RefreshRetrofit
48+
fun provideRefreshRetrofit(
49+
@RefreshOkHttpClient okHttpClient: OkHttpClient,
5050
gson: Gson,
5151
): Retrofit =
5252
Retrofit.Builder()
5353
.baseUrl(BuildConfig.BASE_URL)
5454
.client(okHttpClient)
5555
.addConverterFactory(GsonConverterFactory.create(gson))
5656
.build()
57-
}
57+
}

core/network/src/main/kotlin/com/anddd/nevera/core/network/di/NetworkModule.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ object NetworkModule {
4848

4949
@Provides
5050
@Singleton
51-
fun provideRetrofit(okHttpClient: OkHttpClient, gson: Gson): Retrofit =
51+
fun provideRetrofit(
52+
okHttpClient: OkHttpClient,
53+
gson: Gson
54+
): Retrofit =
5255
Retrofit.Builder()
5356
.baseUrl(BuildConfig.BASE_URL)
5457
.client(okHttpClient)

core/network/src/main/kotlin/com/anddd/nevera/core/network/di/NetworkQualifiers.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ annotation class AuthInterceptorQualifier
88

99
@Qualifier
1010
@Retention(AnnotationRetention.BINARY)
11-
annotation class AuthOkHttpClient
11+
annotation class RefreshOkHttpClient
12+
13+
@Qualifier
14+
@Retention(AnnotationRetention.BINARY)
15+
annotation class RefreshRetrofit
16+
17+
@Qualifier
18+
@Retention(AnnotationRetention.BINARY)
19+
annotation class RefreshApi

data/src/main/kotlin/com/anddd/nevera/data/api/AuthApi.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,40 @@
11
package com.anddd.nevera.data.api
22

33
import com.anddd.nevera.core.network.model.ApiResponse
4-
import com.anddd.nevera.data.model.LoginRequest
5-
import com.anddd.nevera.data.model.LoginResponse
6-
import com.anddd.nevera.data.model.SnsLoginRequest
4+
import com.anddd.nevera.data.model.auth.TokenResponse
5+
import com.anddd.nevera.data.model.auth.EmailRequest
6+
import com.anddd.nevera.data.model.auth.EmailVerifyRequest
7+
import com.anddd.nevera.data.model.auth.LoginRequest
8+
import com.anddd.nevera.data.model.auth.RefreshRequest
9+
import com.anddd.nevera.data.model.auth.SignupRequest
10+
import com.anddd.nevera.data.model.auth.SnsLoginRequest
711
import retrofit2.http.Body
12+
import retrofit2.http.DELETE
813
import retrofit2.http.POST
914

1015
internal interface UserApi {
1116

12-
@POST("auth/login")
13-
suspend fun login(@Body request: LoginRequest): ApiResponse<LoginResponse>
17+
@POST("api/v1/auth/login")
18+
suspend fun login(@Body request: LoginRequest): ApiResponse<TokenResponse>
1419

15-
@POST("auth/sns-login")
16-
suspend fun snsLogin(@Body request: SnsLoginRequest): ApiResponse<LoginResponse>
20+
@POST("api/v1/auth/signup")
21+
suspend fun signup(@Body request: SignupRequest): ApiResponse<String>
22+
23+
@POST("api/v1/auth/google")
24+
suspend fun googleLogin(@Body request: SnsLoginRequest): ApiResponse<TokenResponse>
25+
26+
@POST("api/v1/auth/email-request")
27+
suspend fun emailRequest(@Body request: EmailRequest): ApiResponse<String>
28+
29+
@POST("api/v1/auth/email-verify")
30+
suspend fun emailVerify(@Body request: EmailVerifyRequest): ApiResponse<String>
31+
32+
@POST("api/v1/auth/refresh")
33+
suspend fun refresh(@Body request: RefreshRequest): ApiResponse<TokenResponse>
34+
35+
@POST("api/v1/auth/logout")
36+
suspend fun logout(): ApiResponse<String>
37+
38+
@DELETE("api/v1/auth/withdraw")
39+
suspend fun withdraw(): ApiResponse<String>
1740
}

data/src/main/kotlin/com/anddd/nevera/data/auth/AuthInterceptor.kt

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.anddd.nevera.data.auth
22

33
import com.anddd.nevera.core.network.auth.SessionEventBus
4-
import com.anddd.nevera.core.network.auth.TokenProvider
5-
import com.anddd.nevera.data.api.AuthApi
6-
import com.anddd.nevera.data.api.RefreshRequest
4+
import com.anddd.nevera.data.datasource.RefreshDataSource
5+
import com.anddd.nevera.data.datasource.TokenDataSource
76
import dagger.Lazy
87
import kotlinx.coroutines.runBlocking
98
import kotlinx.coroutines.sync.Mutex
@@ -23,17 +22,17 @@ import javax.inject.Inject
2322
* 4. refresh API가 401을 반환하면 세션 만료로 간주하고 토큰을 삭제한 뒤 세션 만료 이벤트를 발행
2423
* 5. 그 외 갱신 실패는 예외를 그대로 전파 (apiCall이 변환)
2524
*
26-
* [AuthApi]는 [AuthInterceptor]가 없는 전용 [okhttp3.OkHttpClient]를 사용한다.
25+
* [RefreshDataSource]는 [AuthInterceptor]가 없는 전용 [okhttp3.OkHttpClient]를 사용한다.
2726
* refresh 요청이 동일한 인터셉터를 통과하면 [refreshMutex]를 보유한 채 다시 mutex 획득을 시도하는
2827
* 데드락이 발생할 수 있기 때문이다.
2928
*
30-
* [TokenProvider]와 [AuthApi]는 Dagger [Lazy]로 주입한다.
29+
* [TokenDataSource]와 [RefreshDataSource]는 Dagger [Lazy]로 주입한다.
3130
* OkHttpClient 생성 시점에 이 인터셉터가 먼저 만들어지면서 아직 완성되지 않은
3231
* 다른 싱글톤을 참조하는 순환 의존성 문제를 방지하기 위함이다.
3332
*/
3433
internal class AuthInterceptor @Inject constructor(
35-
private val tokenProvider: Lazy<TokenProvider>,
36-
private val authApi: Lazy<AuthApi>,
34+
private val tokenDataSource: Lazy<TokenDataSource>,
35+
private val refreshDataSource: Lazy<RefreshDataSource>,
3736
private val sessionEventBus: SessionEventBus
3837
) : Interceptor {
3938

@@ -42,7 +41,7 @@ internal class AuthInterceptor @Inject constructor(
4241

4342
// OkHttp Interceptor는 동기 API이므로, suspend 함수 호출을 위해 runBlocking을 사용한다.
4443
override fun intercept(chain: Interceptor.Chain): Response {
45-
val tokenBefore = runBlocking { tokenProvider.get().getAccessToken() }
44+
val tokenBefore = runBlocking { tokenDataSource.get().getAccessToken() }
4645
val request = chain.request()
4746
val authedRequest = request.newBuilder()
4847
.apply { tokenBefore?.let { header("Authorization", "Bearer $it") } }
@@ -54,7 +53,7 @@ internal class AuthInterceptor @Inject constructor(
5453
// 401 Unauthorized : 인증 필요 (토큰 없음·만료)
5554
return runBlocking {
5655
refreshMutex.withLock {
57-
val tokenAfterLock = tokenProvider.get().getAccessToken()
56+
val tokenAfterLock = tokenDataSource.get().getAccessToken()
5857

5958
if (tokenAfterLock != null && tokenAfterLock != tokenBefore) {
6059
// response 불필요
@@ -68,22 +67,25 @@ internal class AuthInterceptor @Inject constructor(
6867
)
6968
}
7069

71-
val refreshToken = tokenProvider.get().getRefreshToken()
70+
val refreshToken = tokenDataSource.get().getRefreshToken()
7271
if (refreshToken != null) {
7372
// response 불필요
7473
response.close()
7574

7675
try {
77-
val newTokens = authApi.get().refresh(RefreshRequest(refreshToken))
78-
tokenProvider.get().setTokens(newTokens.accessToken, newTokens.refreshToken)
76+
val response = refreshDataSource.get().refresh(refreshToken)
77+
val newTokenResult = response.result
78+
?: throw IllegalStateException("Refresh response has no result")
79+
tokenDataSource.get().setTokens(newTokenResult.accessToken, newTokenResult.refreshToken)
80+
7981
return@withLock chain.proceed(
8082
request.newBuilder()
81-
.header("Authorization", "Bearer ${newTokens.accessToken}")
83+
.header("Authorization", "Bearer ${newTokenResult.accessToken}")
8284
.build()
8385
)
8486
} catch (e: HttpException) {
8587
if (e.code() == UNAUTHORIZED_STATUS_CODE) {
86-
tokenProvider.get().clearTokens()
88+
tokenDataSource.get().clearLoginInfo()
8789
sessionEventBus.emitSessionExpired()
8890
}
8991
throw e

data/src/main/kotlin/com/anddd/nevera/data/auth/TokenProviderAdapter.kt

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)