From a25cbc8d24a7ac3fb4a0b2a575fdbe6b3f618bca Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 8 Aug 2024 18:29:44 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../api/common/SwitchBaseUrlInterceptor.kt | 43 +++++++++++ .../config/managers/DevApiConfigsManager.kt | 69 +++++++++++++++++ .../config/managers/ProdApiConfigsManager.kt | 18 +++++ .../local/preferences/PreferencesKeys.kt | 2 + .../managers/ProdApiConfigsManagerTest.kt | 77 +++++++++++++++++++ 5 files changed, 209 insertions(+) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchBaseUrlInterceptor.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt create mode 100644 core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchBaseUrlInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchBaseUrlInterceptor.kt new file mode 100644 index 0000000000..6da54754b1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/SwitchBaseUrlInterceptor.kt @@ -0,0 +1,43 @@ +package com.tangem.datasource.api.common + +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor +import okhttp3.Response +import okio.IOException + +/** + * Switch base url [Interceptor] + * + * @property id api config id [ApiConfig.ID] + * @property apiConfigsManager api configs manager + * +[REDACTED_AUTHOR] + */ +internal class SwitchBaseUrlInterceptor( + private val id: ApiConfig.ID, + private val apiConfigsManager: ApiConfigsManager, +) : Interceptor { + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + var request = chain.request() + val builder = request.newBuilder() + + request = builder + .url(url = request.url.adjustBaseUrl()) + .build() + + return chain.proceed(request) + } + + private fun HttpUrl.adjustBaseUrl(): HttpUrl { + val host = apiConfigsManager.getBaseUrl(id).toHttpUrl().host + + return this.newBuilder() + .host(host) + .build() + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt new file mode 100644 index 0000000000..0af38d7ba9 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/DevApiConfigsManager.kt @@ -0,0 +1,69 @@ +package com.tangem.datasource.api.common.config.managers + +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +/** + * Implementation of [ApiConfigsManager] in DEV environment + * + * @property appPreferencesStore app preferences store + * @property dispatchers coroutine dispatcher provider + */ +internal class DevApiConfigsManager( + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : MutableApiConfigsManager { + + override val configs: Flow> get() = _apiConfigs + + private val _apiConfigs = MutableStateFlow(value = ApiConfig.values()) + + override suspend fun initialize() { + // We can't use appPreferencesStore.getObjectMap as base flow, + // because we should keep possibility to work with configs synchronous. + // See [getBaseUrl] + appPreferencesStore.getObjectMap(PreferencesKeys.apiConfigsEnvironmentKey) + .onEach { savedEnvironments -> + _apiConfigs.value = _apiConfigs.value.map { config -> + val savedEnvironment = savedEnvironments[config.id.name] + + if (savedEnvironment != null) { + config.copySealed(currentEnvironment = savedEnvironment) + } else { + config + } + } + } + .launchIn(CoroutineScope(dispatchers.main)) + } + + override fun getBaseUrl(id: ApiConfig.ID): String { + val config = _apiConfigs.value.firstOrNull { it.id == id } + ?: error("Api config with id [$id] not found. Check ApiConfig implementations") + + return config.environments[config.currentEnvironment] + ?: error( + "Api config with id [$id] doesn't contain environment [${config.currentEnvironment}]. " + + "Check ApiConfig implementations", + ) + } + + override suspend fun changeEnvironment(id: String, environment: ApiEnvironment) { + appPreferencesStore.editData { mutablePreferences -> + val updatedMap = mutablePreferences.getObjectMap(PreferencesKeys.apiConfigsEnvironmentKey) + .toMutableMap() + .apply { put(id, environment) } + + mutablePreferences.setObjectMap(PreferencesKeys.apiConfigsEnvironmentKey, updatedMap) + } + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt new file mode 100644 index 0000000000..9f5e1e31e5 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManager.kt @@ -0,0 +1,18 @@ +package com.tangem.datasource.api.common.config.managers + +import com.tangem.datasource.api.common.config.ApiConfig + +/** Implementation of [ApiConfigsManager] in PROD environment */ +internal class ProdApiConfigsManager : ApiConfigsManager { + + override fun getBaseUrl(id: ApiConfig.ID): String { + val config = ApiConfig.values().firstOrNull { it.id == id } + ?: error("Api config with id [$id] not found. Check ApiConfig implementations") + + return config.environments[config.currentEnvironment] + ?: error( + "Api config with id [$id] doesn't contain " + + "environment [${config.currentEnvironment}]. Check ApiConfig implementations", + ) + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index bb2c5fa1b0..9b01024cc8 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -95,6 +95,8 @@ object PreferencesKeys { booleanPreferencesKey(name = "isTokenSwapPromoOkxShown") } + val apiConfigsEnvironmentKey by lazy { stringPreferencesKey(name = "apiConfigsEnvironment") } + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt new file mode 100644 index 0000000000..bfc7de29cc --- /dev/null +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/common/config/managers/ProdApiConfigsManagerTest.kt @@ -0,0 +1,77 @@ +package com.tangem.datasource.api.common.config.managers + +import com.google.common.truth.Truth +import com.tangem.datasource.BuildConfig +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiConfig.Companion.DEBUG_BUILD_TYPE +import com.tangem.datasource.api.common.config.ApiConfig.Companion.EXTERNAL_BUILD_TYPE +import com.tangem.datasource.api.common.config.ApiConfig.Companion.INTERNAL_BUILD_TYPE +import com.tangem.datasource.api.common.config.ApiConfig.Companion.MOCKED_BUILD_TYPE +import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD_TYPE +import com.tangem.datasource.api.common.config.Express +import com.tangem.datasource.api.common.config.TangemTech +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +/** +[REDACTED_AUTHOR] + */ +@RunWith(Parameterized::class) +internal class ProdApiConfigsManagerTest(private val model: Model) { + + private val manager = ProdApiConfigsManager() + + @Test + fun test_getBaseUrl() { + val actual = manager.getBaseUrl(id = model.id) + + Truth.assertThat(actual).isEqualTo(model.expected) + } + + data class Model(val id: ApiConfig.ID, val expected: String) + + private companion object { + + @JvmStatic + @Parameterized.Parameters + fun data(): Collection = ApiConfig.values().map { + when (it) { + is Express -> createExpressModel() + is TangemTech -> createTangemTechModel() + } + } + + private fun createExpressModel(): Model { + return Model( + id = ApiConfig.ID.Express, + expected = when (BuildConfig.BUILD_TYPE) { + DEBUG_BUILD_TYPE -> "[REDACTED_ENV_URL]" + INTERNAL_BUILD_TYPE, + MOCKED_BUILD_TYPE, + -> "[REDACTED_ENV_URL]" + EXTERNAL_BUILD_TYPE, + RELEASE_BUILD_TYPE, + -> "https://express.tangem.com/v1/" + else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]") + }, + ) + } + + private fun createTangemTechModel(): Model { + return Model( + id = ApiConfig.ID.TangemTech, + expected = when (BuildConfig.BUILD_TYPE) { + DEBUG_BUILD_TYPE, + INTERNAL_BUILD_TYPE, + -> "https://devapi.tangem-tech.com/v1/" + MOCKED_BUILD_TYPE, + EXTERNAL_BUILD_TYPE, + RELEASE_BUILD_TYPE, + -> "https://api.tangem-tech.com/v1/" + else -> error("Unknown build type [${BuildConfig.BUILD_TYPE}]") + }, + ) + } + } +} \ No newline at end of file