diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt index 7038dd0..659df72 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/api/UserApi.kt @@ -7,6 +7,7 @@ import com.ohdodok.catchytape.core.data.model.SignUpRequest import retrofit2.Response import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.POST import retrofit2.http.Path @@ -26,4 +27,9 @@ interface UserApi { suspend fun verifyDuplicatedNickname( @Path("nickname") nickname: String, ): Response + + @GET("users/verify") + suspend fun verify( + @Header("Authorization") accessToken: String, + ): Response } \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt new file mode 100644 index 0000000..2bc0e49 --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/datasource/TokenLocalDataSource.kt @@ -0,0 +1,23 @@ +package com.ohdodok.catchytape.core.data.datasource + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class TokenLocalDataSource @Inject constructor( + private val dataStore: DataStore +) { + + private val accessTokenKey = stringPreferencesKey("accessToken") + + suspend fun getAccessToken(): String = + dataStore.data.map { preferences -> preferences[accessTokenKey] ?: "" }.first() + + suspend fun saveAccessToken(token: String) { + dataStore.edit { preferences -> preferences[accessTokenKey] = token } + } +} \ No newline at end of file diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt index 3f759e7..1fe3996 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/NetworkModule.kt @@ -2,11 +2,15 @@ package com.ohdodok.catchytape.core.data.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.ohdodok.catchytape.core.data.BuildConfig +import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource +import com.ohdodok.catchytape.core.data.di.qualifier.AuthInterceptor import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor @@ -14,20 +18,43 @@ import retrofit2.Retrofit import timber.log.Timber import javax.inject.Singleton - @Module @InstallIn(SingletonComponent::class) object NetworkModule { + @AuthInterceptor @Singleton @Provides - fun provideOkHttpClient(): OkHttpClient { + fun provideAuthInterceptor(tokenDataSource: TokenLocalDataSource): Interceptor { + + val accessToken = runBlocking { tokenDataSource.getAccessToken() } + + return Interceptor { chain -> + val newRequest = chain.request().newBuilder() + .addHeader("Authorization", "Bearer $accessToken") + .build() + + chain.proceed(newRequest) + } + } + + @Singleton + @Provides + fun provideLoggingInterceptor(): HttpLoggingInterceptor { val logger = HttpLoggingInterceptor.Logger { message -> Timber.tag("okHttp").d(message) } - val httpInterceptor = HttpLoggingInterceptor(logger) + return HttpLoggingInterceptor(logger) .setLevel(HttpLoggingInterceptor.Level.BODY) + } + @Singleton + @Provides + fun provideOkHttpClient( + loggingInterceptor: HttpLoggingInterceptor, + @AuthInterceptor authInterceptor: Interceptor, + ): OkHttpClient { return OkHttpClient.Builder() - .addInterceptor(httpInterceptor) + .addInterceptor(loggingInterceptor) + .addInterceptor(authInterceptor) .build() } diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt new file mode 100644 index 0000000..52fffef --- /dev/null +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/di/qualifier/AuthInterceptor.kt @@ -0,0 +1,6 @@ +package com.ohdodok.catchytape.core.data.di.qualifier + +import javax.inject.Qualifier + +@Qualifier +annotation class AuthInterceptor diff --git a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt index 7206729..6885e01 100644 --- a/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt +++ b/android/core/data/src/main/java/com/ohdodok/catchytape/core/data/repository/AuthRepositoryImpl.kt @@ -1,33 +1,23 @@ package com.ohdodok.catchytape.core.data.repository -import androidx.datastore.core.DataStore -import androidx.datastore.preferences.core.Preferences -import androidx.datastore.preferences.core.edit -import androidx.datastore.preferences.core.stringPreferencesKey import com.ohdodok.catchytape.core.data.api.UserApi +import com.ohdodok.catchytape.core.data.datasource.TokenLocalDataSource import com.ohdodok.catchytape.core.data.model.LoginRequest import com.ohdodok.catchytape.core.data.model.SignUpRequest import com.ohdodok.catchytape.core.domain.repository.AuthRepository import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.map -import java.lang.RuntimeException import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val userApi: UserApi, - private val preferenceDataStore: DataStore + private val tokenDataSource: TokenLocalDataSource, ) : AuthRepository { - private val idTokenKey = stringPreferencesKey("idToken") - private val accessTokenKey = stringPreferencesKey("accessToken") - override fun loginWithGoogle(googleToken: String): Flow = flow { val response = userApi.login(LoginRequest(idToken = googleToken)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - saveIdToken(googleToken) emit(loginResponse.accessToken) } } else if (response.code() == 401) { @@ -40,7 +30,6 @@ class AuthRepositoryImpl @Inject constructor( val response = userApi.signUp(SignUpRequest(idToken = googleToken, nickname = nickname)) if (response.isSuccessful) { response.body()?.let { loginResponse -> - saveIdToken(googleToken) emit(loginResponse.accessToken) } } else { @@ -49,18 +38,10 @@ class AuthRepositoryImpl @Inject constructor( } } - override suspend fun saveAccessToken(token: String) { - preferenceDataStore.edit { preferences -> preferences[accessTokenKey] = token } + tokenDataSource.saveAccessToken(token) } - override suspend fun saveIdToken(token: String) { - preferenceDataStore.edit { preferences -> preferences[idTokenKey] = token } - } - - override suspend fun getIdToken(): String = - preferenceDataStore.data.map { preferences -> preferences[idTokenKey] ?: "" }.first() - override fun isDuplicatedNickname(nickname: String): Flow = flow { val response = userApi.verifyDuplicatedNickname(nickname = nickname) @@ -70,4 +51,12 @@ class AuthRepositoryImpl @Inject constructor( else -> throw RuntimeException("네트워크 에러") // fixme : 예외 처리 로직이 정해지면 수정 } } + + override suspend fun tryLoginAutomatically(): Boolean { + val accessToken = tokenDataSource.getAccessToken() + + if (accessToken.isBlank()) return false + + return userApi.verify("Bearer $accessToken").isSuccessful + } } \ No newline at end of file diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt index 4189825..24a75d4 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/repository/AuthRepository.kt @@ -10,9 +10,7 @@ interface AuthRepository { suspend fun saveAccessToken(token: String) - suspend fun saveIdToken(token: String) - - suspend fun getIdToken(): String + suspend fun tryLoginAutomatically(): Boolean fun isDuplicatedNickname(nickname: String): Flow diff --git a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt similarity index 59% rename from android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt rename to android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt index 83db5a2..27ec79c 100644 --- a/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/GetIdTokenUseCase.kt +++ b/android/core/domain/src/main/java/com/ohdodok/catchytape/core/domain/usecase/AutomaticallyLoginUseCase.kt @@ -3,8 +3,8 @@ package com.ohdodok.catchytape.core.domain.usecase import com.ohdodok.catchytape.core.domain.repository.AuthRepository import javax.inject.Inject -class GetIdTokenUseCase @Inject constructor( +class AutomaticallyLoginUseCase @Inject constructor( private val authRepository: AuthRepository ) { - suspend operator fun invoke() = authRepository.getIdToken() + suspend operator fun invoke(): Boolean = authRepository.tryLoginAutomatically() } \ No newline at end of file diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt index 72c7f4e..66f72a0 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginFragment.kt @@ -21,8 +21,6 @@ class LoginFragment : BaseFragment(R.layout.fragment_login private val viewModel: LoginViewModel by activityViewModels() - private val loginActivity by lazy { activity as LoginActivity } - private val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) .requestIdToken(BuildConfig.GOOGLE_CLIENT_ID) .requestEmail() @@ -58,7 +56,7 @@ class LoginFragment : BaseFragment(R.layout.fragment_login val intent = Intent() intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") startActivity(intent) - loginActivity.finish() + activity?.finish() } is LoginEvent.NavigateToNickName -> { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt index fb311a2..c065a53 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/LoginViewModel.kt @@ -2,10 +2,9 @@ package com.ohdodok.catchytape.feature.login import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.ohdodok.catchytape.core.domain.usecase.GetIdTokenUseCase +import com.ohdodok.catchytape.core.domain.usecase.AutomaticallyLoginUseCase import com.ohdodok.catchytape.core.domain.usecase.LoginUseCase import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.catch @@ -17,8 +16,7 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val loginUseCase: LoginUseCase, - private val tokenUseCase: GetIdTokenUseCase - + private val automaticallyLoginUseCase: AutomaticallyLoginUseCase ) : ViewModel() { private val _events = MutableSharedFlow() @@ -27,7 +25,6 @@ class LoginViewModel @Inject constructor( var isAutoLoginFinished: Boolean = false private set - fun login(token: String, isAutoLogin: Boolean = false) { loginUseCase(token) .catch { @@ -39,18 +36,13 @@ class LoginViewModel @Inject constructor( }.launchIn(viewModelScope) } - fun automaticallyLogin() { viewModelScope.launch { - val idToken = tokenUseCase() - if (idToken.isNotEmpty()) { - login(idToken, true) - } - delay(1000) + val isLoggedIn = automaticallyLoginUseCase() + if (isLoggedIn) _events.emit(LoginEvent.NavigateToHome) isAutoLoginFinished = true } } - } sealed interface LoginEvent { diff --git a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt index e2fc9ce..bfc54ab 100644 --- a/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt +++ b/android/feature/login/src/main/java/com/ohdodok/catchytape/feature/login/NicknameFragment.kt @@ -1,5 +1,7 @@ package com.ohdodok.catchytape.feature.login +import android.content.ComponentName +import android.content.Intent import android.os.Bundle import android.view.View import android.widget.TextView @@ -41,6 +43,9 @@ class NicknameFragment : BaseFragment(R.layout.fragment viewModel.events.collect { event -> when (event) { is NicknameEvent.NavigateToHome -> { + val intent = Intent() + intent.component = ComponentName("com.ohdodok.catchytape", "com.ohdodok.catchytape.MainActivity") + startActivity(intent) activity?.finish() } }