From cda22215ec7dd7f0b309282908353aa25ff4ea42 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:15:57 +0200 Subject: [PATCH] Extract server info repo --- .../mealient/data/auth/AuthDataSource.kt | 9 +- .../data/auth/impl/AuthDataSourceImpl.kt | 14 +++- .../mealient/data/auth/impl/AuthRepoImpl.kt | 8 +- .../mealient/data/baseurl/ServerInfoRepo.kt | 11 +++ .../data/baseurl/ServerInfoRepoImpl.kt | 47 +++++++++++ ...BaseURLStorage.kt => ServerInfoStorage.kt} | 4 +- .../mealient/data/baseurl/ServerVersion.kt | 3 + .../data/baseurl/VersionDataSourceImpl.kt | 37 +++++++++ ...torageImpl.kt => ServerInfoStorageImpl.kt} | 10 +-- .../data/network/MealieDataSourceWrapper.kt | 83 +++++++------------ .../impl/RecipeImageUrlProviderImpl.kt | 6 +- .../gq/kirmanak/mealient/di/BaseURLModule.kt | 14 ++-- .../mealient/ui/baseurl/BaseURLViewModel.kt | 6 +- .../mealient/ui/splash/SplashViewModel.kt | 6 +- .../data/auth/impl/AuthRepoImplTest.kt | 14 ++-- ...plTest.kt => ServerInfoStorageImplTest.kt} | 8 +- .../network/MealieDataSourceV0WrapperTest.kt | 8 +- .../impl/RecipeImageUrlProviderImplTest.kt | 8 +- .../ui/baseurl/BaseURLViewModelTest.kt | 8 +- .../datasource/v1/MealieDataSourceV1.kt | 4 +- .../datasource/v1/MealieDataSourceV1Impl.kt | 19 ++--- 21 files changed, 205 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt rename app/src/main/java/gq/kirmanak/mealient/data/baseurl/{BaseURLStorage.kt => ServerInfoStorage.kt} (78%) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerVersion.kt create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt rename app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/{BaseURLStorageImpl.kt => ServerInfoStorageImpl.kt} (81%) rename app/src/test/java/gq/kirmanak/mealient/data/baseurl/{BaseURLStorageImplTest.kt => ServerInfoStorageImplTest.kt} (91%) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthDataSource.kt index 71ee822..c6a29f1 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthDataSource.kt @@ -1,8 +1,15 @@ package gq.kirmanak.mealient.data.auth +import gq.kirmanak.mealient.data.baseurl.ServerVersion + interface AuthDataSource { /** * Tries to acquire authentication token using the provided credentials */ - suspend fun authenticate(username: String, password: String, baseUrl: String): String + suspend fun authenticate( + username: String, + password: String, + baseUrl: String, + serverVersion: ServerVersion, + ): String } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt index 8716939..2e5f3fc 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthDataSourceImpl.kt @@ -1,15 +1,25 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource +import gq.kirmanak.mealient.data.baseurl.ServerVersion import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 import javax.inject.Inject import javax.inject.Singleton @Singleton class AuthDataSourceImpl @Inject constructor( private val v0Source: MealieDataSourceV0, + private val v1Source: MealieDataSourceV1, ) : AuthDataSource { - override suspend fun authenticate(username: String, password: String, baseUrl: String): String = - v0Source.authenticate(baseUrl, username, password) + override suspend fun authenticate( + username: String, + password: String, + baseUrl: String, + serverVersion: ServerVersion, + ): String = when (serverVersion) { + ServerVersion.V0 -> v0Source.authenticate(baseUrl, username, password) + ServerVersion.V1 -> v1Source.authenticate(baseUrl, username, password) + } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt index 2fc9b2c..165a701 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImpl.kt @@ -3,7 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.auth.AuthStorage -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.flow.Flow @@ -15,7 +15,7 @@ import javax.inject.Singleton class AuthRepoImpl @Inject constructor( private val authStorage: AuthStorage, private val authDataSource: AuthDataSource, - private val baseURLStorage: BaseURLStorage, + private val serverInfoRepo: ServerInfoRepo, private val logger: Logger, ) : AuthRepo { @@ -24,7 +24,9 @@ class AuthRepoImpl @Inject constructor( override suspend fun authenticate(email: String, password: String) { logger.v { "authenticate() called with: email = $email, password = $password" } - authDataSource.authenticate(email, password, baseURLStorage.requireBaseURL()) + val version = serverInfoRepo.getVersion() + val url = serverInfoRepo.requireUrl() + authDataSource.authenticate(email, password, url, version) .let { AUTH_HEADER_FORMAT.format(it) } .let { authStorage.setAuthHeader(it) } authStorage.setEmail(email) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt new file mode 100644 index 0000000..0517ff8 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt @@ -0,0 +1,11 @@ +package gq.kirmanak.mealient.data.baseurl + +interface ServerInfoRepo { + + suspend fun getUrl(): String? + + suspend fun requireUrl(): String + + suspend fun getVersion(): ServerVersion +} + diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt new file mode 100644 index 0000000..83e2998 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt @@ -0,0 +1,47 @@ +package gq.kirmanak.mealient.data.baseurl + +import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.logging.Logger +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ServerInfoRepoImpl @Inject constructor( + private val serverInfoStorage: ServerInfoStorage, + private val versionDataSource: VersionDataSource, + private val logger: Logger, +) : ServerInfoRepo { + + override suspend fun getUrl(): String? { + val result = serverInfoStorage.getBaseURL() + logger.v { "getUrl() returned: $result" } + return result + } + + override suspend fun requireUrl(): String { + val result = checkNotNull(getUrl()) { "Server URL was null when it was required" } + logger.v { "requireUrl() returned: $result" } + return result + } + + override suspend fun getVersion(): ServerVersion { + var version = serverInfoStorage.getServerVersion() + val serverVersion = if (version == null) { + logger.d { "getVersion: version is null, requesting" } + version = versionDataSource.getVersionInfo(requireUrl()).version + val result = determineServerVersion(version) + serverInfoStorage.storeServerVersion(version) + result + } else { + determineServerVersion(version) + } + logger.v { "getVersion() returned: $serverVersion from $version" } + return serverVersion + } + + private fun determineServerVersion(version: String): ServerVersion = when { + version.startsWith("v0") -> ServerVersion.V0 + version.startsWith("v1") -> ServerVersion.V1 + else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version")) + } +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt similarity index 78% rename from app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt rename to app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt index e629625..5863b40 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt @@ -1,11 +1,9 @@ package gq.kirmanak.mealient.data.baseurl -interface BaseURLStorage { +interface ServerInfoStorage { suspend fun getBaseURL(): String? - suspend fun requireBaseURL(): String - suspend fun storeBaseURL(baseURL: String, version: String) suspend fun storeServerVersion(version: String) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerVersion.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerVersion.kt new file mode 100644 index 0000000..0f133fc --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerVersion.kt @@ -0,0 +1,3 @@ +package gq.kirmanak.mealient.data.baseurl + +enum class ServerVersion { V0, V1 } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt new file mode 100644 index 0000000..9f9dd87 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt @@ -0,0 +1,37 @@ +package gq.kirmanak.mealient.data.baseurl + +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.extensions.toVersionInfo +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class VersionDataSourceImpl @Inject constructor( + private val v0Source: MealieDataSourceV0, + private val v1Source: MealieDataSourceV1, +) : VersionDataSource { + + override suspend fun getVersionInfo(baseUrl: String): VersionInfo { + val responses = coroutineScope { + val v0Deferred = async { + runCatchingExceptCancel { v0Source.getVersionInfo(baseUrl).toVersionInfo() } + } + val v1Deferred = async { + runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() } + } + listOf(v0Deferred, v1Deferred).awaitAll() + } + val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() } + if (firstSuccess == null) { + throw responses.firstNotNullOf { it.exceptionOrNull() } + } else { + return firstSuccess + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/BaseURLStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt similarity index 81% rename from app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/BaseURLStorageImpl.kt rename to app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt index cb77608..0f850f4 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/BaseURLStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt @@ -1,15 +1,15 @@ package gq.kirmanak.mealient.data.baseurl.impl import androidx.datastore.preferences.core.Preferences -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.data.storage.PreferencesStorage import javax.inject.Inject import javax.inject.Singleton @Singleton -class BaseURLStorageImpl @Inject constructor( +class ServerInfoStorageImpl @Inject constructor( private val preferencesStorage: PreferencesStorage, -) : BaseURLStorage { +) : ServerInfoStorage { private val baseUrlKey: Preferences.Key get() = preferencesStorage.baseUrlKey @@ -19,10 +19,6 @@ class BaseURLStorageImpl @Inject constructor( override suspend fun getBaseURL(): String? = getValue(baseUrlKey) - override suspend fun requireBaseURL(): String = checkNotNull(getBaseURL()) { - "Base URL was null when it was required" - } - override suspend fun storeBaseURL(baseURL: String, version: String) { preferencesStorage.storeValues( Pair(baseUrlKey, baseURL), diff --git a/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt b/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt index a9c605d..5d214c7 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapper.kt @@ -2,9 +2,8 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.add.AddRecipeDataSource import gq.kirmanak.mealient.data.auth.AuthRepo -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage -import gq.kirmanak.mealient.data.baseurl.VersionDataSource -import gq.kirmanak.mealient.data.baseurl.VersionInfo +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo +import gq.kirmanak.mealient.data.baseurl.ServerVersion import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo @@ -12,76 +11,52 @@ import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.extensions.toFullRecipeInfo import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo -import gq.kirmanak.mealient.extensions.toVersionInfo -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope import javax.inject.Inject import javax.inject.Singleton @Singleton class MealieDataSourceWrapper @Inject constructor( - private val baseURLStorage: BaseURLStorage, + private val serverInfoRepo: ServerInfoRepo, private val authRepo: AuthRepo, private val v0source: MealieDataSourceV0, private val v1Source: MealieDataSourceV1, -) : AddRecipeDataSource, RecipeDataSource, VersionDataSource { +) : AddRecipeDataSource, RecipeDataSource { - override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = - withAuthHeader { token -> v0source.addRecipe(getUrl(), token, recipe) } - - override suspend fun getVersionInfo(baseUrl: String): VersionInfo { - val responses = coroutineScope { - val v0Deferred = async { - runCatchingExceptCancel { v0source.getVersionInfo(baseUrl).toVersionInfo() } - } - val v1Deferred = async { - runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() } - } - listOf(v0Deferred, v1Deferred).awaitAll() - } - val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() } - if (firstSuccess == null) { - throw responses.firstNotNullOf { it.exceptionOrNull() } - } else { - return firstSuccess - } + override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = withAuthHeader { token -> + v0source.addRecipe(getUrl(), token, recipe) } - override suspend fun requestRecipes(start: Int, limit: Int): List = - withAuthHeader { token -> - val url = getUrl() - if (isV1()) { - v1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } - } else { + override suspend fun requestRecipes( + start: Int, + limit: Int + ): List = withAuthHeader { token -> + val url = getUrl() + when (getVersion()) { + ServerVersion.V0 -> { v0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } - } - - override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = - withAuthHeader { token -> - val url = getUrl() - if (isV1()) { - v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() - } else { - v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + ServerVersion.V1 -> { + // Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3 + val page = start / limit + 1 + v1Source.requestRecipes(url, token, page, limit).map { it.toRecipeSummaryInfo() } } } - - private suspend fun getUrl() = baseURLStorage.requireBaseURL() - - private suspend fun isV1(): Boolean { - var version = baseURLStorage.getServerVersion() - if (version == null) { - version = getVersionInfo(getUrl()).version - baseURLStorage.storeServerVersion(version) - } - return version.startsWith("v1") } + override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = withAuthHeader { token -> + val url = getUrl() + when (getVersion()) { + ServerVersion.V0 -> v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + ServerVersion.V1 -> v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + } + } + + private suspend fun getUrl() = serverInfoRepo.requireUrl() + + private suspend fun getVersion() = serverInfoRepo.getVersion() + private suspend inline fun withAuthHeader(block: (String?) -> T): T = runCatching { block(authRepo.getAuthHeader()) }.getOrElse { if (it is NetworkError.Unauthorized) { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImpl.kt index d5b30fa..5c693ec 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImpl.kt @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.data.recipes.impl -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.logging.Logger import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import javax.inject.Inject @@ -8,7 +8,7 @@ import javax.inject.Singleton @Singleton class RecipeImageUrlProviderImpl @Inject constructor( - private val baseURLStorage: BaseURLStorage, + private val serverInfoRepo: ServerInfoRepo, private val logger: Logger, ) : RecipeImageUrlProvider { @@ -16,7 +16,7 @@ class RecipeImageUrlProviderImpl @Inject constructor( logger.v { "generateImageUrl() called with: slug = $slug" } slug?.takeUnless { it.isBlank() } ?: return null val imagePath = IMAGE_PATH_FORMAT.format(slug) - val baseUrl = baseURLStorage.getBaseURL()?.takeUnless { it.isEmpty() } + val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() } val result = baseUrl?.toHttpUrlOrNull() ?.newBuilder() ?.addPathSegments(imagePath) diff --git a/app/src/main/java/gq/kirmanak/mealient/di/BaseURLModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/BaseURLModule.kt index 30300c3..2e6f1a6 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/BaseURLModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/BaseURLModule.kt @@ -4,10 +4,8 @@ import dagger.Binds import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage -import gq.kirmanak.mealient.data.baseurl.VersionDataSource -import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl -import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper +import gq.kirmanak.mealient.data.baseurl.* +import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl import javax.inject.Singleton @Module @@ -16,9 +14,13 @@ interface BaseURLModule { @Binds @Singleton - fun bindVersionDataSource(mealieDataSourceWrapper: MealieDataSourceWrapper): VersionDataSource + fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource @Binds @Singleton - fun bindBaseUrlStorage(baseURLStorageImpl: BaseURLStorageImpl): BaseURLStorage + fun bindBaseUrlStorage(baseURLStorageImpl: ServerInfoStorageImpl): ServerInfoStorage + + @Binds + @Singleton + fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt index bb3e3c7..f58adae 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.data.baseurl.VersionDataSource import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger @@ -15,7 +15,7 @@ import javax.inject.Inject @HiltViewModel class BaseURLViewModel @Inject constructor( - private val baseURLStorage: BaseURLStorage, + private val serverInfoStorage: ServerInfoStorage, private val versionDataSource: VersionDataSource, private val logger: Logger, ) : ViewModel() { @@ -36,7 +36,7 @@ class BaseURLViewModel @Inject constructor( val result = runCatchingExceptCancel { // If it returns proper version info then it must be a Mealie val version = versionDataSource.getVersionInfo(baseURL).version - baseURLStorage.storeBaseURL(baseURL, version) + serverInfoStorage.storeBaseURL(baseURL, version) } logger.i { "checkBaseURL: result is $result" } _uiState.value = OperationUiState.fromResult(result) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt index 8fe4eb4..399f860 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/splash/SplashViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavDirections import dagger.hilt.android.lifecycle.HiltViewModel -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -15,7 +15,7 @@ import javax.inject.Inject @HiltViewModel class SplashViewModel @Inject constructor( private val disclaimerStorage: DisclaimerStorage, - private val baseURLStorage: BaseURLStorage, + private val serverInfoRepo: ServerInfoRepo, ) : ViewModel() { private val _nextDestination = MutableLiveData() val nextDestination: LiveData = _nextDestination @@ -25,7 +25,7 @@ class SplashViewModel @Inject constructor( delay(1000) _nextDestination.value = when { !disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment() - baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment() + serverInfoRepo.getUrl() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment() else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment() } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt index e180433..13e6341 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthRepoImplTest.kt @@ -4,7 +4,7 @@ import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.auth.AuthStorage -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL @@ -27,7 +27,7 @@ class AuthRepoImplTest { lateinit var dataSource: AuthDataSource @MockK - lateinit var baseURLStorage: BaseURLStorage + lateinit var serverInfoStorage: ServerInfoStorage @MockK(relaxUnitFun = true) lateinit var storage: AuthStorage @@ -40,7 +40,7 @@ class AuthRepoImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = AuthRepoImpl(storage, dataSource, baseURLStorage, logger) + subject = AuthRepoImpl(storage, dataSource, serverInfoStorage, logger) } @Test @@ -58,7 +58,7 @@ class AuthRepoImplTest { eq(TEST_BASE_URL) ) } returns TEST_TOKEN - coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL subject.authenticate(TEST_USERNAME, TEST_PASSWORD) coVerifyAll { storage.setAuthHeader(TEST_AUTH_HEADER) @@ -71,7 +71,7 @@ class AuthRepoImplTest { @Test fun `when authenticate fails then does not change storage`() = runTest { coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException() - coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL runCatching { subject.authenticate("invalid", "") } confirmVerified(storage) } @@ -107,7 +107,7 @@ class AuthRepoImplTest { fun `when invalidate with credentials then calls authenticate`() = runTest { coEvery { storage.getEmail() } returns TEST_USERNAME coEvery { storage.getPassword() } returns TEST_PASSWORD - coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL)) } returns TEST_TOKEN @@ -121,7 +121,7 @@ class AuthRepoImplTest { fun `when invalidate with credentials and auth fails then clears email`() = runTest { coEvery { storage.getEmail() } returns "invalid" coEvery { storage.getPassword() } returns "" - coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException() subject.invalidateAuthHeader() coVerify { storage.setEmail(null) } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt similarity index 91% rename from app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt rename to app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt index 9197830..469a1dd 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt @@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.baseurl import androidx.datastore.preferences.core.stringPreferencesKey import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl +import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl import gq.kirmanak.mealient.data.storage.PreferencesStorage import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -15,19 +15,19 @@ import org.junit.Before import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) -class BaseURLStorageImplTest { +class ServerInfoStorageImplTest { @MockK(relaxUnitFun = true) lateinit var preferencesStorage: PreferencesStorage - lateinit var subject: BaseURLStorage + lateinit var subject: ServerInfoStorage private val baseUrlKey = stringPreferencesKey("baseUrlKey") @Before fun setUp() { MockKAnnotations.init(this) - subject = BaseURLStorageImpl(preferencesStorage) + subject = ServerInfoStorageImpl(preferencesStorage) every { preferencesStorage.baseUrlKey } returns baseUrlKey } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt index c005068..9d1d6c3 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.auth.AuthRepo -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER @@ -21,7 +21,7 @@ import java.io.IOException class MealieDataSourceV0WrapperTest { @MockK - lateinit var baseURLStorage: BaseURLStorage + lateinit var serverInfoStorage: ServerInfoStorage @MockK(relaxUnitFun = true) lateinit var authRepo: AuthRepo @@ -34,12 +34,12 @@ class MealieDataSourceV0WrapperTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceWrapper(baseURLStorage, authRepo, mealieDataSourceV0) + subject = MealieDataSourceWrapper(serverInfoStorage, authRepo, mealieDataSourceV0) } @Test fun `when withAuthHeader fails with Unauthorized then invalidates auth`() = runTest { - coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER coEvery { mealieDataSourceV0.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt index 230f70b..c219eb2 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeImageUrlProviderImplTest.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.recipes.impl import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.logging.Logger import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -17,7 +17,7 @@ class RecipeImageUrlProviderImplTest { lateinit var subject: RecipeImageUrlProvider @MockK - lateinit var baseURLStorage: BaseURLStorage + lateinit var serverInfoStorage: ServerInfoStorage @MockK(relaxUnitFun = true) lateinit var logger: Logger @@ -25,7 +25,7 @@ class RecipeImageUrlProviderImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = RecipeImageUrlProviderImpl(baseURLStorage, logger) + subject = RecipeImageUrlProviderImpl(serverInfoStorage, logger) prepareBaseURL("https://google.com/") } @@ -81,6 +81,6 @@ class RecipeImageUrlProviderImplTest { } private fun prepareBaseURL(baseURL: String?) { - coEvery { baseURLStorage.getBaseURL() } returns baseURL + coEvery { serverInfoStorage.getBaseURL() } returns baseURL } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt index 4c91bf1..ca462c4 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/baseurl/BaseURLViewModelTest.kt @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.ui.baseurl -import gq.kirmanak.mealient.data.baseurl.BaseURLStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.data.baseurl.VersionDataSource import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.logging.Logger @@ -21,7 +21,7 @@ import org.junit.Test class BaseURLViewModelTest : RobolectricTest() { @MockK(relaxUnitFun = true) - lateinit var baseURLStorage: BaseURLStorage + lateinit var serverInfoStorage: ServerInfoStorage @MockK lateinit var versionDataSource: VersionDataSource @@ -34,7 +34,7 @@ class BaseURLViewModelTest : RobolectricTest() { @Before fun setUp() { MockKAnnotations.init(this) - subject = BaseURLViewModel(baseURLStorage, versionDataSource, logger) + subject = BaseURLViewModel(serverInfoStorage, versionDataSource, logger) } @Test @@ -44,6 +44,6 @@ class BaseURLViewModelTest : RobolectricTest() { } returns VersionInfo(TEST_VERSION) subject.saveBaseUrl(TEST_BASE_URL) advanceUntilIdle() - coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) } + coVerify { serverInfoStorage.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) } } } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt index 0fa422d..7c888ec 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt @@ -29,8 +29,8 @@ interface MealieDataSourceV1 { suspend fun requestRecipes( baseUrl: String, token: String?, - start: Int, - limit: Int, + page: Int, + perPage: Int, ): List suspend fun requestRecipeInfo( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt index d8109c0..718d279 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt @@ -48,18 +48,13 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun requestRecipes( baseUrl: String, token: String?, - start: Int, - limit: Int - ): List { - // Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3 - val perPage = limit - val page = start / perPage + 1 - return makeCall( - block = { getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, - logMethod = { "requestRecipesV1" }, - logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } - ).map { it.items }.getOrThrowUnauthorized() - } + page: Int, + perPage: Int + ): List = makeCall( + block = { getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, + logMethod = { "requestRecipesV1" }, + logParameters = { "baseUrl = $baseUrl, token = $token, page = $page, perPage = $perPage" } + ).map { it.items }.getOrThrowUnauthorized() override suspend fun requestRecipeInfo( baseUrl: String,