diff --git a/data/src/main/java/org/gdsc/data/datasource/TokenDataSource.kt b/data/src/main/java/org/gdsc/data/datasource/TokenDataSource.kt new file mode 100644 index 00000000..a5e89e17 --- /dev/null +++ b/data/src/main/java/org/gdsc/data/datasource/TokenDataSource.kt @@ -0,0 +1,9 @@ +package org.gdsc.data.datasource + +import org.gdsc.data.model.Response +import org.gdsc.domain.model.response.TokenResponse + +interface TokenDataSource { + + suspend fun postRefreshToken(refreshToken: String): Response +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/TokenDataSourceImpl.kt b/data/src/main/java/org/gdsc/data/datasource/TokenDataSourceImpl.kt new file mode 100644 index 00000000..7db78f0f --- /dev/null +++ b/data/src/main/java/org/gdsc/data/datasource/TokenDataSourceImpl.kt @@ -0,0 +1,15 @@ +package org.gdsc.data.datasource + +import org.gdsc.data.model.Response +import org.gdsc.data.network.TokenAPI +import org.gdsc.domain.model.request.RefreshTokenRequest +import org.gdsc.domain.model.response.TokenResponse +import javax.inject.Inject + +class TokenDataSourceImpl @Inject constructor( + private val tokenAPI: TokenAPI +): TokenDataSource { + override suspend fun postRefreshToken(refreshToken: String): Response { + return tokenAPI.refreshToken(RefreshTokenRequest(refreshToken)) + } +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/di/ApiModule.kt b/data/src/main/java/org/gdsc/data/di/ApiModule.kt index dd40d410..598998be 100644 --- a/data/src/main/java/org/gdsc/data/di/ApiModule.kt +++ b/data/src/main/java/org/gdsc/data/di/ApiModule.kt @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import org.gdsc.data.network.LoginAPI import org.gdsc.data.network.RestaurantAPI +import org.gdsc.data.network.TokenAPI import org.gdsc.data.network.UserAPI import retrofit2.Retrofit import javax.inject.Singleton @@ -32,4 +33,10 @@ class ApiModule { return retrofit.create(UserAPI::class.java) } + @Provides + @Singleton + fun provideTokenApi(@AuthClient retrofit: Retrofit): TokenAPI { + return retrofit.create(TokenAPI::class.java) + } + } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/di/NetworkModule.kt b/data/src/main/java/org/gdsc/data/di/NetworkModule.kt index ca32e0ee..da0c560e 100644 --- a/data/src/main/java/org/gdsc/data/di/NetworkModule.kt +++ b/data/src/main/java/org/gdsc/data/di/NetworkModule.kt @@ -1,13 +1,14 @@ package org.gdsc.data.di +import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import org.gdsc.data.network.AuthInterceptor -import org.gdsc.domain.repository.TokenManager import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Singleton @@ -33,7 +34,7 @@ class NetworkModule { @AuthClient @Provides @Singleton - fun provideAuthorizedApiClient(tokenManager: TokenManager): Retrofit { + fun provideAuthorizedApiClient(@ApplicationContext context: Context): Retrofit { return Retrofit .Builder() @@ -41,7 +42,7 @@ class NetworkModule { .addConverterFactory(GsonConverterFactory.create()) .client( baseClientBuilder - .addInterceptor(AuthInterceptor(tokenManager)) + .addInterceptor(AuthInterceptor(context.tokenDataStore)) .build() ) .build() diff --git a/data/src/main/java/org/gdsc/data/di/TokenModule.kt b/data/src/main/java/org/gdsc/data/di/TokenModule.kt index 55dfe077..50bc25fe 100644 --- a/data/src/main/java/org/gdsc/data/di/TokenModule.kt +++ b/data/src/main/java/org/gdsc/data/di/TokenModule.kt @@ -9,22 +9,30 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import org.gdsc.data.datasource.TokenManagerImpl -import org.gdsc.domain.repository.TokenManager +import org.gdsc.data.datasource.TokenDataSource +import org.gdsc.data.datasource.TokenDataSourceImpl +import org.gdsc.data.network.TokenAPI +import org.gdsc.data.repository.TokenRepositoryImpl +import org.gdsc.domain.repository.TokenRepository import javax.inject.Singleton +val TOKEN_INFO_STORAGE = "token_info_storage" +val Context.tokenDataStore: DataStore by preferencesDataStore(name = TOKEN_INFO_STORAGE) + @Module @InstallIn(SingletonComponent::class) class TokenModule { - private val TOKEN_INFO_STORAGE = "token_info_storage" - - private val Context.dataStore: DataStore by preferencesDataStore(name = TOKEN_INFO_STORAGE) + @Provides + @Singleton + fun provideTokenDataSource(tokenAPI: TokenAPI): TokenDataSource { + return TokenDataSourceImpl(tokenAPI) + } @Provides @Singleton - fun provideTokenManager(@ApplicationContext context: Context): TokenManager { - return TokenManagerImpl(context.dataStore) + fun provideTokenRepository(@ApplicationContext context: Context, tokenDataSource: TokenDataSource): TokenRepository { + return TokenRepositoryImpl(context.tokenDataStore, tokenDataSource) } } \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/network/Interceptors.kt b/data/src/main/java/org/gdsc/data/network/Interceptors.kt index 1c26b6cf..feecd614 100644 --- a/data/src/main/java/org/gdsc/data/network/Interceptors.kt +++ b/data/src/main/java/org/gdsc/data/network/Interceptors.kt @@ -1,10 +1,15 @@ package org.gdsc.data.network +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.stringPreferencesKey import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Response -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.Empty import java.io.IOException import javax.inject.Inject @@ -20,15 +25,21 @@ interface CoroutineInterceptor : Interceptor { } } } + suspend fun interceptSuspend(chain: Interceptor.Chain): Response } class AuthInterceptor @Inject constructor( - private val tokenManager: TokenManager + private val dataStore: DataStore, ) : CoroutineInterceptor { override suspend fun interceptSuspend(chain: Interceptor.Chain): Response { - val token = "Bearer ${tokenManager.getAccessToken()}" + + val accessToken = dataStore.data.map { preferences -> + preferences[stringPreferencesKey("accessToken")] + }.first() ?: String.Empty + + val token = "Bearer $accessToken" val newRequest = chain.request().newBuilder() .addHeader("Authorization", token) .build() diff --git a/data/src/main/java/org/gdsc/data/network/TokenAPI.kt b/data/src/main/java/org/gdsc/data/network/TokenAPI.kt new file mode 100644 index 00000000..6ec7e01a --- /dev/null +++ b/data/src/main/java/org/gdsc/data/network/TokenAPI.kt @@ -0,0 +1,14 @@ +package org.gdsc.data.network + +import org.gdsc.data.model.Response +import org.gdsc.domain.model.request.RefreshTokenRequest +import org.gdsc.domain.model.response.TokenResponse +import retrofit2.http.Body +import retrofit2.http.POST + +interface TokenAPI { + @POST("api/v1/token") + suspend fun refreshToken( + @Body request: RefreshTokenRequest + ): Response +} \ No newline at end of file diff --git a/data/src/main/java/org/gdsc/data/datasource/TokenManagerImpl.kt b/data/src/main/java/org/gdsc/data/repository/TokenRepositoryImpl.kt similarity index 77% rename from data/src/main/java/org/gdsc/data/datasource/TokenManagerImpl.kt rename to data/src/main/java/org/gdsc/data/repository/TokenRepositoryImpl.kt index c97b8aa5..47e8dd52 100644 --- a/data/src/main/java/org/gdsc/data/datasource/TokenManagerImpl.kt +++ b/data/src/main/java/org/gdsc/data/repository/TokenRepositoryImpl.kt @@ -1,4 +1,4 @@ -package org.gdsc.data.datasource +package org.gdsc.data.repository import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences @@ -7,14 +7,16 @@ import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import org.gdsc.data.datasource.TokenDataSource import org.gdsc.domain.Empty import org.gdsc.domain.model.response.TokenResponse -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject -class TokenManagerImpl @Inject constructor( - private val dataStore: DataStore -): TokenManager { +class TokenRepositoryImpl @Inject constructor( + private val dataStore: DataStore, + private val tokenDataSource: TokenDataSource +) : TokenRepository { override suspend fun saveTokenInfo(tokenResponse: TokenResponse) { @@ -59,6 +61,19 @@ class TokenManagerImpl @Inject constructor( } } + // check success or not + override suspend fun requestRefreshToken(refreshToken: String): Boolean { + + try { + val newTokenInfo = tokenDataSource.postRefreshToken(refreshToken).data + saveTokenInfo(newTokenInfo) + } catch (e: Exception) { + return false + } + + return true + } + companion object { private val ACCESS_TOKEN = stringPreferencesKey("accessToken") private val ACCESS_TOKEN_EXPIRES_IN = longPreferencesKey("accessTokenExpiresIn") diff --git a/domain/src/main/java/org/gdsc/domain/model/request/RefreshTokenRequest.kt b/domain/src/main/java/org/gdsc/domain/model/request/RefreshTokenRequest.kt new file mode 100644 index 00000000..38a83d67 --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/model/request/RefreshTokenRequest.kt @@ -0,0 +1,5 @@ +package org.gdsc.domain.model.request + +data class RefreshTokenRequest( + val refreshToken: String +) diff --git a/domain/src/main/java/org/gdsc/domain/repository/TokenManager.kt b/domain/src/main/java/org/gdsc/domain/repository/TokenRepository.kt similarity index 79% rename from domain/src/main/java/org/gdsc/domain/repository/TokenManager.kt rename to domain/src/main/java/org/gdsc/domain/repository/TokenRepository.kt index cc9380f0..b359d066 100644 --- a/domain/src/main/java/org/gdsc/domain/repository/TokenManager.kt +++ b/domain/src/main/java/org/gdsc/domain/repository/TokenRepository.kt @@ -2,7 +2,7 @@ package org.gdsc.domain.repository import org.gdsc.domain.model.response.TokenResponse -interface TokenManager { +interface TokenRepository { suspend fun saveTokenInfo(tokenResponse: TokenResponse) @@ -15,5 +15,7 @@ interface TokenManager { suspend fun getRefreshToken(): String suspend fun clearTokenInfo() + + suspend fun requestRefreshToken(refreshToken: String): Boolean } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/ClearTokenInfoUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/ClearTokenInfoUseCase.kt index 0678c8f2..b00279f3 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/ClearTokenInfoUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/ClearTokenInfoUseCase.kt @@ -1,12 +1,12 @@ package org.gdsc.domain.usecase.token -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class ClearTokenInfoUseCase @Inject constructor( - private val tokenManager: TokenManager + private val tokenRepository: TokenRepository ) { suspend operator fun invoke() { - tokenManager.clearTokenInfo() + tokenRepository.clearTokenInfo() } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenExpiresInUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenExpiresInUseCase.kt index 0c11bd6f..6fb18235 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenExpiresInUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenExpiresInUseCase.kt @@ -1,13 +1,13 @@ package org.gdsc.domain.usecase.token -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class GetAccessTokenExpiresInUseCase @Inject constructor( - private val tokenManager: TokenManager + private val tokenRepository: TokenRepository ) { suspend operator fun invoke(): Long { - return tokenManager.getAccessTokenExpiresIn() + return tokenRepository.getAccessTokenExpiresIn() } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenUseCase.kt index 993c1818..e70925ae 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/GetAccessTokenUseCase.kt @@ -1,13 +1,13 @@ package org.gdsc.domain.usecase.token -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class GetAccessTokenUseCase @Inject constructor( - private val tokenManager: TokenManager + private val tokenRepository: TokenRepository ) { suspend operator fun invoke(): String { - return tokenManager.getAccessToken() + return tokenRepository.getAccessToken() } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/GetGrantTypeUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/GetGrantTypeUseCase.kt index 6075ab18..d0c683b0 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/GetGrantTypeUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/GetGrantTypeUseCase.kt @@ -1,13 +1,13 @@ package org.gdsc.domain.usecase.token -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class GetGrantTypeUseCase @Inject constructor( - private val tokenManager: TokenManager + private val tokenRepository: TokenRepository ) { suspend operator fun invoke(): String { - return tokenManager.getGrantType() + return tokenRepository.getGrantType() } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/GetRefreshTokenUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/GetRefreshTokenUseCase.kt index 8d847f29..9997a47d 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/GetRefreshTokenUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/GetRefreshTokenUseCase.kt @@ -1,13 +1,13 @@ package org.gdsc.domain.usecase.token -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class GetRefreshTokenUseCase @Inject constructor( - private val tokenManager: TokenManager + private val tokenRepository: TokenRepository ) { suspend operator fun invoke(): String { - return tokenManager.getRefreshToken() + return tokenRepository.getRefreshToken() } } \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/PostRefreshTokenUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/PostRefreshTokenUseCase.kt new file mode 100644 index 00000000..508913cf --- /dev/null +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/PostRefreshTokenUseCase.kt @@ -0,0 +1,13 @@ +package org.gdsc.domain.usecase.token + +import org.gdsc.domain.repository.TokenRepository +import javax.inject.Inject + +class PostRefreshTokenUseCase@Inject constructor( + private val tokenRepository: TokenRepository +) { + + suspend operator fun invoke(refreshToken: String): Boolean { + return tokenRepository.requestRefreshToken(refreshToken) + } +} \ No newline at end of file diff --git a/domain/src/main/java/org/gdsc/domain/usecase/token/SaveTokenUseCase.kt b/domain/src/main/java/org/gdsc/domain/usecase/token/SaveTokenUseCase.kt index 685c558c..12a154fc 100644 --- a/domain/src/main/java/org/gdsc/domain/usecase/token/SaveTokenUseCase.kt +++ b/domain/src/main/java/org/gdsc/domain/usecase/token/SaveTokenUseCase.kt @@ -1,17 +1,17 @@ package org.gdsc.domain.usecase.token import org.gdsc.domain.model.response.TokenResponse -import org.gdsc.domain.repository.TokenManager +import org.gdsc.domain.repository.TokenRepository import javax.inject.Inject class SaveTokenUseCase @Inject constructor( - private val tokenManager: TokenManager, + private val tokenRepository: TokenRepository, ){ suspend operator fun invoke( response: TokenResponse ) { - tokenManager.saveTokenInfo(response) + tokenRepository.saveTokenInfo(response) } } \ No newline at end of file diff --git a/presentation/src/main/java/org/gdsc/presentation/login/SplashActivity.kt b/presentation/src/main/java/org/gdsc/presentation/login/SplashActivity.kt index 4215a506..a34e3fb5 100644 --- a/presentation/src/main/java/org/gdsc/presentation/login/SplashActivity.kt +++ b/presentation/src/main/java/org/gdsc/presentation/login/SplashActivity.kt @@ -3,33 +3,79 @@ package org.gdsc.presentation.login import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle -import android.os.Handler -import android.os.Looper -import androidx.core.view.WindowCompat +import androidx.lifecycle.lifecycleScope +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import org.gdsc.domain.usecase.token.GetRefreshTokenUseCase +import org.gdsc.domain.usecase.token.PostRefreshTokenUseCase +import org.gdsc.domain.usecase.token.VerifyAccessTokenUseCase import org.gdsc.presentation.R +import org.gdsc.presentation.view.MainActivity +import javax.inject.Inject +@AndroidEntryPoint class SplashActivity : AppCompatActivity() { - val handle: Handler = Handler(Looper.getMainLooper()) + @Inject + lateinit var verifyAccessTokenUseCase: VerifyAccessTokenUseCase + + @Inject + lateinit var postRefreshTokenUseCase: PostRefreshTokenUseCase + + @Inject + lateinit var getRefreshTokenUseCase: GetRefreshTokenUseCase + + private val DELAY_TIME = 2000L override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_splash) setToFullPage() - handle.postDelayed({ - Intent(this, LoginActivity::class.java).apply { - startActivity(this) - } + lifecycleScope.launch(Dispatchers.Main) { + + val accessible = validateToken() + + delay(DELAY_TIME) + + if(accessible) + moveToMain() + else + moveToLogin() + } + } + + private fun moveToMain() { + startActivity(Intent(applicationContext,MainActivity::class.java)) + finish() + } + + private fun moveToLogin() { + startActivity(Intent(applicationContext,LoginActivity::class.java)) + finish() + } + + private suspend fun validateToken(): Boolean { + val isAccessTokenValid = verifyAccessTokenUseCase.invoke() + - finish() - }, 2000) + return if (isAccessTokenValid) { // 정상이면 그냥 리프레쉬 + postRefreshTokenUseCase.invoke(getRefreshTokenUseCase.invoke()) + true + } else { + // refresh 시도 + val isRefreshSuccess = postRefreshTokenUseCase.invoke(getRefreshTokenUseCase.invoke()) + isRefreshSuccess + } } private fun setToFullPage() { val window = window window.setFlags( android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, - android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) + android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ) } } \ No newline at end of file