Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

로그인 상태라면 인증 헤더 넣기 #152

Merged
merged 11 commits into from
Nov 23, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -26,4 +27,9 @@ interface UserApi {
suspend fun verifyDuplicatedNickname(
@Path("nickname") nickname: String,
): Response<NicknameResponse>

@GET("users/verify")
suspend fun verify(
@Header("Authorization") accessToken: String,
): Response<Unit>
}
Original file line number Diff line number Diff line change
@@ -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<Preferences>
) {

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 }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,59 @@ 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
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()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.ohdodok.catchytape.core.data.di.qualifier

import javax.inject.Qualifier

@Qualifier
annotation class AuthInterceptor
Original file line number Diff line number Diff line change
@@ -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<Preferences>
private val tokenDataSource: TokenLocalDataSource,
) : AuthRepository {

private val idTokenKey = stringPreferencesKey("idToken")
private val accessTokenKey = stringPreferencesKey("accessToken")

override fun loginWithGoogle(googleToken: String): Flow<String> = 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) {
Expand All @@ -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 {
Expand All @@ -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<Boolean> = flow {
val response = userApi.verifyDuplicatedNickname(nickname = nickname)

Expand All @@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Boolean>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 가져오기보다는 자동으로 로그인하기가 더 비즈니스적인 이름 같아서 변경했어

Copy link
Member

@youlalala youlalala Nov 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이름 바꾼건 좋은것 같아용!! 근데 동사를 앞에 넣어서 LoginAutomaticallyUseCase 어떤가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거는 잘 모르겠어!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UseCase는 앞에 동사가 오는걸 원칙으로 하면 일관성 있지 않을까요???

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저도 동사가 앞에 오는게 일관성 있는것 같습니다~

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

한 번만 봐줘 ㅠㅠㅠ

Copy link
Member

@youlalala youlalala Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

봐줬다😏

private val authRepository: AuthRepository
) {
suspend operator fun invoke() = authRepository.getIdToken()
suspend operator fun invoke(): Boolean = authRepository.tryLoginAutomatically()
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>(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()
Expand Down Expand Up @@ -58,7 +56,7 @@ class LoginFragment : BaseFragment<FragmentLoginBinding>(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 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<LoginEvent>()
Expand All @@ -27,7 +25,6 @@ class LoginViewModel @Inject constructor(
var isAutoLoginFinished: Boolean = false
private set


fun login(token: String, isAutoLogin: Boolean = false) {
loginUseCase(token)
.catch {
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -41,6 +43,9 @@ class NicknameFragment : BaseFragment<FragmentNicknameBinding>(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")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이것도 임시 코드

startActivity(intent)
activity?.finish()
}
}
Expand Down