diff --git a/.editorconfig b/.editorconfig index e5586d73..77c75f79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ [*.{kt,kts}] -ij_kotlin_allow_trailing_comma=true -ij_kotlin_allow_trailing_comma_on_call_site=true -ktlint_function_naming_ignore_when_annotated_with=Composable, Test +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ktlint_function_naming_ignore_when_annotated_with = Composable, Test diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ca22c10d..f7a94a9f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,8 +1,11 @@ ## 관련 이슈번호 +
close # ## 작업 사항 +
## 기타 사항 +
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index dee1cb1e..af241e66 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -7,7 +7,7 @@ on: jobs: build: runs-on: ubuntu-latest - + steps: - uses: actions/checkout@v3 - name: set up JDK 17 @@ -45,3 +45,4 @@ jobs: - name: Run ktlint run: ./gradlew ktlintCheck + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e402d08e..724be0a1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -20,5 +20,10 @@ android { dependencies { implementation(project(":feature:login")) + implementation(project(":core:interceptor")) + implementation(project(":core:data")) + implementation(project(":core:network")) + implementation(project(":core:datastore")) + implementation(project(":core:domain")) implementation(project(":core:designsystem")) } diff --git a/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt index a855b0aa..fabc1c37 100644 --- a/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/withpeace/withpeace/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.withpeace.withpeace -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 33e48a87..ab29bc8c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" diff --git a/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt b/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt index cd59d3f1..8390979d 100644 --- a/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt +++ b/app/src/main/java/com/withpeace/withpeace/WithPeaceApplication.kt @@ -4,5 +4,5 @@ import android.app.Application import dagger.hilt.android.HiltAndroidApp @HiltAndroidApp -class WithPeaceApplication: Application() { +class WithPeaceApplication : Application() { } \ No newline at end of file diff --git a/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt b/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt index 7386af31..944b793d 100644 --- a/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt +++ b/app/src/test/java/com/withpeace/withpeace/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.withpeace.withpeace +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * diff --git a/build-logic/src/main/kotlin/convention.feature.gradle.kts b/build-logic/src/main/kotlin/convention.feature.gradle.kts index 559cfcb4..756f561f 100644 --- a/build-logic/src/main/kotlin/convention.feature.gradle.kts +++ b/build-logic/src/main/kotlin/convention.feature.gradle.kts @@ -6,6 +6,9 @@ plugins { id("convention.android.compose") id("convention.android.hilt") } -dependencies { + +dependencies{ + implementation(project(":core:data")) + implementation(project(":core:domain")) implementation(project(":core:designsystem")) -} +} \ No newline at end of file diff --git a/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt b/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt index b3b26a3a..410df2d5 100644 --- a/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt +++ b/core/data/src/androidTest/java/com/withpeace/withpeace/core/data/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.withpeace.withpeace.core.data -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/core/data/src/main/AndroidManifest.xml b/core/data/src/main/AndroidManifest.xml index a5918e68..44008a43 100644 --- a/core/data/src/main/AndroidManifest.xml +++ b/core/data/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt new file mode 100644 index 00000000..9d9305a0 --- /dev/null +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/RepositoryModule.kt @@ -0,0 +1,18 @@ +package com.withpeace.withpeace.core.data.di + +import com.withpeace.withpeace.core.data.repository.DefaultTokenRepository +import com.withpeace.withpeace.core.domain.repository.TokenRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +interface RepositoryModule { + + @Binds + @Singleton + fun bindsTokenRepository(defaultTokenRepository: DefaultTokenRepository): TokenRepository +} \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt index e7d7c9c2..b95516fa 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/mapper/TokenMapper.kt @@ -6,6 +6,6 @@ import com.withpeace.withpeace.core.network.di.response.TokenResponse fun TokenResponse.toDomain(): Token { return Token( accessToken = accessToken, - refreshToken = refreshToken + refreshToken = refreshToken, ) } \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt index c725e557..e853f8b3 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt +++ b/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/repository/DefaultTokenRepository.kt @@ -1,49 +1,74 @@ package com.withpeace.withpeace.core.data.repository -import com.skydoves.sandwich.message +import com.skydoves.sandwich.messageOrNull import com.skydoves.sandwich.suspendMapSuccess -import com.skydoves.sandwich.suspendOnError +import com.skydoves.sandwich.suspendOnFailure import com.withpeace.withpeace.core.data.mapper.toDomain import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource import com.withpeace.withpeace.core.domain.model.Token import com.withpeace.withpeace.core.domain.repository.TokenRepository +import com.withpeace.withpeace.core.network.di.request.SignUpRequest import com.withpeace.withpeace.core.network.di.service.AuthService +import com.withpeace.withpeace.core.network.di.service.LoginService import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn import javax.inject.Inject +class DefaultTokenRepository + @Inject + constructor( + private val tokenPreferenceDataSource: TokenPreferenceDataSource, + private val loginService: LoginService, + private val authService: AuthService, + ) : TokenRepository { + override fun getAccessToken(): Flow { + return tokenPreferenceDataSource.accessToken + } -class DefaultTokenRepository @Inject constructor( - private val tokenPreferenceDataSource: TokenPreferenceDataSource, - private val authService: AuthService -) : TokenRepository { + override fun getRefreshToken(): Flow { + return tokenPreferenceDataSource.refreshToken + } - override fun getAccessToken(): Flow { - return tokenPreferenceDataSource.accessToken - } + override suspend fun updateAccessToken(accessToken: String) { + tokenPreferenceDataSource.updateAccessToken(accessToken) + } - override fun getRefreshToken(): Flow { - return tokenPreferenceDataSource.refreshToken - } + override suspend fun updateRefreshToken(refreshToken: String) { + tokenPreferenceDataSource.updateRefreshToken(refreshToken) + } - override suspend fun updateAccessToken(accessToken: String) { - tokenPreferenceDataSource.updateAccessToken(accessToken) - } + override suspend fun signUp( + email: String, + nickname: String, + deviceToken: String?, + ) { + authService.signUp( + SignUpRequest( + email = email, + nickname = nickname, + deviceToken = deviceToken, + ), + ) + } - override suspend fun updateRefreshToken(refreshToken: String) { - tokenPreferenceDataSource.updateRefreshToken(refreshToken) - } + override fun googleLogin( + idToken: String, + onError: (String?) -> Unit, + ): Flow = + flow { + loginService.googleLogin(AUTHORIZATION_FORMAT.format(idToken)) + .suspendMapSuccess { + emit(data.toDomain()) + updateAccessToken(data.accessToken) + updateRefreshToken(data.refreshToken) + }.suspendOnFailure { + onError(messageOrNull) + } + }.flowOn(Dispatchers.IO) - override fun googleLogin(onError: (String?) -> Unit): Flow = flow { - authService.googleLogin() - .suspendMapSuccess { - emit(data.toDomain()) - updateAccessToken(data.accessToken) - updateRefreshToken(data.refreshToken) - }.suspendOnError { - onError(message()) - } - }.flowOn(Dispatchers.IO) -} \ No newline at end of file + companion object { + private const val AUTHORIZATION_FORMAT = "Bearer %s" + } + } diff --git a/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt b/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt index 61be3bed..2dce1955 100644 --- a/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt +++ b/core/data/src/test/java/com/withpeace/withpeace/core/data/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.withpeace.withpeace.core.data +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * diff --git a/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt b/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt index 70ef8ee6..1e831671 100644 --- a/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt +++ b/core/datastore/src/androidTest/java/com/withpeace/withpeace/core/datastore/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.withpeace.withpeace.core.datastore -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/core/datastore/src/main/AndroidManifest.xml b/core/datastore/src/main/AndroidManifest.xml index a5918e68..44008a43 100644 --- a/core/datastore/src/main/AndroidManifest.xml +++ b/core/datastore/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt index a1f7b504..6b5cca17 100644 --- a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt +++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/dataStore/DefaultTokenPreferenceDataSource.kt @@ -10,31 +10,31 @@ import javax.inject.Inject import javax.inject.Named class DefaultTokenPreferenceDataSource @Inject constructor( - @Named("auth") private val dataStore: DataStore -): TokenPreferenceDataSource { + @Named("auth") private val dataStore: DataStore, +) : TokenPreferenceDataSource { - override val accessToken: Flow = dataStore.data.map{ preferences -> + override val accessToken: Flow = dataStore.data.map { preferences -> preferences[ACCESS_TOKEN] } - override val refreshToken: Flow = dataStore.data.map{preferences -> + override val refreshToken: Flow = dataStore.data.map { preferences -> preferences[REFRESH_TOKEN] } - override suspend fun updateAccessToken(accessToken: String) { - dataStore.edit{preferences-> + override suspend fun updateAccessToken(accessToken: String) { + dataStore.edit { preferences -> preferences[ACCESS_TOKEN] = accessToken } } override suspend fun updateRefreshToken(refreshToken: String) { - dataStore.edit{preferences-> + dataStore.edit { preferences -> preferences[REFRESH_TOKEN] = refreshToken } } - companion object{ - private val ACCESS_TOKEN= stringPreferencesKey("ACCESS_TOKEN") - private val REFRESH_TOKEN= stringPreferencesKey("REFRESH_TOKEN") + companion object { + private val ACCESS_TOKEN = stringPreferencesKey("ACCESS_TOKEN") + private val REFRESH_TOKEN = stringPreferencesKey("REFRESH_TOKEN") } } \ No newline at end of file diff --git a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt index d670e80e..8215224f 100644 --- a/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt +++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/DataStoreModule.kt @@ -24,6 +24,6 @@ object DataStoreModule { @Singleton @Named("auth") fun providesTokenDataStore( - @ApplicationContext context: Context + @ApplicationContext context: Context, ): DataStore = context.authDataStore } \ No newline at end of file diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/DataSourceModule.kt b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt similarity index 62% rename from core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/DataSourceModule.kt rename to core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt index c2816b1e..e0beaa82 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/DataSourceModule.kt +++ b/core/datastore/src/main/java/com/withpeace/withpeace/core/datastore/di/PreferenceDataSourceModule.kt @@ -1,4 +1,4 @@ -package com.withpeace.withpeace.core.data.di +package com.withpeace.withpeace.core.datastore.di import com.withpeace.withpeace.core.datastore.dataStore.DefaultTokenPreferenceDataSource import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource @@ -10,9 +10,11 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -abstract class DataSourceModule { +interface PreferenceDataSourceModule { @Binds @Singleton - abstract fun bindsTokenDataSource(tokenPreferenceDataSource: DefaultTokenPreferenceDataSource): TokenPreferenceDataSource + fun bindsTokenPreferenceDataSource( + defaultTokenPreferenceDataSource: DefaultTokenPreferenceDataSource, + ): TokenPreferenceDataSource } \ No newline at end of file diff --git a/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt b/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt index ca1f94c0..c98a0772 100644 --- a/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt +++ b/core/datastore/src/test/java/com/withpeace/withpeace/core/datastore/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.withpeace.withpeace.core.datastore +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt index 8fddfc8b..f8ca3553 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/model/Token.kt @@ -2,5 +2,5 @@ package com.withpeace.withpeace.core.domain.model data class Token( val accessToken: String, - val refreshToken: String + val refreshToken: String, ) \ No newline at end of file diff --git a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt index 56f6c7f0..893b0953 100644 --- a/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt +++ b/core/domain/src/main/java/com/withpeace/withpeace/core/domain/repository/TokenRepository.kt @@ -13,5 +13,7 @@ interface TokenRepository { suspend fun updateRefreshToken(refreshToken: String) - fun googleLogin(onError: (message: String?) -> Unit): Flow + suspend fun signUp(email: String, nickname: String, deviceToken: String?) + + fun googleLogin(idToken: String, onError: (message: String?) -> Unit): Flow } \ No newline at end of file diff --git a/core/interceptor/.gitignore b/core/interceptor/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/core/interceptor/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/core/interceptor/build.gradle.kts b/core/interceptor/build.gradle.kts new file mode 100644 index 00000000..1967e9b0 --- /dev/null +++ b/core/interceptor/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("com.android.library") + id("convention.android.base") + id("convention.android.hilt") +} + +android { + namespace = "com.withpeace.withpeace.core.interceptor" +} + +dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.retrofit.core) + implementation(libs.okhttp.logging) + implementation(project(":core:datastore")) + implementation(project(":core:network")) +} \ No newline at end of file diff --git a/core/interceptor/consumer-rules.pro b/core/interceptor/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/core/interceptor/proguard-rules.pro b/core/interceptor/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/core/interceptor/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt b/core/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..5d4149a9 --- /dev/null +++ b/core/interceptor/src/androidTest/java/com/withpeace/withpeace/core/interceptor/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.withpeace.withpeace.core.interceptor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.withpeace.withpeace.core.interceptor.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/core/interceptor/src/main/AndroidManifest.xml b/core/interceptor/src/main/AndroidManifest.xml new file mode 100644 index 00000000..44008a43 --- /dev/null +++ b/core/interceptor/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt new file mode 100644 index 00000000..cb211e25 --- /dev/null +++ b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/AuthInterceptor.kt @@ -0,0 +1,100 @@ +package com.withpeace.withpeace.core.interceptor + +import android.content.Context +import com.withpeace.withpeace.core.datastore.dataStore.TokenPreferenceDataSource +import com.withpeace.withpeace.core.network.di.response.TokenResponse +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.android.EntryPointAccessors +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response + +class AuthInterceptor(context: Context) : Interceptor { + private val tokenPreferenceDataSource = + EntryPointAccessors + .fromApplication(context) + .getTokenPreferenceDataSource() + + private val client = OkHttpClient.Builder().build() + + override fun intercept(chain: Interceptor.Chain): Response { + val accessToken = runBlocking { tokenPreferenceDataSource.accessToken.firstOrNull() } + val tokenAddedRequest = + chain.request() + .newBuilder() + .addHeader( + ACCESS_TOKEN_HEADER, + TOKEN_FORMAT.format(accessToken), + ).build() + var response = chain.proceed(tokenAddedRequest) + + if (response.code == 401) { + val refreshToken = runBlocking { tokenPreferenceDataSource.refreshToken.firstOrNull() } + if (refreshToken != null) { + runCatching { + refreshAccessToken(refreshToken) + }.onSuccess { tokenResponse -> + runBlocking { + tokenPreferenceDataSource.updateAccessToken(tokenResponse.accessToken) + tokenPreferenceDataSource.updateRefreshToken(tokenResponse.refreshToken) + } + response = + chain.proceed( + chain.request().newBuilder().addHeader( + ACCESS_TOKEN_HEADER, + TOKEN_FORMAT.format(tokenResponse.accessToken), + ).build(), + ) + } + } + } + return response + } + + private fun refreshAccessToken(refreshToken: String): TokenResponse { + val response: Response = + runBlocking { + withContext(Dispatchers.IO) { + client.newCall(createAccessTokenRefreshRequest(refreshToken)).execute() + } + } + if (response.isSuccessful) { + return response.toDto() + } + throw IllegalArgumentException() + } + + private fun createAccessTokenRefreshRequest(refreshToken: String): Request { + return Request.Builder() + .url(REFRESH_URL) + .addHeader(REFRESH_TOKEN_FORMAT, TOKEN_FORMAT.format(refreshToken)) + .build() + } + + private inline fun Response.toDto(): T { + body?.let { + return Json.decodeFromString(it.string()) + } ?: throw IllegalArgumentException() + } + + @EntryPoint + @InstallIn(SingletonComponent::class) + interface AuthInterceptorEntryPoint { + fun getTokenPreferenceDataSource(): TokenPreferenceDataSource + } + + companion object { + private const val REFRESH_URL = "http://49.50.160.170:8080/api/v1/auth/refresh" + private const val REFRESH_TOKEN_FORMAT = "ReAuthorization" + private const val ACCESS_TOKEN_HEADER = "Authorization" + private const val TOKEN_FORMAT = "Bearer %s" + } +} diff --git a/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt new file mode 100644 index 00000000..71b74afa --- /dev/null +++ b/core/interceptor/src/main/java/com/withpeace/withpeace/core/interceptor/InterceptorModule.kt @@ -0,0 +1,20 @@ +package com.withpeace.withpeace.core.interceptor + +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.Interceptor +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object InterceptorModule { + + @Provides + @Singleton + fun provideHeaderInterceptor(@ApplicationContext context: Context): Interceptor = + AuthInterceptor(context) +} \ No newline at end of file diff --git a/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt b/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt new file mode 100644 index 00000000..03abd607 --- /dev/null +++ b/core/interceptor/src/test/java/com/withpeace/withpeace/core/interceptor/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.withpeace.withpeace.core.interceptor + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt b/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt index 75fdeedb..dd396ff4 100644 --- a/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt +++ b/core/network/src/androidTest/java/com/withpeace/withpeace/core/network/ExampleInstrumentedTest.kt @@ -1,13 +1,11 @@ package com.withpeace.withpeace.core.network -import androidx.test.platform.app.InstrumentationRegistry import androidx.test.ext.junit.runners.AndroidJUnit4 - +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* import org.junit.Test import org.junit.runner.RunWith -import org.junit.Assert.* - /** * Instrumented test, which will execute on an Android device. * diff --git a/core/network/src/main/AndroidManifest.xml b/core/network/src/main/AndroidManifest.xml index a5918e68..44008a43 100644 --- a/core/network/src/main/AndroidManifest.xml +++ b/core/network/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/AuthInterceptor.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/AuthInterceptor.kt new file mode 100644 index 00000000..e69de29b diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt similarity index 66% rename from core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt rename to core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt index e84b59a3..dae7fe46 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/NetworkModule.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/NetworkModule.kt @@ -1,4 +1,4 @@ -package com.withpeace.withpeace.core.network.di +package com.withpeace.withpeace.core.network.di.di import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.skydoves.sandwich.adapters.ApiResponseCallAdapterFactory @@ -7,11 +7,13 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import kotlinx.serialization.json.Json +import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Converter import retrofit2.Retrofit +import javax.inject.Named import javax.inject.Singleton @Module @@ -39,34 +41,45 @@ object NetworkModule { } } -// @Provides -// @Singleton -// fun provideHeaderInterceptor(chain: Interceptor.Chain) { -// val requestBuilder = chain.request().newBuilder() -// var apiKey = BuildConfig.X_RIOT_TOKEN -// requestBuilder.addHeader("X-Riot-Token", apiKey) -// chain.proceed(requestBuilder.build()) -// } - @Singleton @Provides - fun provideOkhttpClient(httpLoggingInterceptor: HttpLoggingInterceptor): OkHttpClient { + fun provideOkhttpClient( + authInterceptor: Interceptor, + httpLoggingInterceptor: HttpLoggingInterceptor, + ): OkHttpClient { return OkHttpClient.Builder().apply { - // addInterceptor(AccessTokenInterceptor) TODO("토큰 인터셉터 할당") + addInterceptor(authInterceptor) addInterceptor(httpLoggingInterceptor) }.build() } - + @Named("general") @Provides @Singleton - fun provideRetrofitClient( + fun provideTokenRetrofitClient( okHttpClient: OkHttpClient, - converterFactory: Converter.Factory + converterFactory: Converter.Factory, ): Retrofit { return Retrofit.Builder() .client(okHttpClient) - .baseUrl("https://asia.api.riotgames.com/") // TODO("BaseUrl 수정") + .baseUrl("http://49.50.160.170:8080/") + .addConverterFactory(converterFactory) + .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) + .build() + } + + + /** + * todo: 네이밍 수정 + */ + @Named("initial") + @Provides + @Singleton + fun provideRetrofitClient( + converterFactory: Converter.Factory, + ): Retrofit { + return Retrofit.Builder() + .baseUrl("http://49.50.160.170:8080/") .addConverterFactory(converterFactory) .addCallAdapterFactory(ApiResponseCallAdapterFactory.create()) .build() diff --git a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/ServiceModule.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt similarity index 50% rename from core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/ServiceModule.kt rename to core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt index 1b2f6fb1..4c598414 100644 --- a/core/data/src/main/kotlin/com/withpeace/withpeace/core/data/di/ServiceModule.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/di/ServiceModule.kt @@ -1,11 +1,13 @@ -package com.withpeace.withpeace.core.data.di +package com.withpeace.withpeace.core.network.di.di import com.withpeace.withpeace.core.network.di.service.AuthService +import com.withpeace.withpeace.core.network.di.service.LoginService import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import retrofit2.Retrofit +import javax.inject.Named import javax.inject.Singleton @Module @@ -14,6 +16,11 @@ object ServiceModule { @Provides @Singleton - fun providesAuthService(retrofit: Retrofit): AuthService = + fun providesAuthService(@Named("general") retrofit: Retrofit): AuthService = retrofit.create(AuthService::class.java) + + @Provides + @Singleton + fun providesLoginService(@Named("initial") retrofit: Retrofit): LoginService = + retrofit.create(LoginService::class.java) } \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt index af72b92d..d69487fb 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/request/SignUpRequest.kt @@ -1,5 +1,8 @@ package com.withpeace.withpeace.core.network.di.request +import kotlinx.serialization.Serializable + +@Serializable data class SignUpRequest( val email: String, val nickname: String, diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt index 26350bd7..7f0c414f 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/response/TokenResponse.kt @@ -1,5 +1,8 @@ package com.withpeace.withpeace.core.network.di.response +import kotlinx.serialization.Serializable + +@Serializable data class TokenResponse( val accessToken: String, val refreshToken: String, diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt index 546e1ed0..fffa79ad 100644 --- a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/AuthService.kt @@ -5,18 +5,18 @@ import com.withpeace.withpeace.core.network.di.request.SignUpRequest import com.withpeace.withpeace.core.network.di.response.BaseResponse import com.withpeace.withpeace.core.network.di.response.TokenResponse import retrofit2.http.Body +import retrofit2.http.Header import retrofit2.http.POST interface AuthService { - @POST("/api/v1/auth/google") - fun googleLogin(): ApiResponse> - @POST("/api/v1/auth/register") - fun signUp(@Body signUpRequest: SignUpRequest): ApiResponse> + suspend fun signUp( + @Body signUpRequest: SignUpRequest, + ): ApiResponse> @POST("/api/v1/auth/refresh") - fun refreshAccessToken(): ApiResponse> + suspend fun refreshAccessToken(): ApiResponse> @POST("/api/v1/auth/logout") - fun logout(): ApiResponse> + suspend fun logout(): ApiResponse> } \ No newline at end of file diff --git a/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt new file mode 100644 index 00000000..deee313d --- /dev/null +++ b/core/network/src/main/java/com/withpeace/withpeace/core/network/di/service/LoginService.kt @@ -0,0 +1,16 @@ +package com.withpeace.withpeace.core.network.di.service + +import com.skydoves.sandwich.ApiResponse +import com.withpeace.withpeace.core.network.di.response.BaseResponse +import com.withpeace.withpeace.core.network.di.response.TokenResponse +import retrofit2.http.Header +import retrofit2.http.POST + +interface LoginService { + + @POST("/api/v1/auth/google") + suspend fun googleLogin( + @Header("Authorization") + idToken: String, + ): ApiResponse> +} \ No newline at end of file diff --git a/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt b/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt index 9b8a7dd4..3a01bec8 100644 --- a/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt +++ b/core/network/src/test/java/com/withpeace/withpeace/core/network/ExampleUnitTest.kt @@ -1,9 +1,8 @@ package com.withpeace.withpeace.core.network +import org.junit.Assert.assertEquals import org.junit.Test -import org.junit.Assert.* - /** * Example local unit test, which will execute on the development machine (host). * diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt index 71a1a148..8cc21785 100644 --- a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt +++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginScreen.kt @@ -2,6 +2,8 @@ package com.withpeace.withpeace.feature.login import android.util.Log import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Button import androidx.compose.material3.Surface @@ -11,30 +13,37 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview -import com.withpeace.withpeace.core.designsystem.theme.WithpeaceTheme +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.viewmodel.compose.viewModel import com.withpeace.withpeace.googlelogin.GoogleLoginManager +import kotlinx.coroutines.launch @Composable fun LoginScreen() { + val viewModel: LoginViewModel = viewModel() val coroutineScope = rememberCoroutineScope() val googleLoginManager = GoogleLoginManager(LocalContext.current) - Surface(modifier = Modifier.fillMaxSize()) { - Box { - Button( - onClick = { - googleLoginManager.startLogin( - coroutineScope, - onSuccessLogin = { Log.d("Wooseok", it) }, - onFailLogin = { Log.d("wooseok", it.toString()) }, - ) - }, - ) { - Text( - text = "Login", - style = WithpeaceTheme.typography.body, - color = WithpeaceTheme.colors.MainPink, + Row(modifier = Modifier.fillMaxSize()) { + Button( + onClick = { + googleLoginManager.startLogin( + coroutineScope, + onSuccessLogin = { + Log.d("woogi", "idToken: $it") + viewModel.googleLogin(it) + }, + onFailLogin = { Log.d("wooseok", it.toString()) }, ) - } + }, + ) { + Text(text = "Login") + } + Button( + onClick = { + viewModel.signUp() + }, + ) { + Text(text = "signup") } } } diff --git a/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt new file mode 100644 index 00000000..d88918d3 --- /dev/null +++ b/feature/login/src/main/java/com/withpeace/withpeace/feature/login/LoginViewModel.kt @@ -0,0 +1,37 @@ +package com.withpeace.withpeace.feature.login + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.withpeace.withpeace.core.domain.repository.TokenRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel + @Inject + constructor( + private val tokenRepository: TokenRepository, + ) : ViewModel() { + fun googleLogin(idToken: String) { + viewModelScope.launch { + tokenRepository.googleLogin(idToken) { + Log.e("woogi", it ?: "메시지 없음") + }.collect { token -> + tokenRepository.updateAccessToken(token.accessToken) + tokenRepository.updateRefreshToken(token.refreshToken) + } + } + } + + fun signUp() { + viewModelScope.launch { + tokenRepository.signUp( + email = "wooseok", + nickname = "haha", + deviceToken = null, + ) + } + } + } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a183a317..b620c3c7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -139,7 +139,7 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" } -google-login = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "google-login"} +google-login = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "google-login" } # verify verify-detektFormatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } diff --git a/settings.gradle.kts b/settings.gradle.kts index e13144e1..f73b4e73 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,3 +23,4 @@ include(":core:data") include(":core:domain") include(":core:datastore") include(":core:designsystem") +include(":core:interceptor")