Skip to content

Commit 567ddf8

Browse files
authored
Merge pull request #16 from YAPP-Github/feature/#4-network-module-setup
[Feature/#4] network module 세팅
2 parents 64a06e2 + 35fd177 commit 567ddf8

File tree

9 files changed

+249
-0
lines changed

9 files changed

+249
-0
lines changed

.github/workflows/develop_branch.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ jobs:
1717
- name: Generate local.properties
1818
env:
1919
KAKAO_NATIVE_APP_KEY: ${{ secrets.KAKAO_NATIVE_APP_KEY }}
20+
BITNAGIL_DEV_URL: ${{ secrets.BITNAGIL_DEV_URL }}
21+
BITNAGIL_PROD_URL: ${{ secrets.BITNAGIL_PROD_URL }}
2022
run: |
2123
echo "kakao.native.app.key=$KAKAO_NATIVE_APP_KEY" >> local.properties
24+
echo "bitnagil.dev.url=$BITNAGIL_DEV_URL" >> local.properties
25+
echo "bitnagil.prod.url=$BITNAGIL_PROD_URL" >> local.properties
2226
2327
- name: set up JDK 17
2428
uses: actions/setup-java@v4

app/build.gradle.kts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,27 @@ android {
3333
)
3434
}
3535

36+
buildTypes {
37+
debug {
38+
val devUrl = properties["bitnagil.dev.url"] as? String
39+
?: System.getenv("BITNAGIL_DEV_URL")
40+
?: throw GradleException("bitnagil.dev.url 값이 없습니다.")
41+
buildConfigField("String", "BASE_URL", "\"$devUrl\"")
42+
}
43+
44+
release {
45+
val prodUrl = properties["bitnagil.prod.url"] as? String
46+
?: System.getenv("BITNAGIL_PROD_URL")
47+
?: throw GradleException("bitnagil.prod.url 값이 없습니다.")
48+
buildConfigField("String", "BASE_URL", "\"$prodUrl\"")
49+
isMinifyEnabled = false
50+
proguardFiles(
51+
getDefaultProguardFile("proguard-android-optimize.txt"),
52+
"proguard-rules.pro",
53+
)
54+
}
55+
}
56+
3657
buildFeatures {
3758
buildConfig = true
3859
}
@@ -46,5 +67,10 @@ dependencies {
4667
implementation(projects.data)
4768
implementation(projects.domain)
4869
implementation(projects.presentation)
70+
4971
implementation(libs.kakao.v2.user)
72+
implementation(platform(libs.retrofit.bom))
73+
implementation(libs.bundles.retrofit)
74+
implementation(platform(libs.okhttp.bom))
75+
implementation(libs.bundles.okhttp)
5076
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package com.threegap.bitnagil.di
2+
3+
import com.threegap.bitnagil.BuildConfig
4+
import com.threegap.bitnagil.network.auth.AuthInterceptor
5+
import dagger.Module
6+
import dagger.Provides
7+
import dagger.hilt.InstallIn
8+
import dagger.hilt.components.SingletonComponent
9+
import kotlinx.serialization.json.Json
10+
import okhttp3.Interceptor
11+
import okhttp3.MediaType.Companion.toMediaType
12+
import okhttp3.OkHttpClient
13+
import okhttp3.logging.HttpLoggingInterceptor
14+
import retrofit2.Converter
15+
import retrofit2.Retrofit
16+
import retrofit2.converter.kotlinx.serialization.asConverterFactory
17+
import java.util.concurrent.TimeUnit
18+
import javax.inject.Singleton
19+
20+
@Module
21+
@InstallIn(SingletonComponent::class)
22+
object NetworkModule {
23+
private const val APPLICATION_JSON = "application/json"
24+
25+
@Provides
26+
@Singleton
27+
fun provideBaseUrl(): String = BuildConfig.BASE_URL
28+
29+
@Provides
30+
@Singleton
31+
fun provideJson(): Json =
32+
Json {
33+
ignoreUnknownKeys = true
34+
prettyPrint = true
35+
explicitNulls = false
36+
}
37+
38+
@Provides
39+
@Singleton
40+
fun provideJsonConverter(json: Json): Converter.Factory =
41+
json.asConverterFactory(APPLICATION_JSON.toMediaType())
42+
43+
@Provides
44+
@Singleton
45+
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
46+
HttpLoggingInterceptor().apply {
47+
level = if (BuildConfig.DEBUG) {
48+
HttpLoggingInterceptor.Level.BODY
49+
} else {
50+
HttpLoggingInterceptor.Level.NONE
51+
}
52+
}
53+
54+
@Provides
55+
@Singleton
56+
@Auth
57+
fun provideAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor = authInterceptor
58+
59+
@Provides
60+
@Singleton
61+
@Auth
62+
fun provideAuthOkHttpClient(
63+
httpLoggingInterceptor: HttpLoggingInterceptor,
64+
authInterceptor: Interceptor,
65+
): OkHttpClient = OkHttpClient.Builder()
66+
.addInterceptor(authInterceptor)
67+
.addInterceptor(httpLoggingInterceptor)
68+
.connectTimeout(10L, TimeUnit.SECONDS)
69+
.writeTimeout(30L, TimeUnit.SECONDS)
70+
.readTimeout(30L, TimeUnit.SECONDS)
71+
.build()
72+
73+
@Provides
74+
@Singleton
75+
@NoneAuth
76+
fun provideNoneAuthOkHttpClient(
77+
httpLoggingInterceptor: HttpLoggingInterceptor,
78+
): OkHttpClient = OkHttpClient.Builder()
79+
.addInterceptor(httpLoggingInterceptor)
80+
.connectTimeout(10L, TimeUnit.SECONDS)
81+
.writeTimeout(30L, TimeUnit.SECONDS)
82+
.readTimeout(30L, TimeUnit.SECONDS)
83+
.build()
84+
85+
@Provides
86+
@Singleton
87+
@Auth
88+
fun provideAuthRetrofit(
89+
baseUrl: String,
90+
converterFactory: Converter.Factory,
91+
@Auth okHttpClient: OkHttpClient,
92+
): Retrofit = Retrofit.Builder()
93+
.baseUrl(baseUrl)
94+
.addConverterFactory(converterFactory)
95+
.client(okHttpClient)
96+
.build()
97+
98+
@Provides
99+
@Singleton
100+
@NoneAuth
101+
fun provideNoneAuthRetrofit(
102+
baseUrl: String,
103+
converterFactory: Converter.Factory,
104+
@NoneAuth okHttpClient: OkHttpClient,
105+
): Retrofit = Retrofit.Builder()
106+
.baseUrl(baseUrl)
107+
.addConverterFactory(converterFactory)
108+
.client(okHttpClient)
109+
.build()
110+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.threegap.bitnagil.di
2+
3+
import javax.inject.Qualifier
4+
5+
@Qualifier
6+
@Retention(AnnotationRetention.BINARY)
7+
annotation class NoneAuth
8+
9+
@Qualifier
10+
@Retention(AnnotationRetention.BINARY)
11+
annotation class Auth

core/network/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,11 @@ android {
99
}
1010

1111
dependencies {
12+
implementation(projects.core.datastore)
13+
14+
implementation(libs.kotlinx.serialization.json)
15+
implementation(platform(libs.retrofit.bom))
16+
implementation(libs.bundles.retrofit)
17+
implementation(platform(libs.okhttp.bom))
18+
implementation(libs.bundles.okhttp)
1219
}

core/network/src/main/java/com/threegap/bitnagil/network/.gitkeep

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.threegap.bitnagil.network.auth
2+
3+
import com.threegap.bitnagil.datastore.storage.AuthTokenDataStore
4+
import kotlinx.coroutines.flow.first
5+
import kotlinx.coroutines.runBlocking
6+
import okhttp3.Interceptor
7+
import okhttp3.Response
8+
import javax.inject.Inject
9+
10+
class AuthInterceptor @Inject constructor(
11+
private val dataStore: AuthTokenDataStore,
12+
) : Interceptor {
13+
override fun intercept(chain: Interceptor.Chain): Response {
14+
val originalRequest = chain.request()
15+
val token = runBlocking { dataStore.tokenFlow.first().accessToken }
16+
if (token.isNullOrBlank()) {
17+
return chain.proceed(originalRequest)
18+
}
19+
20+
val newRequest = originalRequest.newBuilder()
21+
.addHeader(HEADER_AUTHORIZATION, "$TOKEN_PREFIX $token")
22+
.build()
23+
24+
return chain.proceed(newRequest)
25+
}
26+
27+
companion object {
28+
private const val HEADER_AUTHORIZATION = "Authorization"
29+
private const val TOKEN_PREFIX = "Bearer "
30+
}
31+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.threegap.bitnagil.network.auth
2+
3+
import com.threegap.bitnagil.datastore.storage.AuthTokenDataStore
4+
import kotlinx.coroutines.flow.first
5+
import kotlinx.coroutines.runBlocking
6+
import okhttp3.Authenticator
7+
import okhttp3.Request
8+
import okhttp3.Response
9+
import okhttp3.Route
10+
import javax.inject.Inject
11+
12+
class TokenAuthenticator @Inject constructor(
13+
private val dataStore: AuthTokenDataStore,
14+
) : Authenticator {
15+
16+
override fun authenticate(route: Route?, response: Response): Request? {
17+
if (response.code != UNAUTHORIZED) return null
18+
if (responseCount(response) >= MAX_RETRY) return null
19+
20+
// 재발급 api 연결 시 수정 예정입니다.(현재 코드는 임시)
21+
val newAccessToken = runBlocking {
22+
runCatching { dataStore.tokenFlow.first().refreshToken }.getOrNull()
23+
} ?: return null
24+
25+
return response.request.newBuilder()
26+
.header(AUTHORIZATION, "$BEARER $newAccessToken")
27+
.build()
28+
}
29+
30+
private fun responseCount(response: Response): Int {
31+
var count = 1
32+
var prior = response.priorResponse
33+
while (prior != null) {
34+
count++
35+
prior = prior.priorResponse
36+
}
37+
return count
38+
}
39+
40+
companion object {
41+
private const val UNAUTHORIZED = 401
42+
private const val MAX_RETRY = 2
43+
private const val AUTHORIZATION = "Authorization"
44+
private const val BEARER = "Bearer"
45+
}
46+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.threegap.bitnagil.network.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class BaseResponse<T>(
8+
@SerialName("code")
9+
val code: String,
10+
@SerialName("message")
11+
val message: String,
12+
@SerialName("data")
13+
val data: T? = null,
14+
)

0 commit comments

Comments
 (0)