Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ android {
)
}

buildTypes {
debug {
val devUrl = properties["bitnagil.dev.url"] as? String ?: "https://dev.example.com"
buildConfigField("String", "BASE_URL", "\"$devUrl\"")
}

release {
val prodUrl = properties["bitnagil.prod.url"] as? String ?: "https://prod.example.com"
buildConfigField("String", "BASE_URL", "\"$prodUrl\"")
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
)
}
}

buildFeatures {
buildConfig = true
}
Expand All @@ -46,5 +63,10 @@ dependencies {
implementation(projects.data)
implementation(projects.domain)
implementation(projects.presentation)

implementation(libs.kakao.v2.user)
implementation(platform(libs.retrofit.bom))
implementation(libs.bundles.retrofit)
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
}
110 changes: 110 additions & 0 deletions app/src/main/java/com/threegap/bitnagil/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.threegap.bitnagil.di

import com.threegap.bitnagil.BuildConfig
import com.threegap.bitnagil.network.auth.AuthInterceptor
import dagger.Module
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 retrofit2.converter.kotlinx.serialization.asConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
private const val APPLICATION_JSON = "application/json"

@Provides
@Singleton
fun provideBaseUrl(): String = BuildConfig.BASE_URL

@Provides
@Singleton
fun provideJson(): Json =
Json {
ignoreUnknownKeys = true
prettyPrint = true
explicitNulls = false
}

@Provides
@Singleton
fun provideJsonConverter(json: Json): Converter.Factory =
json.asConverterFactory(APPLICATION_JSON.toMediaType())

@Provides
@Singleton
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}

@Provides
@Singleton
@Auth
fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor

@Provides
@Singleton
@Auth
fun provideAuthOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
authInterceptor: Interceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(10L, TimeUnit.SECONDS)
.writeTimeout(30L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.build()
Comment thread
wjdrjs00 marked this conversation as resolved.

@Provides
@Singleton
@NoneAuth
fun provideNoneAuthOkHttpClient(
httpLoggingInterceptor: HttpLoggingInterceptor,
): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor)
.connectTimeout(10L, TimeUnit.SECONDS)
.writeTimeout(30L, TimeUnit.SECONDS)
.readTimeout(30L, TimeUnit.SECONDS)
.build()
Comment thread
wjdrjs00 marked this conversation as resolved.

@Provides
@Singleton
@Auth
fun provideAuthRetrofit(
baseUrl: String,
converterFactory: Converter.Factory,
@Auth okHttpClient: OkHttpClient,
): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(converterFactory)
.client(okHttpClient)
.build()

@Provides
@Singleton
@NoneAuth
fun provideNoneAuthRetrofit(
baseUrl: String,
converterFactory: Converter.Factory,
@NoneAuth okHttpClient: OkHttpClient,
): Retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(converterFactory)
.client(okHttpClient)
.build()
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/threegap/bitnagil/di/Qualifier.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.threegap.bitnagil.di

import javax.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class NoneAuth

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class Auth
7 changes: 7 additions & 0 deletions core/network/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,11 @@ android {
}

dependencies {
implementation(projects.core.datastore)

implementation(libs.kotlinx.serialization.json)
implementation(platform(libs.retrofit.bom))
implementation(libs.bundles.retrofit)
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.threegap.bitnagil.network.auth

import com.threegap.bitnagil.datastore.storage.AuthTokenDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject

class AuthInterceptor @Inject constructor(
private val dataStore: AuthTokenDataStore,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val token = runBlocking { dataStore.tokenFlow.first().accessToken }
Comment thread
l5x5l marked this conversation as resolved.
if (token.isNullOrBlank()) {
return chain.proceed(originalRequest)
}

val newRequest = originalRequest.newBuilder()
.addHeader(HEADER_AUTHORIZATION, "$TOKEN_PREFIX $token")
.build()

return chain.proceed(newRequest)
}

companion object {
private const val HEADER_AUTHORIZATION = "Authorization"
private const val TOKEN_PREFIX = "Bearer "
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.threegap.bitnagil.network.auth

import com.threegap.bitnagil.datastore.storage.AuthTokenDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import javax.inject.Inject

class TokenAuthenticator @Inject constructor(
private val dataStore: AuthTokenDataStore,
) : Authenticator {

override fun authenticate(route: Route?, response: Response): Request? {
if (response.code != UNAUTHORIZED) return null
if (responseCount(response) >= MAX_RETRY) return null

// 재발급 api 연결 시 수정 예정입니다.(현재 코드는 임시)
val newAccessToken = runBlocking {
runCatching { dataStore.tokenFlow.first().refreshToken }.getOrNull()
} ?: return null
Comment thread
wjdrjs00 marked this conversation as resolved.

return response.request.newBuilder()
.header(AUTHORIZATION, "$BEARER $newAccessToken")
.build()
}

private fun responseCount(response: Response): Int {
var count = 1
var prior = response.priorResponse
while (prior != null) {
count++
prior = prior.priorResponse
}
return count
}

companion object {
private const val UNAUTHORIZED = 401
private const val MAX_RETRY = 2
private const val AUTHORIZATION = "Authorization"
private const val BEARER = "Bearer"
}
}
Comment thread
wjdrjs00 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.threegap.bitnagil.network.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class BaseResponse<T>(
@SerialName("code")
val code: String,
@SerialName("message")
val message: String,
@SerialName("data")
val data: T? = null,
)