From 95205f8ffeed8eec09b3b7ee2d1ad1550aed9ee2 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 7 Aug 2022 17:19:35 +0200 Subject: [PATCH 01/35] Initialize v1 support --- .../mealient/data/recipes/RecipeRepo.kt | 2 +- .../mealient/data/recipes/db/RecipeStorage.kt | 2 +- .../data/recipes/db/RecipeStorageImpl.kt | 2 +- .../data/recipes/impl/RecipeRepoImpl.kt | 2 +- .../extensions/RemoteToLocalMappings.kt | 4 +- .../ui/recipes/images/RecipeModelLoader.kt | 2 +- .../ui/recipes/info/RecipeInfoViewModel.kt | 2 +- app/src/main/res/navigation/nav_graph.xml | 2 +- .../3.json | 404 ++++++++++++++++++ .../gq/kirmanak/mealient/database/AppDb.kt | 4 +- .../mealient/database/DatabaseModule.kt | 4 +- .../mealient/database/recipe/RecipeDao.kt | 6 +- .../recipe/entity/CategoryRecipeEntity.kt | 2 +- .../database/recipe/entity/RecipeEntity.kt | 2 +- .../recipe/entity/RecipeIngredientEntity.kt | 4 +- .../recipe/entity/RecipeInstructionEntity.kt | 2 +- .../recipe/entity/RecipeSummaryEntity.kt | 2 +- .../database/recipe/entity/TagRecipeEntity.kt | 2 +- .../datasource/MealieDataSourceImpl.kt | 31 +- .../mealient/datasource/MealieService.kt | 8 + .../models/GetRecipeIngredientResponse.kt | 2 +- .../datasource/models/GetRecipeResponse.kt | 2 +- .../models/GetRecipeSummaryResponse.kt | 2 +- .../datasource/models/GetRecipesResponseV1.kt | 9 + 24 files changed, 478 insertions(+), 26 deletions(-) create mode 100644 database/schemas/gq.kirmanak.mealient.database.AppDb/3.json create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt index 503a7b2..9c42bf0 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt @@ -9,5 +9,5 @@ interface RecipeRepo { suspend fun clearLocalData() - suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo + suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeInfo } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt index 0ff12a5..a36e84d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt @@ -17,5 +17,5 @@ interface RecipeStorage { suspend fun saveRecipeInfo(recipe: GetRecipeResponse) - suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo + suspend fun queryRecipeInfo(recipeId: String): FullRecipeInfo } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index e5d777f..702f5e3 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -132,7 +132,7 @@ class RecipeStorageImpl @Inject constructor( } } - override suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo { + override suspend fun queryRecipeInfo(recipeId: String): FullRecipeInfo { logger.v { "queryRecipeInfo() called with: recipeId = $recipeId" } val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) { "Can't find recipe by id $recipeId in DB" diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt index 942fe5f..5398aae 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt @@ -38,7 +38,7 @@ class RecipeRepoImpl @Inject constructor( storage.clearAllLocalData() } - override suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo { + override suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeInfo { logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" } runCatchingExceptCancel { diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 2cd87a3..bf13089 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -13,7 +13,7 @@ fun GetRecipeResponse.toRecipeEntity() = RecipeEntity( recipeYield = recipeYield ) -fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: Long) = +fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: String) = RecipeIngredientEntity( recipeId = remoteId, title = title, @@ -24,7 +24,7 @@ fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: Long) = quantity = quantity ) -fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: Long) = +fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: String) = RecipeInstructionEntity( recipeId = remoteId, title = title, diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt index 45a7e4c..edb9704 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt @@ -44,7 +44,7 @@ class RecipeModelLoader private constructor( options: Options? ): String? { logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" } - return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.slug) } + return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.remoteId) } } override fun getHeaders( diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt index cf40aba..5f1591b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt @@ -20,7 +20,7 @@ class RecipeInfoViewModel @Inject constructor( private val _uiState = MutableLiveData(RecipeInfoUiState()) val uiState: LiveData get() = _uiState - fun loadRecipeInfo(recipeId: Long, recipeSlug: String) { + fun loadRecipeInfo(recipeId: String, recipeSlug: String) { logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" } _uiState.value = RecipeInfoUiState() viewModelScope.launch { diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 47fa15b..01445d5 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -36,7 +36,7 @@ app:argType="string" /> + app:argType="string" /> getVersionInfoV1(baseUrl) + is SocketTimeoutException, + is ConnectException -> throw NetworkError.NoServerConnection(it) + else -> throw NetworkError.MalformedUrl(it) + } + } + + private suspend fun getVersionInfoV1(baseUrl: String): VersionResponse = makeCall( + block = { getVersion("$baseUrl/api/app/about") }, + logMethod = { "getVersionInfoV1" }, + logParameters = { "baseUrl = $baseUrl" }, ).getOrElse { throw when (it) { is HttpException, is SerializationException -> NetworkError.NotMealie(it) @@ -58,7 +71,23 @@ class MealieDataSourceImpl @Inject constructor( block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, logMethod = { "requestRecipes" }, logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } - ).getOrThrowUnauthorized() + ).getOrElse { + val code = (it as? HttpException)?.code() ?: throw it + if (code == 404) requestRecipesV1(baseUrl, token, start, limit) else throw it + } + + private suspend fun requestRecipesV1( + 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 = { getRecipeSummaryV1("$baseUrl/api/recipes", token, page, perPage) }, + logMethod = { "requestRecipesV1" }, + logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } + ).map { it.items }.getOrThrowUnauthorized() + } override suspend fun requestRecipeInfo( baseUrl: String, token: String?, slug: String diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt index 9750cf9..6067062 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt @@ -34,6 +34,14 @@ interface MealieService { @Query("limit") limit: Int, ): List + @GET + suspend fun getRecipeSummaryV1( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + @Query("page") page: Int, + @Query("perPage") perPage: Int, + ): GetRecipesResponseV1 + @GET suspend fun getRecipe( @Url url: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt index e00a9fb..257d038 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt @@ -10,5 +10,5 @@ data class GetRecipeIngredientResponse( @SerialName("unit") val unit: String = "", @SerialName("food") val food: String = "", @SerialName("disableAmount") val disableAmount: Boolean, - @SerialName("quantity") val quantity: Int, + @SerialName("quantity") val quantity: Double, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt index 3197d75..36f9ee4 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable data class GetRecipeResponse( - @SerialName("id") val remoteId: Long, + @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("slug") val slug: String, @SerialName("image") val image: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt index f9fc613..828811e 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable data class GetRecipeSummaryResponse( - @SerialName("id") val remoteId: Long, + @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("slug") val slug: String, @SerialName("image") val image: String?, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt new file mode 100644 index 0000000..5977b74 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt @@ -0,0 +1,9 @@ +package gq.kirmanak.mealient.datasource.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetRecipesResponseV1( + @SerialName("items") val items: List, +) \ No newline at end of file From 288ad55995a0511da3372b5c819a5850208ca0c6 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 14 Aug 2022 18:25:18 +0200 Subject: [PATCH 02/35] Update versions of dependencies --- gradle/libs.versions.toml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec53334..593d8b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://maven.google.com/web/index.html?q=com.android.tools.build#com.android.tools.build:gradle -androidGradlePlugin = "7.2.1" +androidGradlePlugin = "7.2.2" # https://developers.google.com/android/guides/google-services-plugin googleServicesPlugin = "4.3.13" # https://mvnrepository.com/artifact/com.google.firebase/firebase-crashlytics-gradle @@ -10,9 +10,9 @@ kotlin = "1.7.10" # https://developer.android.com/jetpack/androidx/releases/navigation googleNavigation = "2.5.1" # https://dagger.dev/hilt/gradle-setup -hilt = "2.43.1" +hilt = "2.43.2" # https://github.com/protocolbuffers/protobuf/releases -protobuf = "3.21.4" +protobuf = "3.21.5" # https://github.com/google/protobuf-gradle-plugin/releases protobufPlugin = "0.8.19" # https://plugins.gradle.org/plugin/org.sonarqube @@ -20,13 +20,13 @@ sonarqube = "3.4.0.2513" # https://plugins.gradle.org/plugin/nl.neotech.plugin.rootcoverage rootCoverage = "1.5.3" # https://plugins.gradle.org/plugin/com.guardsquare.appsweep -appsweep = "1.1.0" +appsweep = "1.2.0" # https://github.com/material-components/material-components-android/releases material = "1.6.1" # https://developer.android.com/kotlin/ktx#core coreKtx = "1.8.0" # https://developer.android.com/jetpack/androidx/releases/appcompat -appcompat = "1.4.2" +appcompat = "1.5.0" # https://developer.android.com/jetpack/androidx/releases/constraintlayout contraintLayout = "2.1.4" # https://developer.android.com/jetpack/androidx/releases/swiperefreshlayout @@ -56,7 +56,7 @@ datastore = "1.0.0" # https://developer.android.com/jetpack/androidx/releases/security security = "1.0.0" # https://mvnrepository.com/artifact/com.google.firebase/firebase-bom?repo=google -firebase = "30.3.1" +firebase = "30.3.2" # https://github.com/junit-team/junit4/releases junit = "4.13.2" # https://developer.android.com/jetpack/androidx/releases/test @@ -74,7 +74,7 @@ leakcanary = "2.9.1" # https://github.com/ChuckerTeam/chucker/releases chucker = "3.5.2" # https://developer.android.com/studio/write/java8-support#library-desugaring -desugar = "1.1.5" +desugar = "1.1.6" # https://github.com/google/ksp/releases kspPlugin = "1.7.10-1.0.6" From ac8395134b99b3450d51c979e7fdba2fad14d4eb Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 20 Aug 2022 12:49:06 +0200 Subject: [PATCH 03/35] Create V1 files --- .../mealient/datasource/MealieDataSourceV1.kt | 41 +++++++++++++++ .../datasource/MealieDataSourceV1Impl.kt | 41 +++++++++++++++ .../mealient/datasource/MealieServiceV1.kt | 50 +++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt new file mode 100644 index 0000000..6125e5d --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt @@ -0,0 +1,41 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.VersionResponse + +interface MealieDataSourceV1 { + + suspend fun addRecipe( + baseUrl: String, + token: String?, + recipe: AddRecipeRequest, + ): String + + /** + * Tries to acquire authentication token using the provided credentials + */ + suspend fun authenticate( + baseUrl: String, + username: String, + password: String, + ): String + + suspend fun getVersionInfo( + baseUrl: String, + ): VersionResponse + + suspend fun requestRecipes( + baseUrl: String, + token: String?, + start: Int, + limit: Int, + ): List + + suspend fun requestRecipeInfo( + baseUrl: String, + token: String?, + slug: String, + ): GetRecipeResponse +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt new file mode 100644 index 0000000..30a5310 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt @@ -0,0 +1,41 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.VersionResponse + +class MealieDataSourceV1Impl : MealieDataSourceV1 { + override suspend fun addRecipe( + baseUrl: String, + token: String?, + recipe: AddRecipeRequest + ): String { + TODO("Not yet implemented") + } + + override suspend fun authenticate(baseUrl: String, username: String, password: String): String { + TODO("Not yet implemented") + } + + override suspend fun getVersionInfo(baseUrl: String): VersionResponse { + TODO("Not yet implemented") + } + + override suspend fun requestRecipes( + baseUrl: String, + token: String?, + start: Int, + limit: Int + ): List { + TODO("Not yet implemented") + } + + override suspend fun requestRecipeInfo( + baseUrl: String, + token: String?, + slug: String + ): GetRecipeResponse { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt new file mode 100644 index 0000000..8e7dcb8 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt @@ -0,0 +1,50 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME +import gq.kirmanak.mealient.datasource.models.* +import retrofit2.http.* + +interface MealieServiceV1 { + + @FormUrlEncoded + @POST + suspend fun getToken( + @Url url: String, + @Field("username") username: String, + @Field("password") password: String, + ): GetTokenResponse + + @POST + suspend fun addRecipe( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + @Body addRecipeRequest: AddRecipeRequest, + ): String + + @GET + suspend fun getVersion( + @Url url: String, + ): VersionResponse + + @GET + suspend fun getRecipeSummary( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + @Query("start") start: Int, + @Query("limit") limit: Int, + ): List + + @GET + suspend fun getRecipeSummaryV1( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + @Query("page") page: Int, + @Query("perPage") perPage: Int, + ): GetRecipesResponseV1 + + @GET + suspend fun getRecipe( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + ): GetRecipeResponse +} \ No newline at end of file From 1f5234d12341786f29bf250fdc683dab9a5bf762 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 16:40:19 +0200 Subject: [PATCH 04/35] Allow viewing recipes on V1 --- .../data/network/MealieDataSourceWrapper.kt | 42 +++++++-- .../mealient/data/recipes/db/RecipeStorage.kt | 6 +- .../data/recipes/db/RecipeStorageImpl.kt | 6 +- .../data/recipes/network/RecipeDataSource.kt | 4 +- .../extensions/RemoteToLocalMappings.kt | 3 +- .../mealient/datasource/DataSourceModule.kt | 11 +++ .../datasource/MealieDataSourceImpl.kt | 15 +--- .../datasource/MealieDataSourceV1Impl.kt | 41 --------- .../mealient/datasource/MealieService.kt | 8 -- .../models/GetRecipeSummaryResponse.kt | 2 +- .../datasource/{ => v1}/MealieDataSourceV1.kt | 6 +- .../datasource/v1/MealieDataSourceV1Impl.kt | 90 +++++++++++++++++++ .../datasource/{ => v1}/MealieServiceV1.kt | 11 +-- .../v1/models/GetRecipeSummaryResponseV1.kt | 24 +++++ .../{ => v1}/models/GetRecipesResponseV1.kt | 4 +- 15 files changed, 178 insertions(+), 95 deletions(-) delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{ => v1}/MealieDataSourceV1.kt (84%) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{ => v1}/MealieServiceV1.kt (78%) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{ => v1}/models/GetRecipesResponseV1.kt (77%) 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 4bb6a63..381cdef 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 @@ -9,8 +9,10 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource.models.AddRecipeRequest import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.extensions.toVersionInfo import javax.inject.Inject import javax.inject.Singleton @@ -20,28 +22,52 @@ class MealieDataSourceWrapper @Inject constructor( private val baseURLStorage: BaseURLStorage, private val authRepo: AuthRepo, private val mealieDataSource: MealieDataSource, + private val mealieDataSourceV1: MealieDataSourceV1, ) : AddRecipeDataSource, RecipeDataSource, VersionDataSource { override suspend fun addRecipe(recipe: AddRecipeRequest): String = - withAuthHeader { token -> addRecipe(getUrl(), token, recipe) } + withAuthHeader { token -> mealieDataSource.addRecipe(getUrl(), token, recipe) } override suspend fun getVersionInfo(baseUrl: String): VersionInfo = mealieDataSource.getVersionInfo(baseUrl).toVersionInfo() - override suspend fun requestRecipes(start: Int, limit: Int): List = - withAuthHeader { token -> requestRecipes(getUrl(), token, start, limit) } + override suspend fun requestRecipes(start: Int, limit: Int): List = + withAuthHeader { token -> + runCatchingExceptCancel { + mealieDataSource.requestRecipes(getUrl(), token, start, limit).map { + GetRecipeSummaryResponseV1( + remoteId = it.remoteId.toString(), + name = it.name, + slug = it.slug, + image = it.image, + description = it.description, + recipeCategories = it.recipeCategories, + tags = it.tags, + rating = it.rating, + dateAdded = it.dateAdded, + dateUpdated = it.dateUpdated, + ) + } + }.getOrElse { + if (it is NetworkError.NotMealie) { + mealieDataSourceV1.requestRecipes(getUrl(), token, start, limit) + } else { + throw it + } + } + } override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse = - withAuthHeader { token -> requestRecipeInfo(getUrl(), token, slug) } + withAuthHeader { token -> mealieDataSource.requestRecipeInfo(getUrl(), token, slug) } private suspend fun getUrl() = baseURLStorage.requireBaseURL() - private suspend inline fun withAuthHeader(block: MealieDataSource.(String?) -> T): T = - mealieDataSource.runCatching { block(authRepo.getAuthHeader()) }.getOrElse { + private suspend inline fun withAuthHeader(block: (String?) -> T): T = + runCatching { block(authRepo.getAuthHeader()) }.getOrElse { if (it is NetworkError.Unauthorized) { authRepo.invalidateAuthHeader() // Trying again with new authentication header - mealieDataSource.block(authRepo.getAuthHeader()) + block(authRepo.getAuthHeader()) } else { throw it } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt index a36e84d..3b0d367 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt @@ -4,14 +4,14 @@ import androidx.paging.PagingSource import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 interface RecipeStorage { - suspend fun saveRecipes(recipes: List) + suspend fun saveRecipes(recipes: List) fun queryRecipes(): PagingSource - suspend fun refreshAll(recipes: List) + suspend fun refreshAll(recipes: List) suspend fun clearAllLocalData() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index 702f5e3..ef203d8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -6,7 +6,7 @@ import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.extensions.recipeEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity @@ -23,7 +23,7 @@ class RecipeStorageImpl @Inject constructor( private val recipeDao: RecipeDao by lazy { db.recipeDao() } override suspend fun saveRecipes( - recipes: List + recipes: List ) = db.withTransaction { logger.v { "saveRecipes() called with $recipes" } @@ -96,7 +96,7 @@ class RecipeStorageImpl @Inject constructor( return recipeDao.queryRecipesByPages() } - override suspend fun refreshAll(recipes: List) { + override suspend fun refreshAll(recipes: List) { logger.v { "refreshAll() called with: recipes = $recipes" } db.withTransaction { recipeDao.removeAllRecipes() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt index 5e82886..02e56b9 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.data.recipes.network import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 interface RecipeDataSource { - suspend fun requestRecipes(start: Int, limit: Int): List + suspend fun requestRecipes(start: Int, limit: Int): List suspend fun requestRecipeInfo(slug: String): GetRecipeResponse } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index bf13089..1587186 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -6,6 +6,7 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft fun GetRecipeResponse.toRecipeEntity() = RecipeEntity( @@ -31,7 +32,7 @@ fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: String) = text = text ) -fun GetRecipeSummaryResponse.recipeEntity() = RecipeSummaryEntity( +fun GetRecipeSummaryResponseV1.recipeEntity() = RecipeSummaryEntity( remoteId = remoteId, name = name, slug = slug, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt index 8d29930..ae9e517 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt @@ -6,6 +6,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl +import gq.kirmanak.mealient.datasource.v1.MealieServiceV1 import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okhttp3.MediaType.Companion.toMediaType @@ -52,6 +55,10 @@ interface DataSourceModule { fun provideMealieService(retrofit: Retrofit): MealieService = retrofit.create() + @Provides + @Singleton + fun provideMealieServiceV1(retrofit: Retrofit): MealieServiceV1 = + retrofit.create() } @Binds @@ -65,4 +72,8 @@ interface DataSourceModule { @Binds @Singleton fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceImpl): MealieDataSource + + @Binds + @Singleton + fun bindMealieDataSourceV1(mealientDataSourceImpl: MealieDataSourceV1Impl): MealieDataSourceV1 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt index 981bac7..e87c7c9 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt @@ -73,20 +73,7 @@ class MealieDataSourceImpl @Inject constructor( logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } ).getOrElse { val code = (it as? HttpException)?.code() ?: throw it - if (code == 404) requestRecipesV1(baseUrl, token, start, limit) else throw it - } - - private suspend fun requestRecipesV1( - 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 = { getRecipeSummaryV1("$baseUrl/api/recipes", token, page, perPage) }, - logMethod = { "requestRecipesV1" }, - logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } - ).map { it.items }.getOrThrowUnauthorized() + if (code == 404) throw NetworkError.NotMealie(it) else throw it } override suspend fun requestRecipeInfo( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt deleted file mode 100644 index 30a5310..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1Impl.kt +++ /dev/null @@ -1,41 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse -import gq.kirmanak.mealient.datasource.models.VersionResponse - -class MealieDataSourceV1Impl : MealieDataSourceV1 { - override suspend fun addRecipe( - baseUrl: String, - token: String?, - recipe: AddRecipeRequest - ): String { - TODO("Not yet implemented") - } - - override suspend fun authenticate(baseUrl: String, username: String, password: String): String { - TODO("Not yet implemented") - } - - override suspend fun getVersionInfo(baseUrl: String): VersionResponse { - TODO("Not yet implemented") - } - - override suspend fun requestRecipes( - baseUrl: String, - token: String?, - start: Int, - limit: Int - ): List { - TODO("Not yet implemented") - } - - override suspend fun requestRecipeInfo( - baseUrl: String, - token: String?, - slug: String - ): GetRecipeResponse { - TODO("Not yet implemented") - } -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt index 6067062..9750cf9 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt @@ -34,14 +34,6 @@ interface MealieService { @Query("limit") limit: Int, ): List - @GET - suspend fun getRecipeSummaryV1( - @Url url: String, - @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Query("page") page: Int, - @Query("perPage") perPage: Int, - ): GetRecipesResponseV1 - @GET suspend fun getRecipe( @Url url: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt index 828811e..4fe3b80 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable data class GetRecipeSummaryResponse( - @SerialName("id") val remoteId: String, + @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, @SerialName("slug") val slug: String, @SerialName("image") val image: String?, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt similarity index 84% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt index 6125e5d..33fd5a3 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource +package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.models.AddRecipeRequest import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse import gq.kirmanak.mealient.datasource.models.VersionResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 interface MealieDataSourceV1 { @@ -31,7 +31,7 @@ interface MealieDataSourceV1 { token: String?, start: Int, limit: Int, - ): List + ): List suspend fun requestRecipeInfo( baseUrl: String, 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 new file mode 100644 index 0000000..39c4113 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt @@ -0,0 +1,90 @@ +package gq.kirmanak.mealient.datasource.v1 + +import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.models.VersionResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.logging.Logger +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import retrofit2.HttpException +import java.net.ConnectException +import java.net.SocketTimeoutException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MealieDataSourceV1Impl @Inject constructor( + private val logger: Logger, + private val mealieService: MealieServiceV1, + private val json: Json, +) : MealieDataSourceV1 { + + override suspend fun addRecipe( + baseUrl: String, + token: String?, + recipe: AddRecipeRequest + ): String { + TODO("Not yet implemented") + } + + override suspend fun authenticate(baseUrl: String, username: String, password: String): String { + TODO("Not yet implemented") + } + + override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall( + block = { getVersion("$baseUrl/api/app/about") }, + logMethod = { "getVersionInfo" }, + logParameters = { "baseUrl = $baseUrl" }, + ).getOrElse { + throw when (it) { + is HttpException, is SerializationException -> NetworkError.NotMealie(it) + is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it) + else -> NetworkError.MalformedUrl(it) + } + } + + 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() + } + + override suspend fun requestRecipeInfo( + baseUrl: String, + token: String?, + slug: String + ): GetRecipeResponse { + TODO("Not yet implemented") + } + + private suspend inline fun makeCall( + crossinline block: suspend MealieServiceV1.() -> T, + crossinline logMethod: () -> String, + crossinline logParameters: () -> String, + ): Result { + logger.v { "${logMethod()} called with: ${logParameters()}" } + return mealieService.runCatching { block() } + .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } + .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } + } +} + +private fun Result.getOrThrowUnauthorized(): T = getOrElse { + throw if (it is HttpException && it.code() in listOf(401, 403)) { + NetworkError.Unauthorized(it) + } else { + it + } +} diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt similarity index 78% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index 8e7dcb8..34b0edc 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -1,7 +1,8 @@ -package gq.kirmanak.mealient.datasource +package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 import retrofit2.http.* interface MealieServiceV1 { @@ -28,14 +29,6 @@ interface MealieServiceV1 { @GET suspend fun getRecipeSummary( - @Url url: String, - @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Query("start") start: Int, - @Query("limit") limit: Int, - ): List - - @GET - suspend fun getRecipeSummaryV1( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, @Query("page") page: Int, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt new file mode 100644 index 0000000..3ec4a06 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt @@ -0,0 +1,24 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetRecipeSummaryResponseV1( + @SerialName("id") val remoteId: String, + @SerialName("name") val name: String, + @SerialName("slug") val slug: String, + @SerialName("image") val image: String?, + @SerialName("description") val description: String = "", + @SerialName("recipeCategory") val recipeCategories: List, + @SerialName("tags") val tags: List, + @SerialName("rating") val rating: Int?, + @SerialName("dateAdded") val dateAdded: LocalDate, + @SerialName("dateUpdated") val dateUpdated: LocalDateTime +) { + override fun toString(): String { + return "GetRecipeSummaryResponseV1(remoteId=$remoteId, name='$name')" + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt similarity index 77% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt index 5977b74..08105d4 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource.v1.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class GetRecipesResponseV1( - @SerialName("items") val items: List, + @SerialName("items") val items: List, ) \ No newline at end of file From da33bc27307935e095f8c770b9e6283dd86048d7 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 16:50:13 +0200 Subject: [PATCH 05/35] Remove v1 version from v0 data source --- .../data/network/MealieDataSourceWrapper.kt | 10 +++++++++- .../datasource/MealieDataSourceImpl.kt | 19 ++++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) 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 381cdef..bd91b0c 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 @@ -29,7 +29,15 @@ class MealieDataSourceWrapper @Inject constructor( withAuthHeader { token -> mealieDataSource.addRecipe(getUrl(), token, recipe) } override suspend fun getVersionInfo(baseUrl: String): VersionInfo = - mealieDataSource.getVersionInfo(baseUrl).toVersionInfo() + runCatchingExceptCancel { + mealieDataSource.getVersionInfo(baseUrl).toVersionInfo() + }.getOrElse { + if (it is NetworkError.NotMealie) { + mealieDataSourceV1.getVersionInfo(baseUrl).toVersionInfo() + } else { + throw it + } + } override suspend fun requestRecipes(start: Int, limit: Int): List = withAuthHeader { token -> diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt index e87c7c9..9d4a6fd 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt @@ -46,25 +46,14 @@ class MealieDataSourceImpl @Inject constructor( logParameters = { "baseUrl = $baseUrl" }, ).getOrElse { when (it) { - is HttpException, is SerializationException -> getVersionInfoV1(baseUrl) - is SocketTimeoutException, - is ConnectException -> throw NetworkError.NoServerConnection(it) + is HttpException, is SerializationException -> throw NetworkError.NotMealie(it) + is SocketTimeoutException, is ConnectException -> throw NetworkError.NoServerConnection( + it + ) else -> throw NetworkError.MalformedUrl(it) } } - private suspend fun getVersionInfoV1(baseUrl: String): VersionResponse = makeCall( - block = { getVersion("$baseUrl/api/app/about") }, - logMethod = { "getVersionInfoV1" }, - logParameters = { "baseUrl = $baseUrl" }, - ).getOrElse { - throw when (it) { - is HttpException, is SerializationException -> NetworkError.NotMealie(it) - is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it) - else -> NetworkError.MalformedUrl(it) - } - } - override suspend fun requestRecipes( baseUrl: String, token: String?, start: Int, limit: Int ): List = makeCall( From 1502d3db9d4278e4742677098a9d50f27fd2e10e Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 16:57:21 +0200 Subject: [PATCH 06/35] Return V1 version response from V1 data source --- .../gq/kirmanak/mealient/data/baseurl/VersionInfo.kt | 2 -- .../mealient/extensions/RemoteToLocalMappings.kt | 5 ++++- .../mealient/datasource/v1/MealieDataSourceV1.kt | 4 ++-- .../mealient/datasource/v1/MealieDataSourceV1Impl.kt | 4 ++-- .../mealient/datasource/v1/MealieServiceV1.kt | 3 ++- .../datasource/v1/models/VersionResponseV1.kt | 12 ++++++++++++ 6 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionInfo.kt index 0e24c3b..a1eb010 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionInfo.kt @@ -1,7 +1,5 @@ package gq.kirmanak.mealient.data.baseurl data class VersionInfo( - val production: Boolean, val version: String, - val demoStatus: Boolean, ) \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 1587186..e2ec913 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -7,6 +7,7 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.models.* import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft fun GetRecipeResponse.toRecipeEntity() = RecipeEntity( @@ -43,7 +44,9 @@ fun GetRecipeSummaryResponseV1.recipeEntity() = RecipeSummaryEntity( dateUpdated = dateUpdated, ) -fun VersionResponse.toVersionInfo() = VersionInfo(production, version, demoStatus) +fun VersionResponse.toVersionInfo() = VersionInfo(version) + +fun VersionResponseV1.toVersionInfo() = VersionInfo(version) fun AddRecipeDraft.toAddRecipeRequest() = AddRecipeRequest( name = recipeName, 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 33fd5a3..a7eb284 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 @@ -2,8 +2,8 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.models.AddRecipeRequest import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.VersionResponse import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 interface MealieDataSourceV1 { @@ -24,7 +24,7 @@ interface MealieDataSourceV1 { suspend fun getVersionInfo( baseUrl: String, - ): VersionResponse + ): VersionResponseV1 suspend fun requestRecipes( baseUrl: String, 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 39c4113..77961ee 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 @@ -3,8 +3,8 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.models.AddRecipeRequest import gq.kirmanak.mealient.datasource.models.GetRecipeResponse import gq.kirmanak.mealient.datasource.models.NetworkError -import gq.kirmanak.mealient.datasource.models.VersionResponse import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json @@ -33,7 +33,7 @@ class MealieDataSourceV1Impl @Inject constructor( TODO("Not yet implemented") } - override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall( + override suspend fun getVersionInfo(baseUrl: String): VersionResponseV1 = makeCall( block = { getVersion("$baseUrl/api/app/about") }, logMethod = { "getVersionInfo" }, logParameters = { "baseUrl = $baseUrl" }, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index 34b0edc..5e6146c 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -3,6 +3,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.models.* import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import retrofit2.http.* interface MealieServiceV1 { @@ -25,7 +26,7 @@ interface MealieServiceV1 { @GET suspend fun getVersion( @Url url: String, - ): VersionResponse + ): VersionResponseV1 @GET suspend fun getRecipeSummary( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt new file mode 100644 index 0000000..53e7763 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt @@ -0,0 +1,12 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VersionResponseV1( + @SerialName("production") val production: Boolean, + @SerialName("version") val version: String, + @SerialName("demoStatus") val demoStatus: Boolean, + @SerialName("allowSignup") val allowSignup: Boolean, +) \ No newline at end of file From 17fc2f62bd98bc354cdd7b8122702b1f704eb561 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 17:09:42 +0200 Subject: [PATCH 07/35] Check version when requesting recipes --- .../mealient/data/baseurl/BaseURLStorage.kt | 4 +++- .../data/baseurl/impl/BaseURLStorageImpl.kt | 16 +++++++++++++--- .../data/network/MealieDataSourceWrapper.kt | 12 +++++------- .../mealient/data/storage/PreferencesStorage.kt | 2 ++ .../data/storage/PreferencesStorageImpl.kt | 2 ++ .../mealient/ui/baseurl/BaseURLViewModel.kt | 4 ++-- .../data/baseurl/BaseURLStorageImplTest.kt | 2 +- .../kirmanak/mealient/test/AuthImplTestData.kt | 1 + .../mealient/ui/baseurl/BaseURLViewModelTest.kt | 5 +++-- 9 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt index 7864ea9..4c0bcee 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt @@ -6,5 +6,7 @@ interface BaseURLStorage { suspend fun requireBaseURL(): String - suspend fun storeBaseURL(baseURL: String) + suspend fun storeBaseURL(baseURL: String, version: String) + + suspend fun getServerVersion(): String? } \ 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/BaseURLStorageImpl.kt index 64280cf..86ac2f6 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/BaseURLStorageImpl.kt @@ -14,13 +14,23 @@ class BaseURLStorageImpl @Inject constructor( private val baseUrlKey: Preferences.Key get() = preferencesStorage.baseUrlKey - override suspend fun getBaseURL(): String? = preferencesStorage.getValue(baseUrlKey) + private val serverVersionKey: Preferences.Key + get() = preferencesStorage.serverVersionKey + + 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) { - preferencesStorage.storeValues(Pair(baseUrlKey, baseURL)) + override suspend fun storeBaseURL(baseURL: String, version: String) { + preferencesStorage.storeValues( + Pair(baseUrlKey, baseURL), + Pair(serverVersionKey, version), + ) } + + override suspend fun getServerVersion(): String? = getValue(serverVersionKey) + + private suspend fun getValue(key: Preferences.Key): T? = preferencesStorage.getValue(key) } \ No newline at end of file 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 bd91b0c..b66084b 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 @@ -41,7 +41,9 @@ class MealieDataSourceWrapper @Inject constructor( override suspend fun requestRecipes(start: Int, limit: Int): List = withAuthHeader { token -> - runCatchingExceptCancel { + if (isV1()) { + mealieDataSourceV1.requestRecipes(getUrl(), token, start, limit) + } else { mealieDataSource.requestRecipes(getUrl(), token, start, limit).map { GetRecipeSummaryResponseV1( remoteId = it.remoteId.toString(), @@ -56,12 +58,6 @@ class MealieDataSourceWrapper @Inject constructor( dateUpdated = it.dateUpdated, ) } - }.getOrElse { - if (it is NetworkError.NotMealie) { - mealieDataSourceV1.requestRecipes(getUrl(), token, start, limit) - } else { - throw it - } } } @@ -70,6 +66,8 @@ class MealieDataSourceWrapper @Inject constructor( private suspend fun getUrl() = baseURLStorage.requireBaseURL() + private suspend fun isV1() = baseURLStorage.getServerVersion().orEmpty().startsWith("v1") + 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/storage/PreferencesStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt index 31e9174..df88285 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorage.kt @@ -7,6 +7,8 @@ interface PreferencesStorage { val baseUrlKey: Preferences.Key + val serverVersionKey: Preferences.Key + val isDisclaimerAcceptedKey: Preferences.Key suspend fun getValue(key: Preferences.Key): T? diff --git a/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt index e42346c..f9e7bfa 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImpl.kt @@ -18,6 +18,8 @@ class PreferencesStorageImpl @Inject constructor( override val baseUrlKey = stringPreferencesKey("baseUrl") + override val serverVersionKey = stringPreferencesKey("serverVersion") + override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted") override suspend fun getValue(key: Preferences.Key): T? { 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 1b98c87..bb3e3c7 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 @@ -35,8 +35,8 @@ class BaseURLViewModel @Inject constructor( logger.v { "checkBaseURL() called with: baseURL = $baseURL" } val result = runCatchingExceptCancel { // If it returns proper version info then it must be a Mealie - versionDataSource.getVersionInfo(baseURL) - baseURLStorage.storeBaseURL(baseURL) + val version = versionDataSource.getVersionInfo(baseURL).version + baseURLStorage.storeBaseURL(baseURL, version) } logger.i { "checkBaseURL: result is $result" } _uiState.value = OperationUiState.fromResult(result) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt index 256139a..9197830 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorageImplTest.kt @@ -57,7 +57,7 @@ class BaseURLStorageImplTest { @Test fun `when storeBaseURL then calls preferences storage`() = runTest { - subject.storeBaseURL("baseUrl") + subject.storeBaseURL("baseUrl", "v0.5.6") coVerify { preferencesStorage.baseUrlKey preferencesStorage.storeValues(eq(Pair(baseUrlKey, "baseUrl"))) diff --git a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt index feb4866..6fd70ba 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt @@ -7,4 +7,5 @@ object AuthImplTestData { const val TEST_TOKEN = "TEST_TOKEN" const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN" const val TEST_URL = "TEST_URL" + const val TEST_VERSION = "v0.5.6" } \ 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 b41b7fa..4c91bf1 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 @@ -5,6 +5,7 @@ import gq.kirmanak.mealient.data.baseurl.VersionDataSource import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION import gq.kirmanak.mealient.test.RobolectricTest import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -40,9 +41,9 @@ class BaseURLViewModelTest : RobolectricTest() { fun `when saveBaseUrl and getVersionInfo returns result then saves to storage`() = runTest { coEvery { versionDataSource.getVersionInfo(eq(TEST_BASE_URL)) - } returns VersionInfo(true, "0.5.6", true) + } returns VersionInfo(TEST_VERSION) subject.saveBaseUrl(TEST_BASE_URL) advanceUntilIdle() - coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL)) } + coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) } } } \ No newline at end of file From 4577aac8fc4495fdade7ba270c6e3460a8967a0b Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 17:14:24 +0200 Subject: [PATCH 08/35] Check version if it is unknown --- .../gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt | 2 ++ .../mealient/data/baseurl/impl/BaseURLStorageImpl.kt | 4 ++++ .../mealient/data/network/MealieDataSourceWrapper.kt | 9 ++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt index 4c0bcee..e629625 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/BaseURLStorage.kt @@ -8,5 +8,7 @@ interface BaseURLStorage { suspend fun storeBaseURL(baseURL: String, version: String) + suspend fun storeServerVersion(version: String) + suspend fun getServerVersion(): String? } \ 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/BaseURLStorageImpl.kt index 86ac2f6..cb77608 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/BaseURLStorageImpl.kt @@ -32,5 +32,9 @@ class BaseURLStorageImpl @Inject constructor( override suspend fun getServerVersion(): String? = getValue(serverVersionKey) + override suspend fun storeServerVersion(version: String) { + preferencesStorage.storeValues(Pair(serverVersionKey, version)) + } + private suspend fun getValue(key: Preferences.Key): T? = preferencesStorage.getValue(key) } \ No newline at end of file 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 b66084b..fdf640f 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 @@ -66,7 +66,14 @@ class MealieDataSourceWrapper @Inject constructor( private suspend fun getUrl() = baseURLStorage.requireBaseURL() - private suspend fun isV1() = baseURLStorage.getServerVersion().orEmpty().startsWith("v1") + private suspend fun isV1(): Boolean { + var version = baseURLStorage.getServerVersion() + if (version == null) { + version = getVersionInfo(getUrl()).version + baseURLStorage.storeServerVersion(version) + } + return version.startsWith("v1") + } private suspend inline fun withAuthHeader(block: (String?) -> T): T = runCatching { block(authRepo.getAuthHeader()) }.getOrElse { From 45b1b073729baf6a6f640d03f19538a6b4cc3143 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 17:33:39 +0200 Subject: [PATCH 09/35] Fix absent images on v0.5.6 --- .../data/network/MealieDataSourceWrapper.kt | 35 +- .../mealient/data/recipes/db/RecipeStorage.kt | 6 +- .../data/recipes/db/RecipeStorageImpl.kt | 6 +- .../data/recipes/network/RecipeDataSource.kt | 3 +- .../data/recipes/network/RecipeSummaryInfo.kt | 18 + .../extensions/RemoteToLocalMappings.kt | 32 +- .../ui/recipes/images/RecipeModelLoader.kt | 2 +- .../4.json | 410 ++++++++++++++++++ .../gq/kirmanak/mealient/database/AppDb.kt | 3 +- .../recipe/entity/RecipeSummaryEntity.kt | 3 +- 10 files changed, 483 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt create mode 100644 database/schemas/gq.kirmanak.mealient.database.AppDb/4.json 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 fdf640f..c9205cc 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 @@ -6,13 +6,14 @@ 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.recipes.network.RecipeDataSource +import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource.models.AddRecipeRequest import gq.kirmanak.mealient.datasource.models.GetRecipeResponse import gq.kirmanak.mealient.datasource.models.NetworkError import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo import gq.kirmanak.mealient.extensions.toVersionInfo import javax.inject.Inject import javax.inject.Singleton @@ -21,48 +22,36 @@ import javax.inject.Singleton class MealieDataSourceWrapper @Inject constructor( private val baseURLStorage: BaseURLStorage, private val authRepo: AuthRepo, - private val mealieDataSource: MealieDataSource, - private val mealieDataSourceV1: MealieDataSourceV1, + private val source: MealieDataSource, + private val v1Source: MealieDataSourceV1, ) : AddRecipeDataSource, RecipeDataSource, VersionDataSource { override suspend fun addRecipe(recipe: AddRecipeRequest): String = - withAuthHeader { token -> mealieDataSource.addRecipe(getUrl(), token, recipe) } + withAuthHeader { token -> source.addRecipe(getUrl(), token, recipe) } override suspend fun getVersionInfo(baseUrl: String): VersionInfo = runCatchingExceptCancel { - mealieDataSource.getVersionInfo(baseUrl).toVersionInfo() + source.getVersionInfo(baseUrl).toVersionInfo() }.getOrElse { if (it is NetworkError.NotMealie) { - mealieDataSourceV1.getVersionInfo(baseUrl).toVersionInfo() + v1Source.getVersionInfo(baseUrl).toVersionInfo() } else { throw it } } - override suspend fun requestRecipes(start: Int, limit: Int): List = + override suspend fun requestRecipes(start: Int, limit: Int): List = withAuthHeader { token -> + val url = getUrl() if (isV1()) { - mealieDataSourceV1.requestRecipes(getUrl(), token, start, limit) + v1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } else { - mealieDataSource.requestRecipes(getUrl(), token, start, limit).map { - GetRecipeSummaryResponseV1( - remoteId = it.remoteId.toString(), - name = it.name, - slug = it.slug, - image = it.image, - description = it.description, - recipeCategories = it.recipeCategories, - tags = it.tags, - rating = it.rating, - dateAdded = it.dateAdded, - dateUpdated = it.dateUpdated, - ) - } + source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } } override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse = - withAuthHeader { token -> mealieDataSource.requestRecipeInfo(getUrl(), token, slug) } + withAuthHeader { token -> source.requestRecipeInfo(getUrl(), token, slug) } private suspend fun getUrl() = baseURLStorage.requireBaseURL() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt index 3b0d367..2d70c3c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt @@ -1,17 +1,17 @@ package gq.kirmanak.mealient.data.recipes.db import androidx.paging.PagingSource +import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 interface RecipeStorage { - suspend fun saveRecipes(recipes: List) + suspend fun saveRecipes(recipes: List) fun queryRecipes(): PagingSource - suspend fun refreshAll(recipes: List) + suspend fun refreshAll(recipes: List) suspend fun clearAllLocalData() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index ef203d8..aa6cdcb 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -2,11 +2,11 @@ package gq.kirmanak.mealient.data.recipes.db import androidx.paging.PagingSource import androidx.room.withTransaction +import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.extensions.recipeEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity @@ -23,7 +23,7 @@ class RecipeStorageImpl @Inject constructor( private val recipeDao: RecipeDao by lazy { db.recipeDao() } override suspend fun saveRecipes( - recipes: List + recipes: List ) = db.withTransaction { logger.v { "saveRecipes() called with $recipes" } @@ -96,7 +96,7 @@ class RecipeStorageImpl @Inject constructor( return recipeDao.queryRecipesByPages() } - override suspend fun refreshAll(recipes: List) { + override suspend fun refreshAll(recipes: List) { logger.v { "refreshAll() called with: recipes = $recipes" } db.withTransaction { recipeDao.removeAllRecipes() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt index 02e56b9..18fef8c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt @@ -1,10 +1,9 @@ package gq.kirmanak.mealient.data.recipes.network import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 interface RecipeDataSource { - suspend fun requestRecipes(start: Int, limit: Int): List + suspend fun requestRecipes(start: Int, limit: Int): List suspend fun requestRecipeInfo(slug: String): GetRecipeResponse } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt new file mode 100644 index 0000000..49a67ab --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt @@ -0,0 +1,18 @@ +package gq.kirmanak.mealient.data.recipes.network + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime + +data class RecipeSummaryInfo( + val remoteId: String, + val name: String, + val slug: String, + val image: String?, + val description: String = "", + val recipeCategories: List, + val tags: List, + val rating: Int?, + val dateAdded: LocalDate, + val dateUpdated: LocalDateTime, + val imageId: String, +) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index e2ec913..223402d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.extensions import gq.kirmanak.mealient.data.baseurl.VersionInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity @@ -33,7 +34,35 @@ fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: String) = text = text ) -fun GetRecipeSummaryResponseV1.recipeEntity() = RecipeSummaryEntity( +fun GetRecipeSummaryResponse.toRecipeSummaryInfo() = RecipeSummaryInfo( + remoteId = remoteId.toString(), + name = name, + slug = slug, + image = image, + description = description, + recipeCategories = recipeCategories, + tags = tags, + rating = rating, + dateAdded = dateAdded, + dateUpdated = dateUpdated, + imageId = slug, +) + +fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo( + remoteId = remoteId, + name = name, + slug = slug, + image = image, + description = description, + recipeCategories = recipeCategories, + tags = tags, + rating = rating, + dateAdded = dateAdded, + dateUpdated = dateUpdated, + imageId = remoteId, +) + +fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity( remoteId = remoteId, name = name, slug = slug, @@ -42,6 +71,7 @@ fun GetRecipeSummaryResponseV1.recipeEntity() = RecipeSummaryEntity( rating = rating, dateAdded = dateAdded, dateUpdated = dateUpdated, + imageId = imageId, ) fun VersionResponse.toVersionInfo() = VersionInfo(version) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt index edb9704..a1651b3 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/images/RecipeModelLoader.kt @@ -44,7 +44,7 @@ class RecipeModelLoader private constructor( options: Options? ): String? { logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" } - return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.remoteId) } + return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.imageId) } } override fun getHeaders( diff --git a/database/schemas/gq.kirmanak.mealient.database.AppDb/4.json b/database/schemas/gq.kirmanak.mealient.database.AppDb/4.json new file mode 100644 index 0000000..6b273aa --- /dev/null +++ b/database/schemas/gq.kirmanak.mealient.database.AppDb/4.json @@ -0,0 +1,410 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "13be83018f147e1f6e864790656da4a7", + "entities": [ + { + "tableName": "categories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_categories_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_categories_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "category_recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `recipe_id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "recipe_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_category_recipe_category_id_recipe_id", + "unique": true, + "columnNames": [ + "category_id", + "recipe_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_category_recipe_category_id_recipe_id` ON `${TABLE_NAME}` (`category_id`, `recipe_id`)" + }, + { + "name": "index_category_recipe_recipe_id", + "unique": false, + "columnNames": [ + "recipe_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_category_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" + } + ], + "foreignKeys": [ + { + "table": "categories", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "local_id" + ] + }, + { + "table": "recipe_summaries", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "recipe_id" + ], + "referencedColumns": [ + "remote_id" + ] + } + ] + }, + { + "tableName": "tags", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_tags_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tags_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "tag_recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`tag_id`, `recipe_id`), FOREIGN KEY(`tag_id`) REFERENCES `tags`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tagId", + "columnName": "tag_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "tag_id", + "recipe_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_tag_recipe_recipe_id", + "unique": false, + "columnNames": [ + "recipe_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tag_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" + } + ], + "foreignKeys": [ + { + "table": "tags", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "tag_id" + ], + "referencedColumns": [ + "local_id" + ] + }, + { + "table": "recipe_summaries", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "recipe_id" + ], + "referencedColumns": [ + "remote_id" + ] + } + ] + }, + { + "tableName": "recipe_summaries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `image` TEXT, `description` TEXT NOT NULL, `rating` INTEGER, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rating", + "columnName": "rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateUpdated", + "columnName": "date_updated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageId", + "columnName": "image_id", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipeYield", + "columnName": "recipe_yield", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_ingredient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `note` TEXT NOT NULL, `unit` TEXT NOT NULL, `food` TEXT NOT NULL, `disable_amount` INTEGER NOT NULL, `quantity` REAL NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "food", + "columnName": "food", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disableAmount", + "columnName": "disable_amount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "REAL", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_instruction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '13be83018f147e1f6e864790656da4a7')" + ] + } +} \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt index 8ab22a8..b71d9d6 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt @@ -8,7 +8,7 @@ import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* @Database( - version = 3, + version = 4, entities = [ CategoryEntity::class, CategoryRecipeEntity::class, @@ -22,6 +22,7 @@ import gq.kirmanak.mealient.database.recipe.entity.* exportSchema = true, autoMigrations = [ AutoMigration(from = 1, to = 2), + AutoMigration(from = 3, to = 4), ] ) @TypeConverters(RoomTypeConverters::class) diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt index 5f89fee..0bde53e 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt @@ -15,7 +15,8 @@ data class RecipeSummaryEntity( @ColumnInfo(name = "description") val description: String, @ColumnInfo(name = "rating") val rating: Int?, @ColumnInfo(name = "date_added") val dateAdded: LocalDate, - @ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime + @ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime, + @ColumnInfo(name = "image_id") val imageId: String?, ) { override fun toString(): String { return "RecipeSummaryEntity(remoteId=$remoteId, name='$name')" From 47addc3b908318cbf04a9f5ee94f3b8b42a65b72 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 17:46:28 +0200 Subject: [PATCH 10/35] Fix opening recipes on v0.5.6 --- .../data/network/MealieDataSourceWrapper.kt | 14 ++- .../mealient/data/recipes/RecipeRepo.kt | 4 +- .../mealient/data/recipes/db/RecipeStorage.kt | 8 +- .../data/recipes/db/RecipeStorageImpl.kt | 6 +- .../data/recipes/impl/RecipeRepoImpl.kt | 4 +- .../data/recipes/network/FullRecipeInfo.kt | 34 +++++++ .../data/recipes/network/RecipeDataSource.kt | 4 +- .../extensions/RemoteToLocalMappings.kt | 98 +++++++++++++++---- .../ui/recipes/info/RecipeInfoUiState.kt | 4 +- .../mealient/test/RecipeImplTestData.kt | 4 +- .../mealient/database/recipe/RecipeDao.kt | 2 +- ...{FullRecipeInfo.kt => FullRecipeEntity.kt} | 2 +- .../datasource/models/GetRecipeResponse.kt | 2 +- .../mealient/datasource/v1/MealieServiceV1.kt | 3 +- .../models/GetRecipeIngredientResponseV1.kt | 14 +++ .../models/GetRecipeInstructionResponseV1.kt | 10 ++ .../v1/models/GetRecipeResponseV1.kt | 23 +++++ 17 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt rename database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/{FullRecipeInfo.kt => FullRecipeEntity.kt} (95%) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt 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 c9205cc..733dc58 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 @@ -5,14 +5,15 @@ 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.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse import gq.kirmanak.mealient.datasource.models.NetworkError 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 javax.inject.Inject @@ -50,8 +51,15 @@ class MealieDataSourceWrapper @Inject constructor( } } - override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse = - withAuthHeader { token -> source.requestRecipeInfo(getUrl(), token, slug) } + override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = + withAuthHeader { token -> + val url = getUrl() + if (isV1()) { + v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + } else { + source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + } + } private suspend fun getUrl() = baseURLStorage.requireBaseURL() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt index 9c42bf0..4cd3b1b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/RecipeRepo.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.recipes import androidx.paging.Pager -import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo +import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity interface RecipeRepo { @@ -9,5 +9,5 @@ interface RecipeRepo { suspend fun clearLocalData() - suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeInfo + suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt index 2d70c3c..431e74a 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorage.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.data.recipes.db import androidx.paging.PagingSource +import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo -import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo +import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse interface RecipeStorage { suspend fun saveRecipes(recipes: List) @@ -15,7 +15,7 @@ interface RecipeStorage { suspend fun clearAllLocalData() - suspend fun saveRecipeInfo(recipe: GetRecipeResponse) + suspend fun saveRecipeInfo(recipe: FullRecipeInfo) - suspend fun queryRecipeInfo(recipeId: String): FullRecipeInfo + suspend fun queryRecipeInfo(recipeId: String): FullRecipeEntity } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index aa6cdcb..8f5224e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -2,11 +2,11 @@ package gq.kirmanak.mealient.data.recipes.db import androidx.paging.PagingSource import androidx.room.withTransaction +import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse import gq.kirmanak.mealient.extensions.recipeEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity @@ -113,7 +113,7 @@ class RecipeStorageImpl @Inject constructor( } } - override suspend fun saveRecipeInfo(recipe: GetRecipeResponse) { + override suspend fun saveRecipeInfo(recipe: FullRecipeInfo) { logger.v { "saveRecipeInfo() called with: recipe = $recipe" } db.withTransaction { recipeDao.insertRecipe(recipe.toRecipeEntity()) @@ -132,7 +132,7 @@ class RecipeStorageImpl @Inject constructor( } } - override suspend fun queryRecipeInfo(recipeId: String): FullRecipeInfo { + override suspend fun queryRecipeInfo(recipeId: String): FullRecipeEntity { logger.v { "queryRecipeInfo() called with: recipeId = $recipeId" } val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) { "Can't find recipe by id $recipeId in DB" diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt index 5398aae..0fee9f7 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt @@ -7,7 +7,7 @@ import androidx.paging.PagingConfig import gq.kirmanak.mealient.data.recipes.RecipeRepo import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource -import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo +import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger @@ -38,7 +38,7 @@ class RecipeRepoImpl @Inject constructor( storage.clearAllLocalData() } - override suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeInfo { + override suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity { logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" } runCatchingExceptCancel { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt new file mode 100644 index 0000000..395b194 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt @@ -0,0 +1,34 @@ +package gq.kirmanak.mealient.data.recipes.network + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime + +data class FullRecipeInfo( + val remoteId: String, + val name: String, + val slug: String, + val image: String, + val description: String, + val recipeCategories: List, + val tags: List, + val rating: Int?, + val dateAdded: LocalDate, + val dateUpdated: LocalDateTime, + val recipeYield: String, + val recipeIngredients: List, + val recipeInstructions: List, +) + +data class RecipeIngredientInfo( + val title: String, + val note: String, + val unit: String, + val food: String, + val disableAmount: Boolean, + val quantity: Double, +) + +data class RecipeInstructionInfo( + val title: String, + val text: String, +) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt index 18fef8c..4305eb5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeDataSource.kt @@ -1,9 +1,7 @@ package gq.kirmanak.mealient.data.recipes.network -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse - interface RecipeDataSource { suspend fun requestRecipes(start: Int, limit: Int): List - suspend fun requestRecipeInfo(slug: String): GetRecipeResponse + suspend fun requestRecipeInfo(slug: String): FullRecipeInfo } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 223402d..a819df8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -1,38 +1,38 @@ package gq.kirmanak.mealient.extensions import gq.kirmanak.mealient.data.baseurl.VersionInfo +import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.models.* -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.* import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft -fun GetRecipeResponse.toRecipeEntity() = RecipeEntity( +fun FullRecipeInfo.toRecipeEntity() = RecipeEntity( remoteId = remoteId, recipeYield = recipeYield ) -fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: String) = - RecipeIngredientEntity( - recipeId = remoteId, - title = title, - note = note, - unit = unit, - food = food, - disableAmount = disableAmount, - quantity = quantity - ) +fun RecipeIngredientInfo.toRecipeIngredientEntity(remoteId: String) = RecipeIngredientEntity( + recipeId = remoteId, + title = title, + note = note, + unit = unit, + food = food, + disableAmount = disableAmount, + quantity = quantity +) -fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: String) = - RecipeInstructionEntity( - recipeId = remoteId, - title = title, - text = text - ) +fun RecipeInstructionInfo.toRecipeInstructionEntity(remoteId: String) = RecipeInstructionEntity( + recipeId = remoteId, + title = title, + text = text +) fun GetRecipeSummaryResponse.toRecipeSummaryInfo() = RecipeSummaryInfo( remoteId = remoteId.toString(), @@ -99,3 +99,63 @@ fun AddRecipeRequest.toDraft(): AddRecipeDraft = AddRecipeDraft( isRecipePublic = settings.public, areCommentsDisabled = settings.disableComments, ) + +fun GetRecipeResponse.toFullRecipeInfo() = FullRecipeInfo( + remoteId = remoteId.toString(), + name = name, + slug = slug, + image = image, + description = description, + recipeCategories = recipeCategories, + tags = tags, + rating = rating, + dateAdded = dateAdded, + dateUpdated = dateUpdated, + recipeYield = recipeYield, + recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() }, + recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() } +) + +fun GetRecipeIngredientResponse.toRecipeIngredientInfo() = RecipeIngredientInfo( + title = title, + note = note, + unit = unit, + food = food, + disableAmount = disableAmount, + quantity = quantity +) + +fun GetRecipeInstructionResponse.toRecipeInstructionInfo() = RecipeInstructionInfo( + title = title, + text = text +) + +fun GetRecipeResponseV1.toFullRecipeInfo() = FullRecipeInfo( + remoteId = remoteId, + name = name, + slug = slug, + image = image, + description = description, + recipeCategories = recipeCategories, + tags = tags, + rating = rating, + dateAdded = dateAdded, + dateUpdated = dateUpdated, + recipeYield = recipeYield, + recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() }, + recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() } +) + +fun GetRecipeIngredientResponseV1.toRecipeIngredientInfo() = RecipeIngredientInfo( + title = title, + note = note, + unit = unit, + food = food, + disableAmount = disableAmount, + quantity = quantity +) + +fun GetRecipeInstructionResponseV1.toRecipeInstructionInfo() = RecipeInstructionInfo( + title = title, + text = text +) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt index 4d1fff5..6083fae 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoUiState.kt @@ -1,9 +1,9 @@ package gq.kirmanak.mealient.ui.recipes.info -import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo +import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity data class RecipeInfoUiState( val areIngredientsVisible: Boolean = false, val areInstructionsVisible: Boolean = false, - val recipeInfo: FullRecipeInfo? = null, + val recipeInfo: FullRecipeEntity? = null, ) diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index d64ec91..d6bc2ee 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -174,7 +174,7 @@ object RecipeImplTestData { quantity = 2 ) - val FULL_CAKE_INFO_ENTITY = FullRecipeInfo( + val FULL_CAKE_INFO_ENTITY = FullRecipeEntity( recipeEntity = CAKE_RECIPE_ENTITY, recipeSummaryEntity = CAKE_RECIPE_SUMMARY_ENTITY, recipeIngredients = listOf( @@ -228,7 +228,7 @@ object RecipeImplTestData { text = "Boil the ingredients" ) - val FULL_PORRIDGE_INFO_ENTITY = FullRecipeInfo( + val FULL_PORRIDGE_INFO_ENTITY = FullRecipeEntity( recipeEntity = PORRIDGE_RECIPE_ENTITY_FULL, recipeSummaryEntity = PORRIDGE_RECIPE_SUMMARY_ENTITY, recipeIngredients = listOf( diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt index 4113f37..091ec3f 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt @@ -66,7 +66,7 @@ interface RecipeDao { @Transaction @SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) // The lint is wrong, the columns are actually used @Query("SELECT * FROM recipe JOIN recipe_summaries ON recipe.remote_id = recipe_summaries.remote_id JOIN recipe_ingredient ON recipe_ingredient.recipe_id = recipe.remote_id JOIN recipe_instruction ON recipe_instruction.recipe_id = recipe.remote_id WHERE recipe.remote_id = :recipeId") - suspend fun queryFullRecipeInfo(recipeId: String): FullRecipeInfo? + suspend fun queryFullRecipeInfo(recipeId: String): FullRecipeEntity? @Query("DELETE FROM recipe_ingredient WHERE recipe_id = :recipeId") suspend fun deleteRecipeIngredients(recipeId: String) diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeInfo.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeEntity.kt similarity index 95% rename from database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeInfo.kt rename to database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeEntity.kt index 27b02cc..2c0cab0 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeInfo.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/FullRecipeEntity.kt @@ -3,7 +3,7 @@ package gq.kirmanak.mealient.database.recipe.entity import androidx.room.Embedded import androidx.room.Relation -data class FullRecipeInfo( +data class FullRecipeEntity( @Embedded val recipeEntity: RecipeEntity, @Relation( parentColumn = "remote_id", diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt index 36f9ee4..1f7dd8d 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable data class GetRecipeResponse( - @SerialName("id") val remoteId: String, + @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, @SerialName("slug") val slug: String, @SerialName("image") val image: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index 5e6146c..b631c81 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -2,6 +2,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import retrofit2.http.* @@ -40,5 +41,5 @@ interface MealieServiceV1 { suspend fun getRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - ): GetRecipeResponse + ): GetRecipeResponseV1 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt new file mode 100644 index 0000000..68bd444 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt @@ -0,0 +1,14 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetRecipeIngredientResponseV1( + @SerialName("title") val title: String = "", + @SerialName("note") val note: String = "", + @SerialName("unit") val unit: String = "", + @SerialName("food") val food: String = "", + @SerialName("disableAmount") val disableAmount: Boolean, + @SerialName("quantity") val quantity: Double, +) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt new file mode 100644 index 0000000..12b5cc7 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt @@ -0,0 +1,10 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetRecipeInstructionResponseV1( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String, +) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt new file mode 100644 index 0000000..a9ca1f5 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt @@ -0,0 +1,23 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetRecipeResponseV1( + @SerialName("id") val remoteId: String, + @SerialName("name") val name: String, + @SerialName("slug") val slug: String, + @SerialName("image") val image: String, + @SerialName("description") val description: String = "", + @SerialName("recipeCategory") val recipeCategories: List, + @SerialName("tags") val tags: List, + @SerialName("rating") val rating: Int?, + @SerialName("dateAdded") val dateAdded: LocalDate, + @SerialName("dateUpdated") val dateUpdated: LocalDateTime, + @SerialName("recipeYield") val recipeYield: String = "", + @SerialName("recipeIngredient") val recipeIngredients: List, + @SerialName("recipeInstructions") val recipeInstructions: List, +) From 3f2f945d99f1f52a2140ed6d1b3c032e8f117e02 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 18:01:16 +0200 Subject: [PATCH 11/35] Fix opening recipes on v1 --- .../mealient/datasource/v1/MealieDataSourceV1.kt | 4 ++-- .../mealient/datasource/v1/MealieDataSourceV1Impl.kt | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) 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 a7eb284..3921ae5 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 @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -37,5 +37,5 @@ interface MealieDataSourceV1 { baseUrl: String, token: String?, slug: String, - ): GetRecipeResponse + ): GetRecipeResponseV1 } \ No newline at end of file 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 77961ee..1ee03d1 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 @@ -1,8 +1,8 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import gq.kirmanak.mealient.logging.Logger @@ -65,9 +65,11 @@ class MealieDataSourceV1Impl @Inject constructor( baseUrl: String, token: String?, slug: String - ): GetRecipeResponse { - TODO("Not yet implemented") - } + ): GetRecipeResponseV1 = makeCall( + block = { getRecipe("$baseUrl/api/recipes/$slug", token) }, + logMethod = { "requestRecipeInfo" }, + logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } + ).getOrThrowUnauthorized() private suspend inline fun makeCall( crossinline block: suspend MealieServiceV1.() -> T, From 7702dbebd17eb9ed62fec2739e1b6a4b2dd833f7 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 18:15:10 +0200 Subject: [PATCH 12/35] Move V0 responses to v0 package --- .../mealient/data/add/AddRecipeDataSource.kt | 4 +- .../mealient/data/add/AddRecipeRepo.kt | 6 +-- .../data/add/impl/AddRecipeRepoImpl.kt | 6 +-- .../data/auth/impl/AuthDataSourceImpl.kt | 6 +-- .../data/network/MealieDataSourceWrapper.kt | 26 ++++----- .../extensions/RemoteToLocalMappings.kt | 22 ++++---- .../mealient/ui/add/AddRecipeFragment.kt | 18 +++---- .../mealient/ui/add/AddRecipeViewModel.kt | 8 +-- .../ui/auth/AuthenticationFragment.kt | 2 +- .../mealient/ui/baseurl/BaseURLFragment.kt | 2 +- ...st.kt => MealieDataSourceV0WrapperTest.kt} | 14 ++--- .../recipes/impl/RecipesRemoteMediatorTest.kt | 2 +- .../extensions/RemoteToLocalMappingsTest.kt | 32 +++++------ .../mealient/test/RecipeImplTestData.kt | 28 +++++----- .../mealient/ui/add/AddRecipeViewModelTest.kt | 12 ++--- .../mealient/datasource/DataSourceModule.kt | 7 ++- .../datasource/{models => }/NetworkError.kt | 2 +- .../datasource/models/AddRecipeRequest.kt | 54 ------------------- .../mealient/datasource/models/ErrorDetail.kt | 7 --- .../datasource/models/GetTokenResponse.kt | 7 --- .../datasource/models/VersionResponse.kt | 14 ----- .../MealieDataSourceV0.kt} | 20 +++---- .../MealieDataSourceV0Impl.kt} | 27 +++++----- .../MealieServiceV0.kt} | 16 +++--- .../v0/models/AddRecipeIngredientV0.kt | 14 +++++ .../v0/models/AddRecipeInstructionV0.kt | 10 ++++ .../datasource/v0/models/AddRecipeNoteV0.kt | 10 ++++ .../v0/models/AddRecipeRequestV0.kt | 23 ++++++++ .../v0/models/AddRecipeSettingsV0.kt | 14 +++++ .../datasource/v0/models/ErrorDetailV0.kt | 7 +++ .../models/GetRecipeIngredientResponseV0.kt} | 4 +- .../models/GetRecipeInstructionResponseV0.kt} | 4 +- .../models/GetRecipeResponseV0.kt} | 8 +-- .../models/GetRecipeSummaryResponseV0.kt} | 4 +- .../v0/models/GetTokenResponseV0.kt | 7 +++ .../datasource/v0/models/VersionResponseV0.kt | 11 ++++ .../datasource/v1/MealieDataSourceV1.kt | 4 +- .../datasource/v1/MealieDataSourceV1Impl.kt | 6 +-- .../mealient/datasource/v1/MealieServiceV1.kt | 6 ++- ...lTest.kt => MealieDataSourceV0ImplTest.kt} | 27 +++++----- 40 files changed, 261 insertions(+), 240 deletions(-) rename app/src/test/java/gq/kirmanak/mealient/data/network/{MealieDataSourceWrapperTest.kt => MealieDataSourceV0WrapperTest.kt} (79%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{models => }/NetworkError.kt (87%) delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/AddRecipeRequest.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{MealieDataSource.kt => v0/MealieDataSourceV0.kt} (55%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{MealieDataSourceImpl.kt => v0/MealieDataSourceV0Impl.kt} (84%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{MealieService.kt => v0/MealieServiceV0.kt} (73%) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ErrorDetailV0.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{models/GetRecipeIngredientResponse.kt => v0/models/GetRecipeIngredientResponseV0.kt} (81%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{models/GetRecipeInstructionResponse.kt => v0/models/GetRecipeInstructionResponseV0.kt} (67%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{models/GetRecipeResponse.kt => v0/models/GetRecipeResponseV0.kt} (85%) rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/{models/GetRecipeSummaryResponse.kt => v0/models/GetRecipeSummaryResponseV0.kt} (90%) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt rename datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/{MealieDataSourceImplTest.kt => MealieDataSourceV0ImplTest.kt} (82%) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt index 1d53c6f..97a3af0 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.add -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 interface AddRecipeDataSource { - suspend fun addRecipe(recipe: AddRecipeRequest): String + suspend fun addRecipe(recipe: AddRecipeRequestV0): String } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt index ec59af7..952ed68 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt @@ -1,13 +1,13 @@ package gq.kirmanak.mealient.data.add -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import kotlinx.coroutines.flow.Flow interface AddRecipeRepo { - val addRecipeRequestFlow: Flow + val addRecipeRequestFlow: Flow - suspend fun preserve(recipe: AddRecipeRequest) + suspend fun preserve(recipe: AddRecipeRequestV0) suspend fun clear() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt index 2d3c096..3356aa4 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt @@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.add.impl import gq.kirmanak.mealient.data.add.AddRecipeDataSource import gq.kirmanak.mealient.data.add.AddRecipeRepo -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage import gq.kirmanak.mealient.extensions.toAddRecipeRequest import gq.kirmanak.mealient.extensions.toDraft @@ -20,10 +20,10 @@ class AddRecipeRepoImpl @Inject constructor( private val logger: Logger, ) : AddRecipeRepo { - override val addRecipeRequestFlow: Flow + override val addRecipeRequestFlow: Flow get() = addRecipeStorage.updates.map { it.toAddRecipeRequest() } - override suspend fun preserve(recipe: AddRecipeRequest) { + override suspend fun preserve(recipe: AddRecipeRequestV0) { logger.v { "preserveRecipe() called with: recipe = $recipe" } addRecipeStorage.save(recipe.toDraft()) } 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 7cadcc0..015c36d 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,15 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource -import gq.kirmanak.mealient.datasource.MealieDataSource +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import javax.inject.Inject import javax.inject.Singleton @Singleton class AuthDataSourceImpl @Inject constructor( - private val mealieDataSource: MealieDataSource, + private val V0Source: MealieDataSourceV0, ) : AuthDataSource { override suspend fun authenticate(username: String, password: String, baseUrl: String): String = - mealieDataSource.authenticate(baseUrl, username, password) + V0Source.authenticate(baseUrl, username, password) } \ No newline at end of file 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 733dc58..3910b1a 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 @@ -8,9 +8,9 @@ import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo -import gq.kirmanak.mealient.datasource.MealieDataSource -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.NetworkError +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 @@ -23,19 +23,19 @@ import javax.inject.Singleton class MealieDataSourceWrapper @Inject constructor( private val baseURLStorage: BaseURLStorage, private val authRepo: AuthRepo, - private val source: MealieDataSource, - private val v1Source: MealieDataSourceV1, + private val V0source: MealieDataSourceV0, + private val V1Source: MealieDataSourceV1, ) : AddRecipeDataSource, RecipeDataSource, VersionDataSource { - override suspend fun addRecipe(recipe: AddRecipeRequest): String = - withAuthHeader { token -> source.addRecipe(getUrl(), token, recipe) } + override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = + withAuthHeader { token -> V0source.addRecipe(getUrl(), token, recipe) } override suspend fun getVersionInfo(baseUrl: String): VersionInfo = runCatchingExceptCancel { - source.getVersionInfo(baseUrl).toVersionInfo() + V0source.getVersionInfo(baseUrl).toVersionInfo() }.getOrElse { if (it is NetworkError.NotMealie) { - v1Source.getVersionInfo(baseUrl).toVersionInfo() + V1Source.getVersionInfo(baseUrl).toVersionInfo() } else { throw it } @@ -45,9 +45,9 @@ class MealieDataSourceWrapper @Inject constructor( withAuthHeader { token -> val url = getUrl() if (isV1()) { - v1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } + V1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } else { - source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } + V0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } } @@ -55,9 +55,9 @@ class MealieDataSourceWrapper @Inject constructor( withAuthHeader { token -> val url = getUrl() if (isV1()) { - v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + V1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() } else { - source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + V0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() } } diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index a819df8..89b615e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -9,7 +9,7 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.datasource.v1.models.* import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft @@ -34,7 +34,7 @@ fun RecipeInstructionInfo.toRecipeInstructionEntity(remoteId: String) = RecipeIn text = text ) -fun GetRecipeSummaryResponse.toRecipeSummaryInfo() = RecipeSummaryInfo( +fun GetRecipeSummaryResponseV0.toRecipeSummaryInfo() = RecipeSummaryInfo( remoteId = remoteId.toString(), name = name, slug = slug, @@ -74,23 +74,23 @@ fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity( imageId = imageId, ) -fun VersionResponse.toVersionInfo() = VersionInfo(version) +fun VersionResponseV0.toVersionInfo() = VersionInfo(version) fun VersionResponseV1.toVersionInfo() = VersionInfo(version) -fun AddRecipeDraft.toAddRecipeRequest() = AddRecipeRequest( +fun AddRecipeDraft.toAddRecipeRequest() = AddRecipeRequestV0( name = recipeName, description = recipeDescription, recipeYield = recipeYield, - recipeIngredient = recipeIngredients.map { AddRecipeIngredient(note = it) }, - recipeInstructions = recipeInstructions.map { AddRecipeInstruction(text = it) }, - settings = AddRecipeSettings( + recipeIngredient = recipeIngredients.map { AddRecipeIngredientV0(note = it) }, + recipeInstructions = recipeInstructions.map { AddRecipeInstructionV0(text = it) }, + settings = AddRecipeSettingsV0( public = isRecipePublic, disableComments = areCommentsDisabled, ) ) -fun AddRecipeRequest.toDraft(): AddRecipeDraft = AddRecipeDraft( +fun AddRecipeRequestV0.toDraft(): AddRecipeDraft = AddRecipeDraft( recipeName = name, recipeDescription = description, recipeYield = recipeYield, @@ -100,7 +100,7 @@ fun AddRecipeRequest.toDraft(): AddRecipeDraft = AddRecipeDraft( areCommentsDisabled = settings.disableComments, ) -fun GetRecipeResponse.toFullRecipeInfo() = FullRecipeInfo( +fun GetRecipeResponseV0.toFullRecipeInfo() = FullRecipeInfo( remoteId = remoteId.toString(), name = name, slug = slug, @@ -116,7 +116,7 @@ fun GetRecipeResponse.toFullRecipeInfo() = FullRecipeInfo( recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() } ) -fun GetRecipeIngredientResponse.toRecipeIngredientInfo() = RecipeIngredientInfo( +fun GetRecipeIngredientResponseV0.toRecipeIngredientInfo() = RecipeIngredientInfo( title = title, note = note, unit = unit, @@ -125,7 +125,7 @@ fun GetRecipeIngredientResponse.toRecipeIngredientInfo() = RecipeIngredientInfo( quantity = quantity ) -fun GetRecipeInstructionResponse.toRecipeInstructionInfo() = RecipeInstructionInfo( +fun GetRecipeInstructionResponseV0.toRecipeInstructionInfo() = RecipeInstructionInfo( title = title, text = text ) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt index d8807b5..d6e655c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt @@ -14,10 +14,10 @@ import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.databinding.FragmentAddRecipeBinding import gq.kirmanak.mealient.databinding.ViewSingleInputBinding -import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient -import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.AddRecipeSettings +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0 import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.extensions.collectWhenViewResumed import gq.kirmanak.mealient.logging.Logger @@ -122,14 +122,14 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) { private fun saveValues() = with(binding) { logger.v { "saveValues() called" } - val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstruction(text = it) } - val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredient(note = it) } - val settings = AddRecipeSettings( + val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstructionV0(text = it) } + val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientV0(note = it) } + val settings = AddRecipeSettingsV0( public = publicRecipe.isChecked, disableComments = disableComments.isChecked, ) viewModel.preserve( - AddRecipeRequest( + AddRecipeRequestV0( name = recipeNameInput.text.toString(), description = recipeDescriptionInput.text.toString(), recipeYield = recipeYieldInput.text.toString(), @@ -148,7 +148,7 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) { .filterNot { it.isBlank() } .toList() - private fun onSavedInputLoaded(request: AddRecipeRequest) = with(binding) { + private fun onSavedInputLoaded(request: AddRecipeRequestV0) = with(binding) { logger.v { "onSavedInputLoaded() called with: request = $request" } recipeNameInput.setText(request.name) recipeDescriptionInput.setText(request.description) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt index b825eff..c8c94d0 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.add.AddRecipeRepo -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.channels.Channel @@ -23,8 +23,8 @@ class AddRecipeViewModel @Inject constructor( private val _addRecipeResultChannel = Channel(Channel.UNLIMITED) val addRecipeResult: Flow get() = _addRecipeResultChannel.receiveAsFlow() - private val _preservedAddRecipeRequestChannel = Channel(Channel.UNLIMITED) - val preservedAddRecipeRequest: Flow + private val _preservedAddRecipeRequestChannel = Channel(Channel.UNLIMITED) + val preservedAddRecipeRequest: Flow get() = _preservedAddRecipeRequestChannel.receiveAsFlow() fun loadPreservedRequest() { @@ -47,7 +47,7 @@ class AddRecipeViewModel @Inject constructor( } } - fun preserve(request: AddRecipeRequest) { + fun preserve(request: AddRecipeRequestV0) { logger.v { "preserve() called with: request = $request" } viewModelScope.launch { addRecipeRepo.preserve(request) } } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt index 651b16f..7a1dd16 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationFragment.kt @@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding -import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.OperationUiState diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt index 288a2db..fb505dd 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/baseurl/BaseURLFragment.kt @@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding -import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.OperationUiState diff --git a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt similarity index 79% rename from app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt rename to app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt index 743aa5a..c005068 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt @@ -2,8 +2,8 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.BaseURLStorage -import gq.kirmanak.mealient.datasource.MealieDataSource -import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE @@ -18,7 +18,7 @@ import org.junit.Test import java.io.IOException @OptIn(ExperimentalCoroutinesApi::class) -class MealieDataSourceWrapperTest { +class MealieDataSourceV0WrapperTest { @MockK lateinit var baseURLStorage: BaseURLStorage @@ -27,14 +27,14 @@ class MealieDataSourceWrapperTest { lateinit var authRepo: AuthRepo @MockK - lateinit var mealieDataSource: MealieDataSource + lateinit var mealieDataSourceV0: MealieDataSourceV0 lateinit var subject: MealieDataSourceWrapper @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceWrapper(baseURLStorage, authRepo, mealieDataSource) + subject = MealieDataSourceWrapper(baseURLStorage, authRepo, mealieDataSourceV0) } @Test @@ -42,10 +42,10 @@ class MealieDataSourceWrapperTest { coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER coEvery { - mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) + mealieDataSourceV0.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) } throws NetworkError.Unauthorized(IOException()) coEvery { - mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake")) + mealieDataSourceV0.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake")) } returns GET_CAKE_RESPONSE subject.requestRecipeInfo("cake") coVerifyAll { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt index 62f4375..f819e66 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediatorTest.kt @@ -6,7 +6,7 @@ import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.datasource.models.NetworkError.Unauthorized +import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES import io.mockk.MockKAnnotations diff --git a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt index 56b8428..08b6749 100644 --- a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.extensions import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient -import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.AddRecipeSettings +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0 import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft import org.junit.Test @@ -22,19 +22,19 @@ class RemoteToLocalMappingsTest { areCommentsDisabled = true, ) - val expected = AddRecipeRequest( + val expected = AddRecipeRequestV0( name = "Recipe name", description = "Recipe description", recipeYield = "Recipe yield", recipeIngredient = listOf( - AddRecipeIngredient(note = "Recipe ingredient 1"), - AddRecipeIngredient(note = "Recipe ingredient 2") + AddRecipeIngredientV0(note = "Recipe ingredient 1"), + AddRecipeIngredientV0(note = "Recipe ingredient 2") ), recipeInstructions = listOf( - AddRecipeInstruction(text = "Recipe instruction 1"), - AddRecipeInstruction(text = "Recipe instruction 2") + AddRecipeInstructionV0(text = "Recipe instruction 1"), + AddRecipeInstructionV0(text = "Recipe instruction 2") ), - settings = AddRecipeSettings( + settings = AddRecipeSettingsV0( public = false, disableComments = true, ) @@ -45,19 +45,19 @@ class RemoteToLocalMappingsTest { @Test fun `when toDraft then fills fields correctly`() { - val request = AddRecipeRequest( + val request = AddRecipeRequestV0( name = "Recipe name", description = "Recipe description", recipeYield = "Recipe yield", recipeIngredient = listOf( - AddRecipeIngredient(note = "Recipe ingredient 1"), - AddRecipeIngredient(note = "Recipe ingredient 2") + AddRecipeIngredientV0(note = "Recipe ingredient 1"), + AddRecipeIngredientV0(note = "Recipe ingredient 2") ), recipeInstructions = listOf( - AddRecipeInstruction(text = "Recipe instruction 1"), - AddRecipeInstruction(text = "Recipe instruction 2") + AddRecipeInstructionV0(text = "Recipe instruction 1"), + AddRecipeInstructionV0(text = "Recipe instruction 2") ), - settings = AddRecipeSettings( + settings = AddRecipeSettingsV0( public = false, disableComments = true, ) diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index d6bc2ee..f2fc2d4 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -1,15 +1,15 @@ package gq.kirmanak.mealient.test import gq.kirmanak.mealient.database.recipe.entity.* -import gq.kirmanak.mealient.datasource.models.GetRecipeIngredientResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeInstructionResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeIngredientResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeInstructionResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0 import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime object RecipeImplTestData { - val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponse( + val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponseV0( remoteId = 1, name = "Cake", slug = "cake", @@ -22,7 +22,7 @@ object RecipeImplTestData { dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), ) - val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponse( + val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponseV0( remoteId = 2, name = "Porridge", slug = "porridge", @@ -59,7 +59,7 @@ object RecipeImplTestData { dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), ) - private val SUGAR_INGREDIENT = GetRecipeIngredientResponse( + private val SUGAR_INGREDIENT = GetRecipeIngredientResponseV0( title = "Sugar", note = "2 oz of white sugar", unit = "", @@ -68,7 +68,7 @@ object RecipeImplTestData { quantity = 1 ) - val BREAD_INGREDIENT = GetRecipeIngredientResponse( + val BREAD_INGREDIENT = GetRecipeIngredientResponseV0( title = "Bread", note = "2 oz of white bread", unit = "", @@ -77,7 +77,7 @@ object RecipeImplTestData { quantity = 2 ) - private val MILK_INGREDIENT = GetRecipeIngredientResponse( + private val MILK_INGREDIENT = GetRecipeIngredientResponseV0( title = "Milk", note = "2 oz of white milk", unit = "", @@ -86,22 +86,22 @@ object RecipeImplTestData { quantity = 3 ) - val MIX_INSTRUCTION = GetRecipeInstructionResponse( + val MIX_INSTRUCTION = GetRecipeInstructionResponseV0( title = "Mix", text = "Mix the ingredients" ) - private val BAKE_INSTRUCTION = GetRecipeInstructionResponse( + private val BAKE_INSTRUCTION = GetRecipeInstructionResponseV0( title = "Bake", text = "Bake the ingredients" ) - private val BOIL_INSTRUCTION = GetRecipeInstructionResponse( + private val BOIL_INSTRUCTION = GetRecipeInstructionResponseV0( title = "Boil", text = "Boil the ingredients" ) - val GET_CAKE_RESPONSE = GetRecipeResponse( + val GET_CAKE_RESPONSE = GetRecipeResponseV0( remoteId = 1, name = "Cake", slug = "cake", @@ -117,7 +117,7 @@ object RecipeImplTestData { recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION) ) - val GET_PORRIDGE_RESPONSE = GetRecipeResponse( + val GET_PORRIDGE_RESPONSE = GetRecipeResponseV0( remoteId = 2, name = "Porridge", slug = "porridge", diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt index 42afb57..3e95fcd 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt @@ -2,7 +2,7 @@ package gq.kirmanak.mealient.ui.add import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.add.AddRecipeRepo -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.logging.Logger import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -61,21 +61,21 @@ class AddRecipeViewModelTest { @Test fun `when preserve then doesn't update UI`() { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest()) - subject.preserve(AddRecipeRequest()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequestV0()) + subject.preserve(AddRecipeRequestV0()) coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow } } @Test fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequestV0()) val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() } assertThat(actual).isNull() } @Test fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeRequest() + val expected = AddRecipeRequestV0() coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.loadPreservedRequest() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) @@ -83,7 +83,7 @@ class AddRecipeViewModelTest { @Test fun `when clear then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeRequest() + val expected = AddRecipeRequestV0() coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.clear() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt index ae9e517..f737948 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt @@ -6,6 +6,9 @@ import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0Impl +import gq.kirmanak.mealient.datasource.v0.MealieServiceV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl import gq.kirmanak.mealient.datasource.v1.MealieServiceV1 @@ -52,7 +55,7 @@ interface DataSourceModule { @Provides @Singleton - fun provideMealieService(retrofit: Retrofit): MealieService = + fun provideMealieService(retrofit: Retrofit): MealieServiceV0 = retrofit.create() @Provides @@ -71,7 +74,7 @@ interface DataSourceModule { @Binds @Singleton - fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceImpl): MealieDataSource + fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceV0Impl): MealieDataSourceV0 @Binds @Singleton diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NetworkError.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt similarity index 87% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NetworkError.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt index 7fcd669..50953a4 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NetworkError.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkError.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource sealed class NetworkError(cause: Throwable) : RuntimeException(cause) { class Unauthorized(cause: Throwable) : NetworkError(cause) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/AddRecipeRequest.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/AddRecipeRequest.kt deleted file mode 100644 index 9c2f34b..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/AddRecipeRequest.kt +++ /dev/null @@ -1,54 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AddRecipeRequest( - @SerialName("name") val name: String = "", - @SerialName("description") val description: String = "", - @SerialName("image") val image: String = "", - @SerialName("recipeYield") val recipeYield: String = "", - @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), - @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("slug") val slug: String = "", - @SerialName("filePath") val filePath: String = "", - @SerialName("tags") val tags: List = emptyList(), - @SerialName("categories") val categories: List = emptyList(), - @SerialName("notes") val notes: List = emptyList(), - @SerialName("extras") val extras: Map = emptyMap(), - @SerialName("assets") val assets: List = emptyList(), - @SerialName("settings") val settings: AddRecipeSettings = AddRecipeSettings(), -) - -@Serializable -data class AddRecipeSettings( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("landscapeView") val landscapeView: Boolean = true, - @SerialName("public") val public: Boolean = true, - @SerialName("showAssets") val showAssets: Boolean = true, - @SerialName("showNutrition") val showNutrition: Boolean = true, -) - -@Serializable -data class AddRecipeNote( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) - -@Serializable -data class AddRecipeInstruction( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) - -@Serializable -data class AddRecipeIngredient( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("food") val food: String? = null, - @SerialName("note") val note: String = "", - @SerialName("quantity") val quantity: Int = 1, - @SerialName("title") val title: String? = null, - @SerialName("unit") val unit: String? = null, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt deleted file mode 100644 index 00efd12..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt +++ /dev/null @@ -1,7 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ErrorDetail(@SerialName("detail") val detail: String? = null) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt deleted file mode 100644 index 66a79db..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetTokenResponse(@SerialName("access_token") val accessToken: String) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt deleted file mode 100644 index 44f0a09..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class VersionResponse( - @SerialName("production") - val production: Boolean, - @SerialName("version") - val version: String, - @SerialName("demoStatus") - val demoStatus: Boolean, -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSource.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt similarity index 55% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSource.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt index c8fd1ae..a51d5a7 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSource.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt @@ -1,16 +1,16 @@ -package gq.kirmanak.mealient.datasource +package gq.kirmanak.mealient.datasource.v0 -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.GetRecipeResponse -import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse -import gq.kirmanak.mealient.datasource.models.VersionResponse +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 -interface MealieDataSource { +interface MealieDataSourceV0 { suspend fun addRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequest, + recipe: AddRecipeRequestV0, ): String /** @@ -24,18 +24,18 @@ interface MealieDataSource { suspend fun getVersionInfo( baseUrl: String, - ): VersionResponse + ): VersionResponseV0 suspend fun requestRecipes( baseUrl: String, token: String?, start: Int, limit: Int, - ): List + ): List suspend fun requestRecipeInfo( baseUrl: String, token: String?, slug: String, - ): GetRecipeResponse + ): GetRecipeResponseV0 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt similarity index 84% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt index 9d4a6fd..411c141 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt @@ -1,6 +1,7 @@ -package gq.kirmanak.mealient.datasource +package gq.kirmanak.mealient.datasource.v0 -import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException @@ -14,14 +15,14 @@ import javax.inject.Inject import javax.inject.Singleton @Singleton -class MealieDataSourceImpl @Inject constructor( +class MealieDataSourceV0Impl @Inject constructor( private val logger: Logger, - private val mealieService: MealieService, + private val mealieServiceV0: MealieServiceV0, private val json: Json, -) : MealieDataSource { +) : MealieDataSourceV0 { override suspend fun addRecipe( - baseUrl: String, token: String?, recipe: AddRecipeRequest + baseUrl: String, token: String?, recipe: AddRecipeRequestV0 ): String = makeCall( block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) }, logMethod = { "addRecipe" }, @@ -36,11 +37,11 @@ class MealieDataSourceImpl @Inject constructor( logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" } ).map { it.accessToken }.getOrElse { val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it - val errorDetail = errorBody.decode() - throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it + val errorDetailV0 = errorBody.decode() + throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } - override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall( + override suspend fun getVersionInfo(baseUrl: String): VersionResponseV0 = makeCall( block = { getVersion("$baseUrl/api/debug/version") }, logMethod = { "getVersionInfo" }, logParameters = { "baseUrl = $baseUrl" }, @@ -56,7 +57,7 @@ class MealieDataSourceImpl @Inject constructor( override suspend fun requestRecipes( baseUrl: String, token: String?, start: Int, limit: Int - ): List = makeCall( + ): List = makeCall( block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, logMethod = { "requestRecipes" }, logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } @@ -67,19 +68,19 @@ class MealieDataSourceImpl @Inject constructor( override suspend fun requestRecipeInfo( baseUrl: String, token: String?, slug: String - ): GetRecipeResponse = makeCall( + ): GetRecipeResponseV0 = makeCall( block = { getRecipe("$baseUrl/api/recipes/$slug", token) }, logMethod = { "requestRecipeInfo" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } ).getOrThrowUnauthorized() private suspend inline fun makeCall( - crossinline block: suspend MealieService.() -> T, + crossinline block: suspend MealieServiceV0.() -> T, crossinline logMethod: () -> String, crossinline logParameters: () -> String, ): Result { logger.v { "${logMethod()} called with: ${logParameters()}" } - return mealieService.runCatching { block() } + return mealieServiceV0.runCatching { block() } .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt similarity index 73% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt index 9750cf9..0d6329b 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource +package gq.kirmanak.mealient.datasource.v0 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME -import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v0.models.* import retrofit2.http.* -interface MealieService { +interface MealieServiceV0 { @FormUrlEncoded @POST @@ -12,19 +12,19 @@ interface MealieService { @Url url: String, @Field("username") username: String, @Field("password") password: String, - ): GetTokenResponse + ): GetTokenResponseV0 @POST suspend fun addRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Body addRecipeRequest: AddRecipeRequest, + @Body addRecipeRequestV0: AddRecipeRequestV0, ): String @GET suspend fun getVersion( @Url url: String, - ): VersionResponse + ): VersionResponseV0 @GET suspend fun getRecipeSummary( @@ -32,11 +32,11 @@ interface MealieService { @Header(AUTHORIZATION_HEADER_NAME) token: String?, @Query("start") start: Int, @Query("limit") limit: Int, - ): List + ): List @GET suspend fun getRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - ): GetRecipeResponse + ): GetRecipeResponseV0 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt new file mode 100644 index 0000000..c4fc75d --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt @@ -0,0 +1,14 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeIngredientV0( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("food") val food: String? = null, + @SerialName("note") val note: String = "", + @SerialName("quantity") val quantity: Int = 1, + @SerialName("title") val title: String? = null, + @SerialName("unit") val unit: String? = null, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt new file mode 100644 index 0000000..eef390b --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt @@ -0,0 +1,10 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeInstructionV0( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt new file mode 100644 index 0000000..1f4a771 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt @@ -0,0 +1,10 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeNoteV0( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt new file mode 100644 index 0000000..eb597f6 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt @@ -0,0 +1,23 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeRequestV0( + @SerialName("name") val name: String = "", + @SerialName("description") val description: String = "", + @SerialName("image") val image: String = "", + @SerialName("recipeYield") val recipeYield: String = "", + @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), + @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), + @SerialName("slug") val slug: String = "", + @SerialName("filePath") val filePath: String = "", + @SerialName("tags") val tags: List = emptyList(), + @SerialName("categories") val categories: List = emptyList(), + @SerialName("notes") val notes: List = emptyList(), + @SerialName("extras") val extras: Map = emptyMap(), + @SerialName("assets") val assets: List = emptyList(), + @SerialName("settings") val settings: AddRecipeSettingsV0 = AddRecipeSettingsV0(), +) + diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt new file mode 100644 index 0000000..c58d162 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt @@ -0,0 +1,14 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeSettingsV0( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("disableComments") val disableComments: Boolean = false, + @SerialName("landscapeView") val landscapeView: Boolean = true, + @SerialName("public") val public: Boolean = true, + @SerialName("showAssets") val showAssets: Boolean = true, + @SerialName("showNutrition") val showNutrition: Boolean = true, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ErrorDetailV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ErrorDetailV0.kt new file mode 100644 index 0000000..50bce5e --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ErrorDetailV0.kt @@ -0,0 +1,7 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorDetailV0(@SerialName("detail") val detail: String? = null) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt similarity index 81% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt index 257d038..8def202 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeIngredientResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource.v0.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeIngredientResponse( +data class GetRecipeIngredientResponseV0( @SerialName("title") val title: String = "", @SerialName("note") val note: String = "", @SerialName("unit") val unit: String = "", diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeInstructionResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt similarity index 67% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeInstructionResponse.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt index c6c2fe7..1eaef94 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeInstructionResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource.v0.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeInstructionResponse( +data class GetRecipeInstructionResponseV0( @SerialName("title") val title: String = "", @SerialName("text") val text: String, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt similarity index 85% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt index 1f7dd8d..f940811 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource.v0.models import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -6,7 +6,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeResponse( +data class GetRecipeResponseV0( @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, @SerialName("slug") val slug: String, @@ -18,6 +18,6 @@ data class GetRecipeResponse( @SerialName("dateAdded") val dateAdded: LocalDate, @SerialName("dateUpdated") val dateUpdated: LocalDateTime, @SerialName("recipeYield") val recipeYield: String = "", - @SerialName("recipeIngredient") val recipeIngredients: List, - @SerialName("recipeInstructions") val recipeInstructions: List, + @SerialName("recipeIngredient") val recipeIngredients: List, + @SerialName("recipeInstructions") val recipeInstructions: List, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt similarity index 90% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt index 4fe3b80..6fdd0f0 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.datasource.models +package gq.kirmanak.mealient.datasource.v0.models import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -6,7 +6,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeSummaryResponse( +data class GetRecipeSummaryResponseV0( @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, @SerialName("slug") val slug: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt new file mode 100644 index 0000000..1a21be2 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt @@ -0,0 +1,7 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetTokenResponseV0(@SerialName("access_token") val accessToken: String) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt new file mode 100644 index 0000000..8cb8d25 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt @@ -0,0 +1,11 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VersionResponseV0( + @SerialName("production") val production: Boolean, + @SerialName("version") val version: String, + @SerialName("demoStatus") val demoStatus: Boolean, +) \ 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 3921ae5..0fa422d 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 @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.datasource.v1 -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -10,7 +10,7 @@ interface MealieDataSourceV1 { suspend fun addRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequest, + recipe: AddRecipeRequestV0, ): String /** 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 1ee03d1..d8109c0 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 @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.datasource.v1 -import gq.kirmanak.mealient.datasource.models.AddRecipeRequest -import gq.kirmanak.mealient.datasource.models.NetworkError +import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -24,7 +24,7 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun addRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequest + recipe: AddRecipeRequestV0 ): String { TODO("Not yet implemented") } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index b631c81..936f130 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -2,6 +2,8 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.models.* +import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v0.models.GetTokenResponseV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -15,13 +17,13 @@ interface MealieServiceV1 { @Url url: String, @Field("username") username: String, @Field("password") password: String, - ): GetTokenResponse + ): GetTokenResponseV0 @POST suspend fun addRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Body addRecipeRequest: AddRecipeRequest, + @Body addRecipeRequestV0: AddRecipeRequestV0, ): String @GET diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImplTest.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt similarity index 82% rename from datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImplTest.kt rename to datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt index fa82b9b..51bc77b 100644 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceImplTest.kt +++ b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt @@ -1,9 +1,10 @@ package gq.kirmanak.mealient.datasource import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.models.GetTokenResponse -import gq.kirmanak.mealient.datasource.models.NetworkError -import gq.kirmanak.mealient.datasource.models.VersionResponse +import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0Impl +import gq.kirmanak.mealient.datasource.v0.MealieServiceV0 +import gq.kirmanak.mealient.datasource.v0.models.GetTokenResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.toJsonResponseBody import io.mockk.MockKAnnotations @@ -21,25 +22,25 @@ import java.io.IOException import java.net.ConnectException @OptIn(ExperimentalCoroutinesApi::class) -class MealieDataSourceImplTest { +class MealieDataSourceV0ImplTest { @MockK - lateinit var service: MealieService + lateinit var service: MealieServiceV0 @MockK(relaxUnitFun = true) lateinit var logger: Logger - lateinit var subject: MealieDataSourceImpl + lateinit var subject: MealieDataSourceV0Impl @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceImpl(logger, service, Json.Default) + subject = MealieDataSourceV0Impl(logger, service, Json) } @Test(expected = NetworkError.NotMealie::class) fun `when getVersionInfo and getVersion throws HttpException then NotMealie`() = runTest { - val error = HttpException(Response.error(404, "".toJsonResponseBody())) + val error = HttpException(Response.error(404, "".toJsonResponseBody())) coEvery { service.getVersion(any()) } throws error subject.getVersionInfo(TEST_BASE_URL) } @@ -60,14 +61,14 @@ class MealieDataSourceImplTest { @Test fun `when getVersionInfo and getVersion returns result then result`() = runTest { - val versionResponse = VersionResponse(true, "v0.5.6", true) + val versionResponse = VersionResponseV0(true, "v0.5.6", true) coEvery { service.getVersion(any()) } returns versionResponse assertThat(subject.getVersionInfo(TEST_BASE_URL)).isSameInstanceAs(versionResponse) } @Test fun `when authentication is successful then token is correct`() = runTest { - coEvery { service.getToken(any(), any(), any()) } returns GetTokenResponse(TEST_TOKEN) + coEvery { service.getToken(any(), any(), any()) } returns GetTokenResponseV0(TEST_TOKEN) assertThat(callAuthenticate()).isEqualTo(TEST_TOKEN) } @@ -76,7 +77,7 @@ class MealieDataSourceImplTest { val body = "{\"detail\":\"Unauthorized\"}".toJsonResponseBody() coEvery { service.getToken(any(), any(), any()) - } throws HttpException(Response.error(401, body)) + } throws HttpException(Response.error(401, body)) callAuthenticate() } @@ -85,7 +86,7 @@ class MealieDataSourceImplTest { val body = "{\"detail\":\"Something\"}".toJsonResponseBody() coEvery { service.getToken(any(), any(), any()) - } throws HttpException(Response.error(401, body)) + } throws HttpException(Response.error(401, body)) callAuthenticate() } @@ -94,7 +95,7 @@ class MealieDataSourceImplTest { val body = "".toJsonResponseBody() coEvery { service.getToken(any(), any(), any()) - } throws HttpException(Response.error(401, body)) + } throws HttpException(Response.error(401, body)) callAuthenticate() } From f2b20024eb7ed7ed12abc3c0288499a24196e3e9 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 18:26:29 +0200 Subject: [PATCH 13/35] Request both versions simultaneously --- .../data/auth/impl/AuthDataSourceImpl.kt | 4 +- .../data/network/MealieDataSourceWrapper.kt | 40 ++++++++++++------- 2 files changed, 27 insertions(+), 17 deletions(-) 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 015c36d..8716939 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 @@ -7,9 +7,9 @@ import javax.inject.Singleton @Singleton class AuthDataSourceImpl @Inject constructor( - private val V0Source: MealieDataSourceV0, + private val v0Source: MealieDataSourceV0, ) : AuthDataSource { override suspend fun authenticate(username: String, password: String, baseUrl: String): String = - V0Source.authenticate(baseUrl, username, password) + v0Source.authenticate(baseUrl, username, password) } \ No newline at end of file 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 3910b1a..a9c605d 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 @@ -16,6 +16,9 @@ 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 @@ -23,31 +26,38 @@ import javax.inject.Singleton class MealieDataSourceWrapper @Inject constructor( private val baseURLStorage: BaseURLStorage, private val authRepo: AuthRepo, - private val V0source: MealieDataSourceV0, - private val V1Source: MealieDataSourceV1, + private val v0source: MealieDataSourceV0, + private val v1Source: MealieDataSourceV1, ) : AddRecipeDataSource, RecipeDataSource, VersionDataSource { override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = - withAuthHeader { token -> V0source.addRecipe(getUrl(), token, recipe) } + withAuthHeader { token -> v0source.addRecipe(getUrl(), token, recipe) } - override suspend fun getVersionInfo(baseUrl: String): VersionInfo = - runCatchingExceptCancel { - V0source.getVersionInfo(baseUrl).toVersionInfo() - }.getOrElse { - if (it is NetworkError.NotMealie) { - V1Source.getVersionInfo(baseUrl).toVersionInfo() - } else { - throw it + 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 requestRecipes(start: Int, limit: Int): List = withAuthHeader { token -> val url = getUrl() if (isV1()) { - V1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } + v1Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } else { - V0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } + v0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } } @@ -55,9 +65,9 @@ class MealieDataSourceWrapper @Inject constructor( withAuthHeader { token -> val url = getUrl() if (isV1()) { - V1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() } else { - V0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() } } From 9e9d07db7d7a5df5318ad0bd70b23d16dbfa1b68 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 18:36:15 +0200 Subject: [PATCH 14/35] Fix invalid import --- .../kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index 936f130..e9b658c 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -1,7 +1,6 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME -import gq.kirmanak.mealient.datasource.models.* import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v0.models.GetTokenResponseV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 From cda22215ec7dd7f0b309282908353aa25ff4ea42 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:15:57 +0200 Subject: [PATCH 15/35] 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, From b2ff374cef507e3ebe94b109dda0d68be5bbcf29 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:19:20 +0200 Subject: [PATCH 16/35] Use server info repo in ViewModel --- .../gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt | 2 ++ .../gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt | 5 +++++ .../gq/kirmanak/mealient/ui/baseurl/BaseURLViewModel.kt | 6 +++--- 3 files changed, 10 insertions(+), 3 deletions(-) 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 index 0517ff8..d4d7f42 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepo.kt @@ -7,5 +7,7 @@ interface ServerInfoRepo { suspend fun requireUrl(): String suspend fun getVersion(): ServerVersion + + suspend fun storeBaseURL(baseURL: String, version: String) } 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 index 83e2998..f46c19a 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoImpl.kt @@ -44,4 +44,9 @@ class ServerInfoRepoImpl @Inject constructor( version.startsWith("v1") -> ServerVersion.V1 else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version")) } + + override suspend fun storeBaseURL(baseURL: String, version: String) { + logger.v { "storeBaseURL() called with: baseURL = $baseURL, version = $version" } + serverInfoStorage.storeBaseURL(baseURL, version) + } } \ 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 f58adae..c58a941 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.ServerInfoStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo 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 serverInfoStorage: ServerInfoStorage, + private val serverInfoRepo: ServerInfoRepo, 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 - serverInfoStorage.storeBaseURL(baseURL, version) + serverInfoRepo.storeBaseURL(baseURL, version) } logger.i { "checkBaseURL: result is $result" } _uiState.value = OperationUiState.fromResult(result) From 5c5bfdedd31acb4d3e2c6a58aeb129a7c354fbe9 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:22:54 +0200 Subject: [PATCH 17/35] Fix sending duplicate requests --- .../data/network/MealieDataSourceWrapper.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 5d214c7..d851cd7 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 @@ -11,6 +11,7 @@ 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 javax.inject.Inject @@ -29,8 +30,7 @@ class MealieDataSourceWrapper @Inject constructor( } override suspend fun requestRecipes( - start: Int, - limit: Int + start: Int, limit: Int ): List = withAuthHeader { token -> val url = getUrl() when (getVersion()) { @@ -57,14 +57,17 @@ class MealieDataSourceWrapper @Inject constructor( private suspend fun getVersion() = serverInfoRepo.getVersion() - private suspend inline fun withAuthHeader(block: (String?) -> T): T = - runCatching { block(authRepo.getAuthHeader()) }.getOrElse { + private suspend inline fun withAuthHeader(block: (String?) -> T): T { + val authHeader = authRepo.getAuthHeader() + return runCatchingExceptCancel { block(authHeader) }.getOrElse { if (it is NetworkError.Unauthorized) { authRepo.invalidateAuthHeader() // Trying again with new authentication header - block(authRepo.getAuthHeader()) + val newHeader = authRepo.getAuthHeader() + if (newHeader == authHeader) throw it else block(newHeader) } else { throw it } } + } } \ No newline at end of file From 6280445a7c5be9a125063c9a189c0e4e09b4ca67 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:31:02 +0200 Subject: [PATCH 18/35] Move runCatchingExceptCancel to datasource --- .../mealient/data/auth/impl/AuthRepoImpl.kt | 2 +- .../data/baseurl/VersionDataSourceImpl.kt | 2 +- .../data/network/MealieDataSourceWrapper.kt | 2 +- .../mealient/data/recipes/impl/RecipeRepoImpl.kt | 2 +- .../data/recipes/impl/RecipesRemoteMediator.kt | 2 +- .../mealient/extensions/CoroutineExtensions.kt | 13 ------------- .../mealient/ui/add/AddRecipeViewModel.kt | 2 +- .../mealient/ui/auth/AuthenticationViewModel.kt | 2 +- .../mealient/ui/baseurl/BaseURLViewModel.kt | 2 +- .../ui/recipes/info/RecipeInfoViewModel.kt | 2 +- .../mealient/data/auth/impl/AuthRepoImplTest.kt | 3 ++- .../mealient/datasource/DataSourceExtensions.kt | 15 +++++++++++++++ .../datasource/v0/MealieDataSourceV0Impl.kt | 3 ++- .../datasource/v1/MealieDataSourceV1Impl.kt | 3 ++- 14 files changed, 30 insertions(+), 25 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt 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 165a701..2108a00 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 @@ -4,7 +4,7 @@ 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.ServerInfoRepo -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map 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 index 9f9dd87..7fd6be0 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSourceImpl.kt @@ -1,8 +1,8 @@ package gq.kirmanak.mealient.data.baseurl +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel 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 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 d851cd7..1bca823 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 @@ -8,10 +8,10 @@ import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel 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 javax.inject.Inject diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt index 0fee9f7..a48691f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt @@ -9,7 +9,7 @@ import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt index 06e785a..435d76d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipesRemoteMediator.kt @@ -7,7 +7,7 @@ import androidx.paging.LoadType.REFRESH import gq.kirmanak.mealient.data.recipes.db.RecipeStorage import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt index c0b7379..276348f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt @@ -1,15 +1,2 @@ package gq.kirmanak.mealient.extensions -import kotlinx.coroutines.CancellationException - -/** - * Like [runCatching] but rethrows [CancellationException] to support - * cancellation of coroutines. - */ -inline fun runCatchingExceptCancel(block: () -> T): Result = try { - Result.success(block()) -} catch (e: CancellationException) { - throw e -} catch (e: Throwable) { - Result.failure(e) -} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt index c8c94d0..231638f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.add.AddRecipeRepo +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt index 55a3cdd..145852d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/auth/AuthenticationViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.auth.AuthRepo -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.OperationUiState import kotlinx.coroutines.launch 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 c58a941..d2a66de 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 @@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.baseurl.VersionDataSource -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.ui.OperationUiState import kotlinx.coroutines.launch diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt index 5f1591b..932ae33 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModel.kt @@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.data.recipes.RecipeRepo -import gq.kirmanak.mealient.extensions.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.launch import javax.inject.Inject 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 13e6341..6c83f25 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 @@ -5,6 +5,7 @@ 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.ServerInfoStorage +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL @@ -72,7 +73,7 @@ class AuthRepoImplTest { fun `when authenticate fails then does not change storage`() = runTest { coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException() coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL - runCatching { subject.authenticate("invalid", "") } + runCatchingExceptCancel { subject.authenticate("invalid", "") } confirmVerified(storage) } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt new file mode 100644 index 0000000..6a10001 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt @@ -0,0 +1,15 @@ +package gq.kirmanak.mealient.datasource + +import kotlinx.coroutines.CancellationException + +/** + * Like [runCatching] but rethrows [CancellationException] to support + * cancellation of coroutines. + */ +inline fun runCatchingExceptCancel(block: () -> T): Result = try { + Result.success(block()) +} catch (e: CancellationException) { + throw e +} catch (e: Throwable) { + Result.failure(e) +} diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt index 411c141..83e75f5 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.datasource.v0 import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.ExperimentalSerializationApi @@ -80,7 +81,7 @@ class MealieDataSourceV0Impl @Inject constructor( crossinline logParameters: () -> String, ): Result { logger.v { "${logMethod()} called with: ${logParameters()}" } - return mealieServiceV0.runCatching { block() } + return runCatchingExceptCancel { mealieServiceV0.block() } .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } } 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 718d279..17d0615 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 @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.NetworkError +import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 @@ -72,7 +73,7 @@ class MealieDataSourceV1Impl @Inject constructor( crossinline logParameters: () -> String, ): Result { logger.v { "${logMethod()} called with: ${logParameters()}" } - return mealieService.runCatching { block() } + return runCatchingExceptCancel { mealieService.block() } .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } } From 5d99b0ee964b9e86cd9fd1b1a48bc5ed3006b146 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:46:09 +0200 Subject: [PATCH 19/35] Extract duplicated makeCall methods --- .../mealient/datasource/DataSourceModule.kt | 4 + .../datasource/NetworkRequestWrapper.kt | 17 ++++ .../datasource/NetworkRequestWrapperImpl.kt | 36 ++++++++ .../datasource/v0/MealieDataSourceV0Impl.kt | 82 ++++++++----------- .../datasource/v1/MealieDataSourceV1Impl.kt | 42 +++------- 5 files changed, 105 insertions(+), 76 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapperImpl.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt index f737948..afaf344 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt @@ -79,4 +79,8 @@ interface DataSourceModule { @Binds @Singleton fun bindMealieDataSourceV1(mealientDataSourceImpl: MealieDataSourceV1Impl): MealieDataSourceV1 + + @Binds + @Singleton + fun bindNetworkRequestWrapper(networkRequestWrapperImpl: NetworkRequestWrapperImpl): NetworkRequestWrapper } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt new file mode 100644 index 0000000..8a8dd01 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt @@ -0,0 +1,17 @@ +package gq.kirmanak.mealient.datasource + +interface NetworkRequestWrapper { + + suspend fun makeCall( + block: suspend () -> T, + logMethod: () -> String, + logParameters: () -> String, + ): Result + + suspend fun makeCallAndHandleUnauthorized( + block: suspend () -> T, + logMethod: () -> String, + logParameters: () -> String, + ): T + +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapperImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapperImpl.kt new file mode 100644 index 0000000..5bf01dc --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapperImpl.kt @@ -0,0 +1,36 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.logging.Logger +import retrofit2.HttpException +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NetworkRequestWrapperImpl @Inject constructor( + private val logger: Logger, +) : NetworkRequestWrapper { + + override suspend fun makeCall( + block: suspend () -> T, + logMethod: () -> String, + logParameters: () -> String, + ): Result { + logger.v { "${logMethod()} called with: ${logParameters()}" } + return runCatchingExceptCancel { block() } + .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } + .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}, result = $it" } } + } + + override suspend fun makeCallAndHandleUnauthorized( + block: suspend () -> T, + logMethod: () -> String, + logParameters: () -> String + ): T = makeCall(block, logMethod, logParameters).getOrElse { + throw if (it is HttpException && it.code() in listOf(401, 403)) { + NetworkError.Unauthorized(it) + } else { + it + } + } + +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt index 83e75f5..d8fd7b8 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.datasource.v0 import gq.kirmanak.mealient.datasource.NetworkError -import gq.kirmanak.mealient.datasource.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.NetworkRequestWrapper import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.ExperimentalSerializationApi @@ -17,23 +17,28 @@ import javax.inject.Singleton @Singleton class MealieDataSourceV0Impl @Inject constructor( + private val networkRequestWrapper: NetworkRequestWrapper, private val logger: Logger, - private val mealieServiceV0: MealieServiceV0, + private val service: MealieServiceV0, private val json: Json, ) : MealieDataSourceV0 { override suspend fun addRecipe( - baseUrl: String, token: String?, recipe: AddRecipeRequestV0 - ): String = makeCall( - block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) }, + baseUrl: String, + token: String?, + recipe: AddRecipeRequestV0, + ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.addRecipe("$baseUrl/api/recipes/create", token, recipe) }, logMethod = { "addRecipe" }, logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } - ).getOrThrowUnauthorized() + ) override suspend fun authenticate( - baseUrl: String, username: String, password: String - ): String = makeCall( - block = { getToken("$baseUrl/api/auth/token", username, password) }, + baseUrl: String, + username: String, + password: String, + ): String = networkRequestWrapper.makeCall( + block = { service.getToken("$baseUrl/api/auth/token", username, password) }, logMethod = { "authenticate" }, logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" } ).map { it.accessToken }.getOrElse { @@ -42,58 +47,41 @@ class MealieDataSourceV0Impl @Inject constructor( throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } - override suspend fun getVersionInfo(baseUrl: String): VersionResponseV0 = makeCall( - block = { getVersion("$baseUrl/api/debug/version") }, + override suspend fun getVersionInfo( + baseUrl: String + ): VersionResponseV0 = networkRequestWrapper.makeCall( + block = { service.getVersion("$baseUrl/api/debug/version") }, logMethod = { "getVersionInfo" }, logParameters = { "baseUrl = $baseUrl" }, ).getOrElse { - when (it) { - is HttpException, is SerializationException -> throw NetworkError.NotMealie(it) - is SocketTimeoutException, is ConnectException -> throw NetworkError.NoServerConnection( - it - ) - else -> throw NetworkError.MalformedUrl(it) + throw when (it) { + is HttpException, is SerializationException -> NetworkError.NotMealie(it) + is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it) + else -> NetworkError.MalformedUrl(it) } } override suspend fun requestRecipes( - baseUrl: String, token: String?, start: Int, limit: Int - ): List = makeCall( - block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, + baseUrl: String, + token: String?, + start: Int, + limit: Int, + ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, logMethod = { "requestRecipes" }, logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } - ).getOrElse { - val code = (it as? HttpException)?.code() ?: throw it - if (code == 404) throw NetworkError.NotMealie(it) else throw it - } + ) override suspend fun requestRecipeInfo( - baseUrl: String, token: String?, slug: String - ): GetRecipeResponseV0 = makeCall( - block = { getRecipe("$baseUrl/api/recipes/$slug", token) }, + baseUrl: String, + token: String?, + slug: String, + ): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) }, logMethod = { "requestRecipeInfo" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } - ).getOrThrowUnauthorized() - - private suspend inline fun makeCall( - crossinline block: suspend MealieServiceV0.() -> T, - crossinline logMethod: () -> String, - crossinline logParameters: () -> String, - ): Result { - logger.v { "${logMethod()} called with: ${logParameters()}" } - return runCatchingExceptCancel { mealieServiceV0.block() } - .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } - .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } - } + ) @OptIn(ExperimentalSerializationApi::class) private inline fun ResponseBody.decode(): R = json.decodeFromStream(byteStream()) } - -private fun Result.getOrThrowUnauthorized(): T = getOrElse { - throw if (it is HttpException && it.code() in listOf(401, 403)) { - NetworkError.Unauthorized(it) - } else { - it - } -} \ No newline at end of file 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 17d0615..9a743bb 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 @@ -1,12 +1,11 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.NetworkError -import gq.kirmanak.mealient.datasource.runCatchingExceptCancel +import gq.kirmanak.mealient.datasource.NetworkRequestWrapper import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 -import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import retrofit2.HttpException @@ -17,8 +16,8 @@ import javax.inject.Singleton @Singleton class MealieDataSourceV1Impl @Inject constructor( - private val logger: Logger, - private val mealieService: MealieServiceV1, + private val networkRequestWrapper: NetworkRequestWrapper, + private val service: MealieServiceV1, private val json: Json, ) : MealieDataSourceV1 { @@ -34,8 +33,10 @@ class MealieDataSourceV1Impl @Inject constructor( TODO("Not yet implemented") } - override suspend fun getVersionInfo(baseUrl: String): VersionResponseV1 = makeCall( - block = { getVersion("$baseUrl/api/app/about") }, + override suspend fun getVersionInfo( + baseUrl: String, + ): VersionResponseV1 = networkRequestWrapper.makeCall( + block = { service.getVersion("$baseUrl/api/app/about") }, logMethod = { "getVersionInfo" }, logParameters = { "baseUrl = $baseUrl" }, ).getOrElse { @@ -51,38 +52,21 @@ class MealieDataSourceV1Impl @Inject constructor( token: String?, page: Int, perPage: Int - ): List = makeCall( - block = { getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, + ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, logMethod = { "requestRecipesV1" }, logParameters = { "baseUrl = $baseUrl, token = $token, page = $page, perPage = $perPage" } - ).map { it.items }.getOrThrowUnauthorized() + ).items override suspend fun requestRecipeInfo( baseUrl: String, token: String?, slug: String - ): GetRecipeResponseV1 = makeCall( - block = { getRecipe("$baseUrl/api/recipes/$slug", token) }, + ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) }, logMethod = { "requestRecipeInfo" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } - ).getOrThrowUnauthorized() + ) - private suspend inline fun makeCall( - crossinline block: suspend MealieServiceV1.() -> T, - crossinline logMethod: () -> String, - crossinline logParameters: () -> String, - ): Result { - logger.v { "${logMethod()} called with: ${logParameters()}" } - return runCatchingExceptCancel { mealieService.block() } - .onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } } - .onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } } - } } -private fun Result.getOrThrowUnauthorized(): T = getOrElse { - throw if (it is HttpException && it.code() in listOf(401, 403)) { - NetworkError.Unauthorized(it) - } else { - it - } -} From 3d267f319ff2dc338ad7429a2f7b597c42ff6908 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:46:33 +0200 Subject: [PATCH 20/35] Fix logging method name --- .../kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9a743bb..965e1f7 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 @@ -54,7 +54,7 @@ class MealieDataSourceV1Impl @Inject constructor( perPage: Int ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, - logMethod = { "requestRecipesV1" }, + logMethod = { "requestRecipes" }, logParameters = { "baseUrl = $baseUrl, token = $token, page = $page, perPage = $perPage" } ).items From 9bf9146a4015ed3cc0dca3689c41e8b33f95d32c Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:55:40 +0200 Subject: [PATCH 21/35] Implement V1 authentication --- .../mealient/datasource/DataSourceExtensions.kt | 7 +++++++ .../datasource/v0/MealieDataSourceV0Impl.kt | 9 ++------- .../datasource/v1/MealieDataSourceV1Impl.kt | 16 ++++++++++++++-- .../datasource/v1/models/ErrorDetailV1.kt | 7 +++++++ 4 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt index 6a10001..4b7f291 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt @@ -1,6 +1,10 @@ package gq.kirmanak.mealient.datasource import kotlinx.coroutines.CancellationException +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.decodeFromStream +import okhttp3.ResponseBody /** * Like [runCatching] but rethrows [CancellationException] to support @@ -13,3 +17,6 @@ inline fun runCatchingExceptCancel(block: () -> T): Result = try { } catch (e: Throwable) { Result.failure(e) } + +@OptIn(ExperimentalSerializationApi::class) +inline fun ResponseBody.decode(json: Json): R = json.decodeFromStream(byteStream()) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt index d8fd7b8..641fb12 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt @@ -2,13 +2,11 @@ package gq.kirmanak.mealient.datasource.v0 import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper +import gq.kirmanak.mealient.datasource.decode import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.logging.Logger -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json -import kotlinx.serialization.json.decodeFromStream -import okhttp3.ResponseBody import retrofit2.HttpException import java.net.ConnectException import java.net.SocketTimeoutException @@ -43,7 +41,7 @@ class MealieDataSourceV0Impl @Inject constructor( logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" } ).map { it.accessToken }.getOrElse { val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it - val errorDetailV0 = errorBody.decode() + val errorDetailV0 = errorBody.decode(json) throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } @@ -81,7 +79,4 @@ class MealieDataSourceV0Impl @Inject constructor( logMethod = { "requestRecipeInfo" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } ) - - @OptIn(ExperimentalSerializationApi::class) - private inline fun ResponseBody.decode(): R = json.decodeFromStream(byteStream()) } 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 965e1f7..0857a91 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 @@ -2,7 +2,9 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper +import gq.kirmanak.mealient.datasource.decode import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -29,8 +31,18 @@ class MealieDataSourceV1Impl @Inject constructor( TODO("Not yet implemented") } - override suspend fun authenticate(baseUrl: String, username: String, password: String): String { - TODO("Not yet implemented") + override suspend fun authenticate( + baseUrl: String, + username: String, + password: String, + ): String = networkRequestWrapper.makeCall( + block = { service.getToken("$baseUrl/api/auth/token", username, password) }, + logMethod = { "authenticate" }, + logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" } + ).map { it.accessToken }.getOrElse { + val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it + val errorDetailV0 = errorBody.decode(json) + throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } override suspend fun getVersionInfo( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt new file mode 100644 index 0000000..b3f1810 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt @@ -0,0 +1,7 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ErrorDetailV1(@SerialName("detail") val detail: String? = null) \ No newline at end of file From a06d710f7a42047c57bbe490e450a0fc85c2df5c Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 19:59:09 +0200 Subject: [PATCH 22/35] Use V1 model for authentication --- .../kirmanak/mealient/datasource/v1/MealieServiceV1.kt | 4 ++-- .../datasource/v1/models/GetTokenResponseV1.kt | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index e9b658c..e441396 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -2,9 +2,9 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.GetTokenResponseV0 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.GetTokenResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 import retrofit2.http.* @@ -16,7 +16,7 @@ interface MealieServiceV1 { @Url url: String, @Field("username") username: String, @Field("password") password: String, - ): GetTokenResponseV0 + ): GetTokenResponseV1 @POST suspend fun addRecipe( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt new file mode 100644 index 0000000..b603283 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt @@ -0,0 +1,10 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetTokenResponseV1( + @SerialName("access_token") val accessToken: String, + @SerialName("token_type") val tokenType: String, +) \ No newline at end of file From 5f9779d904f09de3a799b404433ffaf76e9bc77b Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sat, 29 Oct 2022 20:24:32 +0200 Subject: [PATCH 23/35] Fix failing tests --- .../data/auth/impl/AuthRepoImplTest.kt | 32 +++++--- .../data/baseurl/ServerInfoStorageImplTest.kt | 19 ++--- ...Test.kt => MealieDataSourceWrapperTest.kt} | 28 ++++--- .../data/recipes/db/RecipeStorageImplTest.kt | 32 ++++---- .../impl/RecipeImageUrlProviderImplTest.kt | 8 +- .../data/recipes/impl/RecipeRepoImplTest.kt | 12 +-- .../mealient/test/AuthImplTestData.kt | 4 +- .../mealient/test/RecipeImplTestData.kt | 80 ++++++++++--------- .../ui/baseurl/BaseURLViewModelTest.kt | 8 +- .../datasource/v0/MealieDataSourceV0Impl.kt | 2 - .../datasource/MealieDataSourceV0ImplTest.kt | 4 +- 11 files changed, 121 insertions(+), 108 deletions(-) rename app/src/test/java/gq/kirmanak/mealient/data/network/{MealieDataSourceV0WrapperTest.kt => MealieDataSourceWrapperTest.kt} (58%) 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 6c83f25..8fde756 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,12 +4,13 @@ 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.ServerInfoStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME import io.mockk.* @@ -28,7 +29,7 @@ class AuthRepoImplTest { lateinit var dataSource: AuthDataSource @MockK - lateinit var serverInfoStorage: ServerInfoStorage + lateinit var serverInfoRepo: ServerInfoRepo @MockK(relaxUnitFun = true) lateinit var storage: AuthStorage @@ -41,7 +42,7 @@ class AuthRepoImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = AuthRepoImpl(storage, dataSource, serverInfoStorage, logger) + subject = AuthRepoImpl(storage, dataSource, serverInfoRepo, logger) } @Test @@ -52,14 +53,16 @@ class AuthRepoImplTest { @Test fun `when authenticate successfully then saves to storage`() = runTest { + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION coEvery { dataSource.authenticate( eq(TEST_USERNAME), eq(TEST_PASSWORD), - eq(TEST_BASE_URL) + eq(TEST_BASE_URL), + eq(TEST_SERVER_VERSION), ) } returns TEST_TOKEN - coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL subject.authenticate(TEST_USERNAME, TEST_PASSWORD) coVerifyAll { storage.setAuthHeader(TEST_AUTH_HEADER) @@ -71,8 +74,8 @@ class AuthRepoImplTest { @Test fun `when authenticate fails then does not change storage`() = runTest { - coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException() - coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { dataSource.authenticate(any(), any(), any(), any()) } throws RuntimeException() + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL runCatchingExceptCancel { subject.authenticate("invalid", "") } confirmVerified(storage) } @@ -108,13 +111,18 @@ class AuthRepoImplTest { fun `when invalidate with credentials then calls authenticate`() = runTest { coEvery { storage.getEmail() } returns TEST_USERNAME coEvery { storage.getPassword() } returns TEST_PASSWORD - coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { - dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL)) + dataSource.authenticate( + eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL), eq(TEST_SERVER_VERSION) + ) } returns TEST_TOKEN subject.invalidateAuthHeader() coVerifyAll { - dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL)) + dataSource.authenticate( + eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL), eq(TEST_SERVER_VERSION) + ) } } @@ -122,8 +130,8 @@ class AuthRepoImplTest { fun `when invalidate with credentials and auth fails then clears email`() = runTest { coEvery { storage.getEmail() } returns "invalid" coEvery { storage.getPassword() } returns "" - coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException() + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL + coEvery { dataSource.authenticate(any(), any(), any(), any()) } throws RuntimeException() subject.invalidateAuthHeader() coVerify { storage.setEmail(null) } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt index 469a1dd..978310d 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageImplTest.kt @@ -23,12 +23,14 @@ class ServerInfoStorageImplTest { lateinit var subject: ServerInfoStorage private val baseUrlKey = stringPreferencesKey("baseUrlKey") + private val serverVersionKey = stringPreferencesKey("serverVersionKey") @Before fun setUp() { MockKAnnotations.init(this) subject = ServerInfoStorageImpl(preferencesStorage) every { preferencesStorage.baseUrlKey } returns baseUrlKey + every { preferencesStorage.serverVersionKey } returns serverVersionKey } @Test @@ -37,30 +39,21 @@ class ServerInfoStorageImplTest { assertThat(subject.getBaseURL()).isNull() } - @Test(expected = IllegalStateException::class) - fun `when requireBaseURL and preferences storage empty then IllegalStateException`() = runTest { - coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null - subject.requireBaseURL() - } - @Test fun `when getBaseUrl and preferences storage has value then value`() = runTest { coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl" assertThat(subject.getBaseURL()).isEqualTo("baseUrl") } - @Test - fun `when requireBaseURL and preferences storage has value then value`() = runTest { - coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl" - assertThat(subject.requireBaseURL()).isEqualTo("baseUrl") - } - @Test fun `when storeBaseURL then calls preferences storage`() = runTest { subject.storeBaseURL("baseUrl", "v0.5.6") coVerify { preferencesStorage.baseUrlKey - preferencesStorage.storeValues(eq(Pair(baseUrlKey, "baseUrl"))) + preferencesStorage.storeValues( + eq(Pair(baseUrlKey, "baseUrl")), + eq(Pair(serverVersionKey, "v0.5.6")), + ) } } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt similarity index 58% rename from app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt rename to app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt index 9d1d6c3..7578362 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceV0WrapperTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt @@ -1,16 +1,19 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.auth.AuthRepo -import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 +import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 +import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL -import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerifyAll import io.mockk.impl.annotations.MockK +import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -18,35 +21,40 @@ import org.junit.Test import java.io.IOException @OptIn(ExperimentalCoroutinesApi::class) -class MealieDataSourceV0WrapperTest { +class MealieDataSourceWrapperTest { @MockK - lateinit var serverInfoStorage: ServerInfoStorage + lateinit var serverInfoRepo: ServerInfoRepo @MockK(relaxUnitFun = true) lateinit var authRepo: AuthRepo @MockK - lateinit var mealieDataSourceV0: MealieDataSourceV0 + lateinit var v0Source: MealieDataSourceV0 + + @MockK + lateinit var v1Source: MealieDataSourceV1 lateinit var subject: MealieDataSourceWrapper @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceWrapper(serverInfoStorage, authRepo, mealieDataSourceV0) + subject = MealieDataSourceWrapper(serverInfoRepo, authRepo, v0Source, v1Source) } @Test fun `when withAuthHeader fails with Unauthorized then invalidates auth`() = runTest { - coEvery { serverInfoStorage.requireBaseURL() } returns TEST_BASE_URL + coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION + coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER coEvery { - mealieDataSourceV0.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) + v0Source.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake")) } throws NetworkError.Unauthorized(IOException()) + val successResponse = mockk(relaxed = true) coEvery { - mealieDataSourceV0.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake")) - } returns GET_CAKE_RESPONSE + v0Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake")) + } returns successResponse subject.requestRecipeInfo("cake") coVerifyAll { authRepo.getAuthHeader() diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt index f3c18ab..d432f53 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt @@ -73,10 +73,10 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.saveRecipes(TEST_RECIPE_SUMMARIES) val actual = appDb.recipeDao().queryAllCategoryRecipes() assertThat(actual).containsExactly( - CategoryRecipeEntity(categoryId = 1, recipeId = 1), - CategoryRecipeEntity(categoryId = 2, recipeId = 1), - CategoryRecipeEntity(categoryId = 3, recipeId = 2), - CategoryRecipeEntity(categoryId = 2, recipeId = 2) + CategoryRecipeEntity(categoryId = 1, recipeId = "1"), + CategoryRecipeEntity(categoryId = 2, recipeId = "1"), + CategoryRecipeEntity(categoryId = 3, recipeId = "2"), + CategoryRecipeEntity(categoryId = 2, recipeId = "2") ) } @@ -85,10 +85,10 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.saveRecipes(TEST_RECIPE_SUMMARIES) val actual = appDb.recipeDao().queryAllTagRecipes() assertThat(actual).containsExactly( - TagRecipeEntity(tagId = 1, recipeId = 1), - TagRecipeEntity(tagId = 2, recipeId = 1), - TagRecipeEntity(tagId = 3, recipeId = 2), - TagRecipeEntity(tagId = 1, recipeId = 2), + TagRecipeEntity(tagId = 1, recipeId = "1"), + TagRecipeEntity(tagId = 2, recipeId = "1"), + TagRecipeEntity(tagId = 3, recipeId = "2"), + TagRecipeEntity(tagId = 1, recipeId = "2"), ) } @@ -106,8 +106,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE)) val actual = appDb.recipeDao().queryAllCategoryRecipes() assertThat(actual).containsExactly( - CategoryRecipeEntity(categoryId = 1, recipeId = 1), - CategoryRecipeEntity(categoryId = 2, recipeId = 1), + CategoryRecipeEntity(categoryId = 1, recipeId = "1"), + CategoryRecipeEntity(categoryId = 2, recipeId = "1"), ) } @@ -117,8 +117,8 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE)) val actual = appDb.recipeDao().queryAllTagRecipes() assertThat(actual).containsExactly( - TagRecipeEntity(tagId = 1, recipeId = 1), - TagRecipeEntity(tagId = 2, recipeId = 1), + TagRecipeEntity(tagId = 1, recipeId = "1"), + TagRecipeEntity(tagId = 2, recipeId = "1"), ) } @@ -150,7 +150,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() { fun `when saveRecipeInfo then saves recipe info`() = runTest { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE)) subject.saveRecipeInfo(GET_CAKE_RESPONSE) - val actual = appDb.recipeDao().queryFullRecipeInfo(1) + val actual = appDb.recipeDao().queryFullRecipeInfo("1") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } @@ -159,7 +159,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)) subject.saveRecipeInfo(GET_CAKE_RESPONSE) subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE) - val actual = appDb.recipeDao().queryFullRecipeInfo(2) + val actual = appDb.recipeDao().queryFullRecipeInfo("2") assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY) } @@ -169,7 +169,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.saveRecipeInfo(GET_CAKE_RESPONSE) val newRecipe = GET_CAKE_RESPONSE.copy(recipeIngredients = listOf(BREAD_INGREDIENT)) subject.saveRecipeInfo(newRecipe) - val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeIngredients + val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeIngredients val expected = listOf(CAKE_BREAD_RECIPE_INGREDIENT_ENTITY.copy(localId = 3)) assertThat(actual).isEqualTo(expected) } @@ -180,7 +180,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() { subject.saveRecipeInfo(GET_CAKE_RESPONSE) val newRecipe = GET_CAKE_RESPONSE.copy(recipeInstructions = listOf(MIX_INSTRUCTION)) subject.saveRecipeInfo(newRecipe) - val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeInstructions + val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeInstructions val expected = listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY.copy(localId = 3)) assertThat(actual).isEqualTo(expected) } 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 c219eb2..728bb60 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.ServerInfoStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo 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 serverInfoStorage: ServerInfoStorage + lateinit var serverInfoRepo: ServerInfoRepo @MockK(relaxUnitFun = true) lateinit var logger: Logger @@ -25,7 +25,7 @@ class RecipeImageUrlProviderImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = RecipeImageUrlProviderImpl(serverInfoStorage, logger) + subject = RecipeImageUrlProviderImpl(serverInfoRepo, logger) prepareBaseURL("https://google.com/") } @@ -81,6 +81,6 @@ class RecipeImageUrlProviderImplTest { } private fun prepareBaseURL(baseURL: String?) { - coEvery { serverInfoStorage.getBaseURL() } returns baseURL + coEvery { serverInfoRepo.getUrl() } returns baseURL } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt index 2a8801f..b66b447 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImplTest.kt @@ -47,24 +47,24 @@ class RecipeRepoImplTest { @Test fun `when loadRecipeInfo then loads recipe`() = runTest { coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE - coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY - val actual = subject.loadRecipeInfo(1, "cake") + coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY + val actual = subject.loadRecipeInfo("1", "cake") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } @Test fun `when loadRecipeInfo then saves to DB`() = runTest { coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE - coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY - subject.loadRecipeInfo(1, "cake") + coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY + subject.loadRecipeInfo("1", "cake") coVerify { storage.saveRecipeInfo(eq(GET_CAKE_RESPONSE)) } } @Test fun `when loadRecipeInfo with error then loads from DB`() = runTest { coEvery { dataSource.requestRecipeInfo(eq("cake")) } throws RuntimeException() - coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY - val actual = subject.loadRecipeInfo(1, "cake") + coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY + val actual = subject.loadRecipeInfo("1", "cake") assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY) } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt index 6fd70ba..cc5c8fc 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt @@ -1,11 +1,13 @@ package gq.kirmanak.mealient.test +import gq.kirmanak.mealient.data.baseurl.ServerVersion + object AuthImplTestData { const val TEST_USERNAME = "TEST_USERNAME" const val TEST_PASSWORD = "TEST_PASSWORD" const val TEST_BASE_URL = "https://example.com/" const val TEST_TOKEN = "TEST_TOKEN" const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN" - const val TEST_URL = "TEST_URL" const val TEST_VERSION = "v0.5.6" + val TEST_SERVER_VERSION = ServerVersion.V0 } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index f2fc2d4..9608cfc 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -1,16 +1,16 @@ package gq.kirmanak.mealient.test +import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo +import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.recipe.entity.* -import gq.kirmanak.mealient.datasource.v0.models.GetRecipeIngredientResponseV0 -import gq.kirmanak.mealient.datasource.v0.models.GetRecipeInstructionResponseV0 -import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 -import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0 import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime object RecipeImplTestData { - val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponseV0( - remoteId = 1, + val RECIPE_SUMMARY_CAKE = RecipeSummaryInfo( + remoteId = "1", name = "Cake", slug = "cake", image = "86", @@ -20,10 +20,11 @@ object RecipeImplTestData { rating = 4, dateAdded = LocalDate.parse("2021-11-13"), dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), + imageId = "cake", ) - val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponseV0( - remoteId = 2, + val RECIPE_SUMMARY_PORRIDGE = RecipeSummaryInfo( + remoteId = "2", name = "Porridge", slug = "porridge", image = "89", @@ -33,23 +34,25 @@ object RecipeImplTestData { rating = 5, dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), + imageId = "porridge", ) val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE) val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity( - remoteId = 1, + remoteId = "1", name = "Cake", slug = "cake", image = "86", description = "A tasty cake", rating = 4, dateAdded = LocalDate.parse("2021-11-13"), - dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13") + dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), + imageId = "cake", ) val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity( - remoteId = 2, + remoteId = "2", name = "Porridge", slug = "porridge", image = "89", @@ -57,52 +60,53 @@ object RecipeImplTestData { rating = 5, dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), + imageId = "porridge", ) - private val SUGAR_INGREDIENT = GetRecipeIngredientResponseV0( + private val SUGAR_INGREDIENT = RecipeIngredientInfo( title = "Sugar", note = "2 oz of white sugar", unit = "", food = "", disableAmount = true, - quantity = 1 + quantity = 1.0 ) - val BREAD_INGREDIENT = GetRecipeIngredientResponseV0( + val BREAD_INGREDIENT = RecipeIngredientInfo( title = "Bread", note = "2 oz of white bread", unit = "", food = "", disableAmount = false, - quantity = 2 + quantity = 2.0 ) - private val MILK_INGREDIENT = GetRecipeIngredientResponseV0( + private val MILK_INGREDIENT = RecipeIngredientInfo( title = "Milk", note = "2 oz of white milk", unit = "", food = "", disableAmount = true, - quantity = 3 + quantity = 3.0 ) - val MIX_INSTRUCTION = GetRecipeInstructionResponseV0( + val MIX_INSTRUCTION = RecipeInstructionInfo( title = "Mix", text = "Mix the ingredients" ) - private val BAKE_INSTRUCTION = GetRecipeInstructionResponseV0( + private val BAKE_INSTRUCTION = RecipeInstructionInfo( title = "Bake", text = "Bake the ingredients" ) - private val BOIL_INSTRUCTION = GetRecipeInstructionResponseV0( + private val BOIL_INSTRUCTION = RecipeInstructionInfo( title = "Boil", text = "Boil the ingredients" ) - val GET_CAKE_RESPONSE = GetRecipeResponseV0( - remoteId = 1, + val GET_CAKE_RESPONSE = FullRecipeInfo( + remoteId = "1", name = "Cake", slug = "cake", image = "86", @@ -117,8 +121,8 @@ object RecipeImplTestData { recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION) ) - val GET_PORRIDGE_RESPONSE = GetRecipeResponseV0( - remoteId = 2, + val GET_PORRIDGE_RESPONSE = FullRecipeInfo( + remoteId = "2", name = "Porridge", slug = "porridge", image = "89", @@ -135,43 +139,43 @@ object RecipeImplTestData { val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 1, - recipeId = 1, + recipeId = "1", title = "Mix", text = "Mix the ingredients", ) private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 2, - recipeId = 1, + recipeId = "1", title = "Bake", text = "Bake the ingredients", ) private val CAKE_RECIPE_ENTITY = RecipeEntity( - remoteId = 1, + remoteId = "1", recipeYield = "4 servings" ) private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 1, - recipeId = 1, + recipeId = "1", title = "Sugar", note = "2 oz of white sugar", unit = "", food = "", disableAmount = true, - quantity = 1 + quantity = 1.0 ) val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 2, - recipeId = 1, + recipeId = "1", title = "Bread", note = "2 oz of white bread", unit = "", food = "", disableAmount = false, - quantity = 2 + quantity = 2.0 ) val FULL_CAKE_INFO_ENTITY = FullRecipeEntity( @@ -188,42 +192,42 @@ object RecipeImplTestData { ) private val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity( - remoteId = 2, + remoteId = "2", recipeYield = "3 servings" ) private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 4, - recipeId = 2, + recipeId = "2", title = "Milk", note = "2 oz of white milk", unit = "", food = "", disableAmount = true, - quantity = 3 + quantity = 3.0 ) private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 3, - recipeId = 2, + recipeId = "2", title = "Sugar", note = "2 oz of white sugar", unit = "", food = "", disableAmount = true, - quantity = 1 + quantity = 1.0 ) private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 3, - recipeId = 2, + recipeId = "2", title = "Mix", text = "Mix the ingredients" ) private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 4, - recipeId = 2, + recipeId = "2", title = "Boil", text = "Boil the ingredients" ) 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 ca462c4..6cee162 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.ServerInfoStorage +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo 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 serverInfoStorage: ServerInfoStorage + lateinit var serverInfoRepo: ServerInfoRepo @MockK lateinit var versionDataSource: VersionDataSource @@ -34,7 +34,7 @@ class BaseURLViewModelTest : RobolectricTest() { @Before fun setUp() { MockKAnnotations.init(this) - subject = BaseURLViewModel(serverInfoStorage, versionDataSource, logger) + subject = BaseURLViewModel(serverInfoRepo, versionDataSource, logger) } @Test @@ -44,6 +44,6 @@ class BaseURLViewModelTest : RobolectricTest() { } returns VersionInfo(TEST_VERSION) subject.saveBaseUrl(TEST_BASE_URL) advanceUntilIdle() - coVerify { serverInfoStorage.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) } + coVerify { serverInfoRepo.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/v0/MealieDataSourceV0Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt index 641fb12..db7b276 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt @@ -4,7 +4,6 @@ import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper import gq.kirmanak.mealient.datasource.decode import gq.kirmanak.mealient.datasource.v0.models.* -import gq.kirmanak.mealient.logging.Logger import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import retrofit2.HttpException @@ -16,7 +15,6 @@ import javax.inject.Singleton @Singleton class MealieDataSourceV0Impl @Inject constructor( private val networkRequestWrapper: NetworkRequestWrapper, - private val logger: Logger, private val service: MealieServiceV0, private val json: Json, ) : MealieDataSourceV0 { diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt index 51bc77b..799cc1e 100644 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt +++ b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt @@ -35,7 +35,8 @@ class MealieDataSourceV0ImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = MealieDataSourceV0Impl(logger, service, Json) + val networkRequestWrapper: NetworkRequestWrapper = NetworkRequestWrapperImpl(logger) + subject = MealieDataSourceV0Impl(networkRequestWrapper, service, Json) } @Test(expected = NetworkError.NotMealie::class) @@ -113,6 +114,5 @@ class MealieDataSourceV0ImplTest { const val TEST_PASSWORD = "TEST_PASSWORD" const val TEST_BASE_URL = "https://example.com/" const val TEST_TOKEN = "TEST_TOKEN" - const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN" } } \ No newline at end of file From 2328c6ed5974aeb099d011ca9ebe3851b84354f0 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 10:43:56 +0100 Subject: [PATCH 24/35] Create separate model for v1 add recipe request --- .../mealient/data/add/AddRecipeDataSource.kt | 5 +- .../mealient/data/add/AddRecipeInfo.kt | 46 ++++++++ .../mealient/data/add/AddRecipeRepo.kt | 5 +- .../data/add/impl/AddRecipeRepoImpl.kt | 10 +- .../data/network/MealieDataSourceWrapper.kt | 46 ++++---- .../extensions/RemoteToLocalMappings.kt | 101 +++++++++++++++++- .../mealient/ui/add/AddRecipeFragment.kt | 19 ++-- .../mealient/ui/add/AddRecipeViewModel.kt | 8 +- .../extensions/RemoteToLocalMappingsTest.kt | 34 +++--- .../mealient/ui/add/AddRecipeViewModelTest.kt | 12 +-- .../datasource/v1/MealieDataSourceV1.kt | 4 +- .../datasource/v1/MealieDataSourceV1Impl.kt | 16 ++- .../mealient/datasource/v1/MealieServiceV1.kt | 8 +- .../v1/models/AddRecipeRequestV1.kt | 54 ++++++++++ 14 files changed, 279 insertions(+), 89 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt index 97a3af0..dc5769b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeDataSource.kt @@ -1,7 +1,6 @@ package gq.kirmanak.mealient.data.add -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 - interface AddRecipeDataSource { - suspend fun addRecipe(recipe: AddRecipeRequestV0): String + + suspend fun addRecipe(recipe: AddRecipeInfo): String } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt new file mode 100644 index 0000000..69c2ee9 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt @@ -0,0 +1,46 @@ +package gq.kirmanak.mealient.data.add + +data class AddRecipeInfo( + val name: String = "", + val description: String = "", + val image: String = "", + val recipeYield: String = "", + val recipeIngredient: List = emptyList(), + val recipeInstructions: List = emptyList(), + val slug: String = "", + val filePath: String = "", + val tags: List = emptyList(), + val categories: List = emptyList(), + val notes: List = emptyList(), + val extras: Map = emptyMap(), + val assets: List = emptyList(), + val settings: AddRecipeSettingsInfo = AddRecipeSettingsInfo(), +) + +data class AddRecipeSettingsInfo( + val disableAmount: Boolean = true, + val disableComments: Boolean = false, + val landscapeView: Boolean = true, + val public: Boolean = true, + val showAssets: Boolean = true, + val showNutrition: Boolean = true, +) + +data class AddRecipeNoteInfo( + val title: String = "", + val text: String = "", +) + +data class AddRecipeIngredientInfo( + val disableAmount: Boolean = true, + val food: String? = null, + val note: String = "", + val quantity: Int = 1, + val title: String? = null, + val unit: String? = null, +) + +data class AddRecipeInstructionInfo( + val title: String = "", + val text: String = "", +) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt index 952ed68..a0c7620 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeRepo.kt @@ -1,13 +1,12 @@ package gq.kirmanak.mealient.data.add -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import kotlinx.coroutines.flow.Flow interface AddRecipeRepo { - val addRecipeRequestFlow: Flow + val addRecipeRequestFlow: Flow - suspend fun preserve(recipe: AddRecipeRequestV0) + suspend fun preserve(recipe: AddRecipeInfo) suspend fun clear() diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt index 3356aa4..9043ca8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoImpl.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.data.add.impl import gq.kirmanak.mealient.data.add.AddRecipeDataSource +import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.add.AddRecipeRepo -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage -import gq.kirmanak.mealient.extensions.toAddRecipeRequest +import gq.kirmanak.mealient.extensions.toAddRecipeInfo import gq.kirmanak.mealient.extensions.toDraft import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.flow.Flow @@ -20,10 +20,10 @@ class AddRecipeRepoImpl @Inject constructor( private val logger: Logger, ) : AddRecipeRepo { - override val addRecipeRequestFlow: Flow - get() = addRecipeStorage.updates.map { it.toAddRecipeRequest() } + override val addRecipeRequestFlow: Flow + get() = addRecipeStorage.updates.map { it.toAddRecipeInfo() } - override suspend fun preserve(recipe: AddRecipeRequestV0) { + override suspend fun preserve(recipe: AddRecipeInfo) { logger.v { "preserveRecipe() called with: recipe = $recipe" } addRecipeStorage.save(recipe.toDraft()) } 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 1bca823..4f8f2ea 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 @@ -1,6 +1,7 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.add.AddRecipeDataSource +import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.baseurl.ServerVersion @@ -10,10 +11,11 @@ import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.runCatchingExceptCancel 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.toFullRecipeInfo import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo +import gq.kirmanak.mealient.extensions.toV0Request +import gq.kirmanak.mealient.extensions.toV1Request import javax.inject.Inject import javax.inject.Singleton @@ -21,21 +23,26 @@ import javax.inject.Singleton class MealieDataSourceWrapper @Inject constructor( private val serverInfoRepo: ServerInfoRepo, private val authRepo: AuthRepo, - private val v0source: MealieDataSourceV0, + private val v0Source: MealieDataSourceV0, private val v1Source: MealieDataSourceV1, ) : AddRecipeDataSource, RecipeDataSource { - override suspend fun addRecipe(recipe: AddRecipeRequestV0): String = withAuthHeader { token -> - v0source.addRecipe(getUrl(), token, recipe) + override suspend fun addRecipe( + recipe: AddRecipeInfo, + ): String = makeCall { token, url, version -> + when (version) { + ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request()) + ServerVersion.V1 -> v1Source.addRecipe(url, token, recipe.toV1Request()) + } } override suspend fun requestRecipes( - start: Int, limit: Int - ): List = withAuthHeader { token -> - val url = getUrl() - when (getVersion()) { + start: Int, + limit: Int, + ): List = makeCall { token, url, version -> + when (version) { ServerVersion.V0 -> { - v0source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } + v0Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } } 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 @@ -45,26 +52,25 @@ class MealieDataSourceWrapper @Inject constructor( } } - override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = withAuthHeader { token -> - val url = getUrl() - when (getVersion()) { - ServerVersion.V0 -> v0source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() + override suspend fun requestRecipeInfo( + slug: String, + ): FullRecipeInfo = makeCall { token, url, version -> + when (version) { + 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 { + private suspend inline fun makeCall(block: (String?, String, ServerVersion) -> T): T { val authHeader = authRepo.getAuthHeader() - return runCatchingExceptCancel { block(authHeader) }.getOrElse { + val url = serverInfoRepo.requireUrl() + val version = serverInfoRepo.getVersion() + return runCatchingExceptCancel { block(authHeader, url, version) }.getOrElse { if (it is NetworkError.Unauthorized) { authRepo.invalidateAuthHeader() // Trying again with new authentication header val newHeader = authRepo.getAuthHeader() - if (newHeader == authHeader) throw it else block(newHeader) + if (newHeader == authHeader) throw it else block(newHeader, url, version) } else { throw it } diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 89b615e..d45668e 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -1,5 +1,6 @@ package gq.kirmanak.mealient.extensions +import gq.kirmanak.mealient.data.add.* import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo @@ -78,19 +79,19 @@ fun VersionResponseV0.toVersionInfo() = VersionInfo(version) fun VersionResponseV1.toVersionInfo() = VersionInfo(version) -fun AddRecipeDraft.toAddRecipeRequest() = AddRecipeRequestV0( +fun AddRecipeDraft.toAddRecipeInfo() = AddRecipeInfo( name = recipeName, description = recipeDescription, recipeYield = recipeYield, - recipeIngredient = recipeIngredients.map { AddRecipeIngredientV0(note = it) }, - recipeInstructions = recipeInstructions.map { AddRecipeInstructionV0(text = it) }, - settings = AddRecipeSettingsV0( + recipeIngredient = recipeIngredients.map { AddRecipeIngredientInfo(note = it) }, + recipeInstructions = recipeInstructions.map { AddRecipeInstructionInfo(text = it) }, + settings = AddRecipeSettingsInfo( public = isRecipePublic, disableComments = areCommentsDisabled, ) ) -fun AddRecipeRequestV0.toDraft(): AddRecipeDraft = AddRecipeDraft( +fun AddRecipeInfo.toDraft(): AddRecipeDraft = AddRecipeDraft( recipeName = name, recipeDescription = description, recipeYield = recipeYield, @@ -159,3 +160,93 @@ fun GetRecipeInstructionResponseV1.toRecipeInstructionInfo() = RecipeInstruction title = title, text = text ) + +fun AddRecipeInfo.toV0Request() = AddRecipeRequestV0( + name = name, + description = description, + image = image, + recipeYield = recipeYield, + recipeIngredient = recipeIngredient.map { it.toV0Ingredient() }, + recipeInstructions = recipeInstructions.map { it.toV0Instruction() }, + slug = slug, + filePath = filePath, + tags = tags, + categories = categories, + notes = notes.map { it.toV0Note() }, + extras = extras, + assets = assets, + settings = settings.toV0Settings(), +) + +private fun AddRecipeSettingsInfo.toV0Settings() = AddRecipeSettingsV0( + disableAmount = disableAmount, + disableComments = disableComments, + landscapeView = landscapeView, + public = public, + showAssets = showAssets, + showNutrition = showNutrition, +) + +private fun AddRecipeNoteInfo.toV0Note() = AddRecipeNoteV0( + title = title, + text = text, +) + +private fun AddRecipeIngredientInfo.toV0Ingredient() = AddRecipeIngredientV0( + disableAmount = disableAmount, + food = food, + note = note, + quantity = quantity, + title = title, + unit = unit +) + +private fun AddRecipeInstructionInfo.toV0Instruction() = AddRecipeInstructionV0( + title = title, + text = text, +) + +fun AddRecipeInfo.toV1Request() = AddRecipeRequestV1( + name = name, + description = description, + image = image, + recipeYield = recipeYield, + recipeIngredient = recipeIngredient.map { it.toV1Ingredient() }, + recipeInstructions = recipeInstructions.map { it.toV1Instruction() }, + slug = slug, + filePath = filePath, + tags = tags, + categories = categories, + notes = notes.map { it.toV1Note() }, + extras = extras, + assets = assets, + settings = settings.toV1Settings(), +) + +private fun AddRecipeSettingsInfo.toV1Settings() = AddRecipeSettingsV1( + disableAmount = disableAmount, + disableComments = disableComments, + landscapeView = landscapeView, + public = public, + showAssets = showAssets, + showNutrition = showNutrition, +) + +private fun AddRecipeNoteInfo.toV1Note() = AddRecipeNoteV1( + title = title, + text = text, +) + +private fun AddRecipeIngredientInfo.toV1Ingredient() = AddRecipeIngredientV1( + disableAmount = disableAmount, + food = food, + note = note, + quantity = quantity, + title = title, + unit = unit +) + +private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1( + title = title, + text = text, +) \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt index d6e655c..a746d50 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeFragment.kt @@ -12,12 +12,12 @@ import androidx.fragment.app.viewModels import by.kirich1409.viewbindingdelegate.viewBinding import dagger.hilt.android.AndroidEntryPoint import gq.kirmanak.mealient.R +import gq.kirmanak.mealient.data.add.AddRecipeInfo +import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo +import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo +import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo import gq.kirmanak.mealient.databinding.FragmentAddRecipeBinding import gq.kirmanak.mealient.databinding.ViewSingleInputBinding -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0 import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty import gq.kirmanak.mealient.extensions.collectWhenViewResumed import gq.kirmanak.mealient.logging.Logger @@ -122,14 +122,15 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) { private fun saveValues() = with(binding) { logger.v { "saveValues() called" } - val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstructionV0(text = it) } - val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientV0(note = it) } - val settings = AddRecipeSettingsV0( + val instructions = + parseInputRows(instructionsFlow).map { AddRecipeInstructionInfo(text = it) } + val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientInfo(note = it) } + val settings = AddRecipeSettingsInfo( public = publicRecipe.isChecked, disableComments = disableComments.isChecked, ) viewModel.preserve( - AddRecipeRequestV0( + AddRecipeInfo( name = recipeNameInput.text.toString(), description = recipeDescriptionInput.text.toString(), recipeYield = recipeYieldInput.text.toString(), @@ -148,7 +149,7 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) { .filterNot { it.isBlank() } .toList() - private fun onSavedInputLoaded(request: AddRecipeRequestV0) = with(binding) { + private fun onSavedInputLoaded(request: AddRecipeInfo) = with(binding) { logger.v { "onSavedInputLoaded() called with: request = $request" } recipeNameInput.setText(request.name) recipeDescriptionInput.setText(request.description) diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt index 231638f..982e56f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModel.kt @@ -3,9 +3,9 @@ package gq.kirmanak.mealient.ui.add import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.add.AddRecipeRepo import gq.kirmanak.mealient.datasource.runCatchingExceptCancel -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow @@ -23,8 +23,8 @@ class AddRecipeViewModel @Inject constructor( private val _addRecipeResultChannel = Channel(Channel.UNLIMITED) val addRecipeResult: Flow get() = _addRecipeResultChannel.receiveAsFlow() - private val _preservedAddRecipeRequestChannel = Channel(Channel.UNLIMITED) - val preservedAddRecipeRequest: Flow + private val _preservedAddRecipeRequestChannel = Channel(Channel.UNLIMITED) + val preservedAddRecipeRequest: Flow get() = _preservedAddRecipeRequestChannel.receiveAsFlow() fun loadPreservedRequest() { @@ -47,7 +47,7 @@ class AddRecipeViewModel @Inject constructor( } } - fun preserve(request: AddRecipeRequestV0) { + fun preserve(request: AddRecipeInfo) { logger.v { "preserve() called with: request = $request" } viewModelScope.launch { addRecipeRepo.preserve(request) } } diff --git a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt b/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt index 08b6749..a43e6c9 100644 --- a/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappingsTest.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.extensions import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeIngredientV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeInstructionV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeSettingsV0 +import gq.kirmanak.mealient.data.add.AddRecipeInfo +import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo +import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo +import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft import org.junit.Test @@ -22,42 +22,42 @@ class RemoteToLocalMappingsTest { areCommentsDisabled = true, ) - val expected = AddRecipeRequestV0( + val expected = AddRecipeInfo( name = "Recipe name", description = "Recipe description", recipeYield = "Recipe yield", recipeIngredient = listOf( - AddRecipeIngredientV0(note = "Recipe ingredient 1"), - AddRecipeIngredientV0(note = "Recipe ingredient 2") + AddRecipeIngredientInfo(note = "Recipe ingredient 1"), + AddRecipeIngredientInfo(note = "Recipe ingredient 2") ), recipeInstructions = listOf( - AddRecipeInstructionV0(text = "Recipe instruction 1"), - AddRecipeInstructionV0(text = "Recipe instruction 2") + AddRecipeInstructionInfo(text = "Recipe instruction 1"), + AddRecipeInstructionInfo(text = "Recipe instruction 2") ), - settings = AddRecipeSettingsV0( + settings = AddRecipeSettingsInfo( public = false, disableComments = true, ) ) - assertThat(input.toAddRecipeRequest()).isEqualTo(expected) + assertThat(input.toAddRecipeInfo()).isEqualTo(expected) } @Test fun `when toDraft then fills fields correctly`() { - val request = AddRecipeRequestV0( + val request = AddRecipeInfo( name = "Recipe name", description = "Recipe description", recipeYield = "Recipe yield", recipeIngredient = listOf( - AddRecipeIngredientV0(note = "Recipe ingredient 1"), - AddRecipeIngredientV0(note = "Recipe ingredient 2") + AddRecipeIngredientInfo(note = "Recipe ingredient 1"), + AddRecipeIngredientInfo(note = "Recipe ingredient 2") ), recipeInstructions = listOf( - AddRecipeInstructionV0(text = "Recipe instruction 1"), - AddRecipeInstructionV0(text = "Recipe instruction 2") + AddRecipeInstructionInfo(text = "Recipe instruction 1"), + AddRecipeInstructionInfo(text = "Recipe instruction 2") ), - settings = AddRecipeSettingsV0( + settings = AddRecipeSettingsInfo( public = false, disableComments = true, ) diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt index 3e95fcd..898b2f1 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt @@ -1,8 +1,8 @@ package gq.kirmanak.mealient.ui.add import com.google.common.truth.Truth.assertThat +import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.add.AddRecipeRepo -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.logging.Logger import io.mockk.MockKAnnotations import io.mockk.coEvery @@ -61,21 +61,21 @@ class AddRecipeViewModelTest { @Test fun `when preserve then doesn't update UI`() { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequestV0()) - subject.preserve(AddRecipeRequestV0()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeInfo()) + subject.preserve(AddRecipeInfo()) coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow } } @Test fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequestV0()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeInfo()) val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() } assertThat(actual).isNull() } @Test fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeRequestV0() + val expected = AddRecipeInfo() coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.loadPreservedRequest() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) @@ -83,7 +83,7 @@ class AddRecipeViewModelTest { @Test fun `when clear then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeRequestV0() + val expected = AddRecipeInfo() coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.clear() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) 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 7c888ec..0af5545 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 @@ -1,6 +1,6 @@ package gq.kirmanak.mealient.datasource.v1 -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 +import gq.kirmanak.mealient.datasource.v1.models.AddRecipeRequestV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -10,7 +10,7 @@ interface MealieDataSourceV1 { suspend fun addRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequestV0, + recipe: AddRecipeRequestV1, ): String /** 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 0857a91..8edd86f 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 @@ -3,11 +3,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper import gq.kirmanak.mealient.datasource.decode -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.* import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json import retrofit2.HttpException @@ -26,10 +22,12 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun addRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequestV0 - ): String { - TODO("Not yet implemented") - } + recipe: AddRecipeRequestV1 + ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.addRecipe("$baseUrl/api/recipes/create", token, recipe) }, + logMethod = { "addRecipe" }, + logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } + ) override suspend fun authenticate( baseUrl: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index e441396..6db2042 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -1,11 +1,7 @@ package gq.kirmanak.mealient.datasource.v1 import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipesResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetTokenResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.* import retrofit2.http.* interface MealieServiceV1 { @@ -22,7 +18,7 @@ interface MealieServiceV1 { suspend fun addRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Body addRecipeRequestV0: AddRecipeRequestV0, + @Body addRecipeRequestV0: AddRecipeRequestV1, ): String @GET diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt new file mode 100644 index 0000000..ffa23e1 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt @@ -0,0 +1,54 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class AddRecipeRequestV1( + @SerialName("name") val name: String = "", + @SerialName("description") val description: String = "", + @SerialName("image") val image: String = "", + @SerialName("recipeYield") val recipeYield: String = "", + @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), + @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), + @SerialName("slug") val slug: String = "", + @SerialName("filePath") val filePath: String = "", + @SerialName("tags") val tags: List = emptyList(), + @SerialName("categories") val categories: List = emptyList(), + @SerialName("notes") val notes: List = emptyList(), + @SerialName("extras") val extras: Map = emptyMap(), + @SerialName("assets") val assets: List = emptyList(), + @SerialName("settings") val settings: AddRecipeSettingsV1 = AddRecipeSettingsV1(), +) + +@Serializable +data class AddRecipeIngredientV1( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("food") val food: String? = null, + @SerialName("note") val note: String = "", + @SerialName("quantity") val quantity: Int = 1, + @SerialName("title") val title: String? = null, + @SerialName("unit") val unit: String? = null, +) + +@Serializable +data class AddRecipeInstructionV1( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) + +@Serializable +data class AddRecipeNoteV1( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) + +@Serializable +data class AddRecipeSettingsV1( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("disableComments") val disableComments: Boolean = false, + @SerialName("landscapeView") val landscapeView: Boolean = true, + @SerialName("public") val public: Boolean = true, + @SerialName("showAssets") val showAssets: Boolean = true, + @SerialName("showNutrition") val showNutrition: Boolean = true, +) \ No newline at end of file From 7ac0409cd50e505e35a3371de99ffef81dd6a424 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 10:45:06 +0100 Subject: [PATCH 25/35] Move v0 add recipe models to one file --- .../v0/models/AddRecipeIngredientV0.kt | 14 --------- .../v0/models/AddRecipeInstructionV0.kt | 10 ------ .../datasource/v0/models/AddRecipeNoteV0.kt | 10 ------ .../v0/models/AddRecipeRequestV0.kt | 31 +++++++++++++++++++ .../v0/models/AddRecipeSettingsV0.kt | 14 --------- 5 files changed, 31 insertions(+), 48 deletions(-) delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt deleted file mode 100644 index c4fc75d..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeIngredientV0.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AddRecipeIngredientV0( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("food") val food: String? = null, - @SerialName("note") val note: String = "", - @SerialName("quantity") val quantity: Int = 1, - @SerialName("title") val title: String? = null, - @SerialName("unit") val unit: String? = null, -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt deleted file mode 100644 index eef390b..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeInstructionV0.kt +++ /dev/null @@ -1,10 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AddRecipeInstructionV0( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt deleted file mode 100644 index 1f4a771..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeNoteV0.kt +++ /dev/null @@ -1,10 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AddRecipeNoteV0( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt index eb597f6..dcec314 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt @@ -21,3 +21,34 @@ data class AddRecipeRequestV0( @SerialName("settings") val settings: AddRecipeSettingsV0 = AddRecipeSettingsV0(), ) +@Serializable +data class AddRecipeIngredientV0( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("food") val food: String? = null, + @SerialName("note") val note: String = "", + @SerialName("quantity") val quantity: Int = 1, + @SerialName("title") val title: String? = null, + @SerialName("unit") val unit: String? = null, +) + +@Serializable +data class AddRecipeInstructionV0( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) + +@Serializable +data class AddRecipeNoteV0( + @SerialName("title") val title: String = "", + @SerialName("text") val text: String = "", +) + +@Serializable +data class AddRecipeSettingsV0( + @SerialName("disableAmount") val disableAmount: Boolean = true, + @SerialName("disableComments") val disableComments: Boolean = false, + @SerialName("landscapeView") val landscapeView: Boolean = true, + @SerialName("public") val public: Boolean = true, + @SerialName("showAssets") val showAssets: Boolean = true, + @SerialName("showNutrition") val showNutrition: Boolean = true, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt deleted file mode 100644 index c58d162..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeSettingsV0.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class AddRecipeSettingsV0( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("landscapeView") val landscapeView: Boolean = true, - @SerialName("public") val public: Boolean = true, - @SerialName("showAssets") val showAssets: Boolean = true, - @SerialName("showNutrition") val showNutrition: Boolean = true, -) \ No newline at end of file From 71d5900530b0d1d330e6cccfc2111f8a899c1147 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 11:07:02 +0100 Subject: [PATCH 26/35] Split v1 creation to create/update --- .../data/network/MealieDataSourceWrapper.kt | 10 +++++----- .../data/recipes/network/FullRecipeInfo.kt | 2 +- .../extensions/RemoteToLocalMappings.kt | 7 ++++++- .../datasource/v1/MealieDataSourceV1.kt | 16 ++++++++++------ .../datasource/v1/MealieDataSourceV1Impl.kt | 19 +++++++++++++++---- .../mealient/datasource/v1/MealieServiceV1.kt | 11 +++++++++-- .../v1/models/CreateRecipeRequestV1.kt | 9 +++++++++ .../v1/models/GetRecipeResponseV1.kt | 2 +- ...eRequestV1.kt => UpdateRecipeRequestV1.kt} | 2 +- 9 files changed, 57 insertions(+), 21 deletions(-) create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt rename datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/{AddRecipeRequestV1.kt => UpdateRecipeRequestV1.kt} (98%) 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 4f8f2ea..a9cf2b6 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 @@ -12,10 +12,7 @@ import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.extensions.toFullRecipeInfo -import gq.kirmanak.mealient.extensions.toRecipeSummaryInfo -import gq.kirmanak.mealient.extensions.toV0Request -import gq.kirmanak.mealient.extensions.toV1Request +import gq.kirmanak.mealient.extensions.* import javax.inject.Inject import javax.inject.Singleton @@ -32,7 +29,10 @@ class MealieDataSourceWrapper @Inject constructor( ): String = makeCall { token, url, version -> when (version) { ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request()) - ServerVersion.V1 -> v1Source.addRecipe(url, token, recipe.toV1Request()) + ServerVersion.V1 -> { + val slug = v1Source.createRecipe(url, token, recipe.toV1CreateRequest()) + v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest(slug)) + } } } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt index 395b194..25b5705 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt @@ -7,7 +7,7 @@ data class FullRecipeInfo( val remoteId: String, val name: String, val slug: String, - val image: String, + val image: String?, val description: String, val recipeCategories: List, val tags: List, diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index d45668e..e0b15ca 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -206,7 +206,12 @@ private fun AddRecipeInstructionInfo.toV0Instruction() = AddRecipeInstructionV0( text = text, ) -fun AddRecipeInfo.toV1Request() = AddRecipeRequestV1( + +fun AddRecipeInfo.toV1CreateRequest() = CreateRecipeRequestV1( + name = name, +) + +fun AddRecipeInfo.toV1UpdateRequest(slug: String) = UpdateRecipeRequestV1( name = name, description = description, image = image, 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 0af5545..c485584 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 @@ -1,16 +1,20 @@ package gq.kirmanak.mealient.datasource.v1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.* interface MealieDataSourceV1 { - suspend fun addRecipe( + suspend fun createRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequestV1, + recipe: CreateRecipeRequestV1, + ): String + + suspend fun updateRecipe( + baseUrl: String, + token: String?, + slug: String, + recipe: UpdateRecipeRequestV1, ): String /** 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 8edd86f..b57d0c5 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 @@ -19,16 +19,27 @@ class MealieDataSourceV1Impl @Inject constructor( private val json: Json, ) : MealieDataSourceV1 { - override suspend fun addRecipe( + override suspend fun createRecipe( baseUrl: String, token: String?, - recipe: AddRecipeRequestV1 + recipe: CreateRecipeRequestV1 ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.addRecipe("$baseUrl/api/recipes/create", token, recipe) }, - logMethod = { "addRecipe" }, + block = { service.createRecipe("$baseUrl/api/recipes", token, recipe) }, + logMethod = { "createRecipe" }, logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } ) + override suspend fun updateRecipe( + baseUrl: String, + token: String?, + slug: String, + recipe: UpdateRecipeRequestV1 + ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.updateRecipe("$baseUrl/api/recipes/$slug", token, recipe) }, + logMethod = { "addRecipe" }, + logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug, recipe = $recipe" } + ) + override suspend fun authenticate( baseUrl: String, username: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index 6db2042..b119258 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -15,10 +15,17 @@ interface MealieServiceV1 { ): GetTokenResponseV1 @POST - suspend fun addRecipe( + suspend fun createRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, - @Body addRecipeRequestV0: AddRecipeRequestV1, + @Body addRecipeRequest: CreateRecipeRequestV1, + ): String + + @PUT + suspend fun updateRecipe( + @Url url: String, + @Header(AUTHORIZATION_HEADER_NAME) token: String?, + @Body addRecipeRequest: UpdateRecipeRequestV1, ): String @GET diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt new file mode 100644 index 0000000..bf5bc90 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt @@ -0,0 +1,9 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CreateRecipeRequestV1( + @SerialName("name") val name: String, +) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt index a9ca1f5..ce9b90f 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt @@ -10,7 +10,7 @@ data class GetRecipeResponseV1( @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("slug") val slug: String, - @SerialName("image") val image: String, + @SerialName("image") val image: String? = null, @SerialName("description") val description: String = "", @SerialName("recipeCategory") val recipeCategories: List, @SerialName("tags") val tags: List, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt similarity index 98% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt index ffa23e1..a9a2eca 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/AddRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class AddRecipeRequestV1( +data class UpdateRecipeRequestV1( @SerialName("name") val name: String = "", @SerialName("description") val description: String = "", @SerialName("image") val image: String = "", From 90380dcbf13daa9d664dd3aa9e9d6ecfbc4e8dbe Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 11:08:27 +0100 Subject: [PATCH 27/35] Fix logging method name --- .../kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b57d0c5..d96641a 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 @@ -36,7 +36,7 @@ class MealieDataSourceV1Impl @Inject constructor( recipe: UpdateRecipeRequestV1 ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.updateRecipe("$baseUrl/api/recipes/$slug", token, recipe) }, - logMethod = { "addRecipe" }, + logMethod = { "updateRecipe" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug, recipe = $recipe" } ) From 9ed229f20f2e917d8e9eea2487171c78e5bb95f9 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 11:53:48 +0100 Subject: [PATCH 28/35] Fix parsing update recipe response --- .../mealient/data/network/MealieDataSourceWrapper.kt | 3 ++- .../gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt | 5 ++--- .../gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt | 2 +- .../mealient/datasource/v1/MealieDataSourceV1Impl.kt | 2 +- .../gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt | 4 ++-- .../mealient/datasource/v1/models/UpdateRecipeRequestV1.kt | 3 +-- 6 files changed, 9 insertions(+), 10 deletions(-) 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 a9cf2b6..1fbd9d3 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 @@ -31,7 +31,8 @@ class MealieDataSourceWrapper @Inject constructor( ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request()) ServerVersion.V1 -> { val slug = v1Source.createRecipe(url, token, recipe.toV1CreateRequest()) - v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest(slug)) + v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest()) + slug } } } diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index e0b15ca..84dea9b 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -211,14 +211,12 @@ fun AddRecipeInfo.toV1CreateRequest() = CreateRecipeRequestV1( name = name, ) -fun AddRecipeInfo.toV1UpdateRequest(slug: String) = UpdateRecipeRequestV1( - name = name, +fun AddRecipeInfo.toV1UpdateRequest() = UpdateRecipeRequestV1( description = description, image = image, recipeYield = recipeYield, recipeIngredient = recipeIngredient.map { it.toV1Ingredient() }, recipeInstructions = recipeInstructions.map { it.toV1Instruction() }, - slug = slug, filePath = filePath, tags = tags, categories = categories, @@ -254,4 +252,5 @@ private fun AddRecipeIngredientInfo.toV1Ingredient() = AddRecipeIngredientV1( private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1( title = title, text = text, + ingredientReferences = emptyList(), ) \ 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 c485584..6ba2d6b 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 @@ -15,7 +15,7 @@ interface MealieDataSourceV1 { token: String?, slug: String, recipe: UpdateRecipeRequestV1, - ): String + ): GetRecipeResponseV1 /** * Tries to acquire authentication token using the provided credentials 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 d96641a..e00c0d0 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 @@ -34,7 +34,7 @@ class MealieDataSourceV1Impl @Inject constructor( token: String?, slug: String, recipe: UpdateRecipeRequestV1 - ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( + ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.updateRecipe("$baseUrl/api/recipes/$slug", token, recipe) }, logMethod = { "updateRecipe" }, logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug, recipe = $recipe" } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt index b119258..c6644fc 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt @@ -21,12 +21,12 @@ interface MealieServiceV1 { @Body addRecipeRequest: CreateRecipeRequestV1, ): String - @PUT + @PATCH suspend fun updateRecipe( @Url url: String, @Header(AUTHORIZATION_HEADER_NAME) token: String?, @Body addRecipeRequest: UpdateRecipeRequestV1, - ): String + ): GetRecipeResponseV1 @GET suspend fun getVersion( diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt index a9a2eca..b4a8632 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt @@ -5,13 +5,11 @@ import kotlinx.serialization.Serializable @Serializable data class UpdateRecipeRequestV1( - @SerialName("name") val name: String = "", @SerialName("description") val description: String = "", @SerialName("image") val image: String = "", @SerialName("recipeYield") val recipeYield: String = "", @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("slug") val slug: String = "", @SerialName("filePath") val filePath: String = "", @SerialName("tags") val tags: List = emptyList(), @SerialName("categories") val categories: List = emptyList(), @@ -35,6 +33,7 @@ data class AddRecipeIngredientV1( data class AddRecipeInstructionV1( @SerialName("title") val title: String = "", @SerialName("text") val text: String = "", + @SerialName("ingredientReferences") val ingredientReferences: List = emptyList(), ) @Serializable From 31ccf8822dbcef456b2f5179683d38be170be1c3 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 12:44:33 +0100 Subject: [PATCH 29/35] Remove unused fields --- .../mealient/data/add/AddRecipeInfo.kt | 23 -- .../data/recipes/network/FullRecipeInfo.kt | 17 - .../extensions/RemoteToLocalMappings.kt | 84 +--- .../mealient/test/RecipeImplTestData.kt | 58 --- .../5.json | 374 ++++++++++++++++++ .../gq/kirmanak/mealient/database/AppDb.kt | 17 +- .../recipe/entity/RecipeIngredientEntity.kt | 5 - .../recipe/entity/RecipeInstructionEntity.kt | 1 - .../v0/models/AddRecipeRequestV0.kt | 24 -- .../v0/models/GetRecipeResponseV0.kt | 10 - .../v1/models/GetRecipeResponseV1.kt | 10 - .../v1/models/UpdateRecipeRequestV1.kt | 23 -- 12 files changed, 390 insertions(+), 256 deletions(-) create mode 100644 database/schemas/gq.kirmanak.mealient.database.AppDb/5.json diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt index 69c2ee9..a6283e3 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt @@ -3,44 +3,21 @@ package gq.kirmanak.mealient.data.add data class AddRecipeInfo( val name: String = "", val description: String = "", - val image: String = "", val recipeYield: String = "", val recipeIngredient: List = emptyList(), val recipeInstructions: List = emptyList(), - val slug: String = "", - val filePath: String = "", - val tags: List = emptyList(), - val categories: List = emptyList(), - val notes: List = emptyList(), - val extras: Map = emptyMap(), - val assets: List = emptyList(), val settings: AddRecipeSettingsInfo = AddRecipeSettingsInfo(), ) data class AddRecipeSettingsInfo( - val disableAmount: Boolean = true, val disableComments: Boolean = false, - val landscapeView: Boolean = true, val public: Boolean = true, - val showAssets: Boolean = true, - val showNutrition: Boolean = true, -) - -data class AddRecipeNoteInfo( - val title: String = "", - val text: String = "", ) data class AddRecipeIngredientInfo( - val disableAmount: Boolean = true, - val food: String? = null, val note: String = "", - val quantity: Int = 1, - val title: String? = null, - val unit: String? = null, ) data class AddRecipeInstructionInfo( - val title: String = "", val text: String = "", ) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt index 25b5705..756e4ac 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/FullRecipeInfo.kt @@ -1,34 +1,17 @@ package gq.kirmanak.mealient.data.recipes.network -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime - data class FullRecipeInfo( val remoteId: String, val name: String, - val slug: String, - val image: String?, - val description: String, - val recipeCategories: List, - val tags: List, - val rating: Int?, - val dateAdded: LocalDate, - val dateUpdated: LocalDateTime, val recipeYield: String, val recipeIngredients: List, val recipeInstructions: List, ) data class RecipeIngredientInfo( - val title: String, val note: String, - val unit: String, - val food: String, - val disableAmount: Boolean, - val quantity: Double, ) data class RecipeInstructionInfo( - val title: String, val text: String, ) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 84dea9b..9a95a2d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -1,6 +1,9 @@ package gq.kirmanak.mealient.extensions -import gq.kirmanak.mealient.data.add.* +import gq.kirmanak.mealient.data.add.AddRecipeInfo +import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo +import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo +import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo import gq.kirmanak.mealient.data.baseurl.VersionInfo import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo @@ -21,17 +24,11 @@ fun FullRecipeInfo.toRecipeEntity() = RecipeEntity( fun RecipeIngredientInfo.toRecipeIngredientEntity(remoteId: String) = RecipeIngredientEntity( recipeId = remoteId, - title = title, note = note, - unit = unit, - food = food, - disableAmount = disableAmount, - quantity = quantity ) fun RecipeInstructionInfo.toRecipeInstructionEntity(remoteId: String) = RecipeInstructionEntity( recipeId = remoteId, - title = title, text = text ) @@ -104,105 +101,54 @@ fun AddRecipeInfo.toDraft(): AddRecipeDraft = AddRecipeDraft( fun GetRecipeResponseV0.toFullRecipeInfo() = FullRecipeInfo( remoteId = remoteId.toString(), name = name, - slug = slug, - image = image, - description = description, - recipeCategories = recipeCategories, - tags = tags, - rating = rating, - dateAdded = dateAdded, - dateUpdated = dateUpdated, recipeYield = recipeYield, recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() }, recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() } ) fun GetRecipeIngredientResponseV0.toRecipeIngredientInfo() = RecipeIngredientInfo( - title = title, note = note, - unit = unit, - food = food, - disableAmount = disableAmount, - quantity = quantity ) fun GetRecipeInstructionResponseV0.toRecipeInstructionInfo() = RecipeInstructionInfo( - title = title, text = text ) fun GetRecipeResponseV1.toFullRecipeInfo() = FullRecipeInfo( remoteId = remoteId, name = name, - slug = slug, - image = image, - description = description, - recipeCategories = recipeCategories, - tags = tags, - rating = rating, - dateAdded = dateAdded, - dateUpdated = dateUpdated, recipeYield = recipeYield, recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() }, recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() } ) fun GetRecipeIngredientResponseV1.toRecipeIngredientInfo() = RecipeIngredientInfo( - title = title, note = note, - unit = unit, - food = food, - disableAmount = disableAmount, - quantity = quantity ) fun GetRecipeInstructionResponseV1.toRecipeInstructionInfo() = RecipeInstructionInfo( - title = title, text = text ) fun AddRecipeInfo.toV0Request() = AddRecipeRequestV0( name = name, description = description, - image = image, recipeYield = recipeYield, recipeIngredient = recipeIngredient.map { it.toV0Ingredient() }, recipeInstructions = recipeInstructions.map { it.toV0Instruction() }, - slug = slug, - filePath = filePath, - tags = tags, - categories = categories, - notes = notes.map { it.toV0Note() }, - extras = extras, - assets = assets, settings = settings.toV0Settings(), ) private fun AddRecipeSettingsInfo.toV0Settings() = AddRecipeSettingsV0( - disableAmount = disableAmount, disableComments = disableComments, - landscapeView = landscapeView, public = public, - showAssets = showAssets, - showNutrition = showNutrition, -) - -private fun AddRecipeNoteInfo.toV0Note() = AddRecipeNoteV0( - title = title, - text = text, ) private fun AddRecipeIngredientInfo.toV0Ingredient() = AddRecipeIngredientV0( - disableAmount = disableAmount, - food = food, note = note, - quantity = quantity, - title = title, - unit = unit ) private fun AddRecipeInstructionInfo.toV0Instruction() = AddRecipeInstructionV0( - title = title, text = text, ) @@ -213,44 +159,22 @@ fun AddRecipeInfo.toV1CreateRequest() = CreateRecipeRequestV1( fun AddRecipeInfo.toV1UpdateRequest() = UpdateRecipeRequestV1( description = description, - image = image, recipeYield = recipeYield, recipeIngredient = recipeIngredient.map { it.toV1Ingredient() }, recipeInstructions = recipeInstructions.map { it.toV1Instruction() }, - filePath = filePath, - tags = tags, - categories = categories, - notes = notes.map { it.toV1Note() }, - extras = extras, - assets = assets, settings = settings.toV1Settings(), ) private fun AddRecipeSettingsInfo.toV1Settings() = AddRecipeSettingsV1( - disableAmount = disableAmount, disableComments = disableComments, - landscapeView = landscapeView, public = public, - showAssets = showAssets, - showNutrition = showNutrition, -) - -private fun AddRecipeNoteInfo.toV1Note() = AddRecipeNoteV1( - title = title, - text = text, ) private fun AddRecipeIngredientInfo.toV1Ingredient() = AddRecipeIngredientV1( - disableAmount = disableAmount, - food = food, note = note, - quantity = quantity, - title = title, - unit = unit ) private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1( - title = title, text = text, ingredientReferences = emptyList(), ) \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index 9608cfc..d4ab9e1 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -64,58 +64,32 @@ object RecipeImplTestData { ) private val SUGAR_INGREDIENT = RecipeIngredientInfo( - title = "Sugar", note = "2 oz of white sugar", - unit = "", - food = "", - disableAmount = true, - quantity = 1.0 ) val BREAD_INGREDIENT = RecipeIngredientInfo( - title = "Bread", note = "2 oz of white bread", - unit = "", - food = "", - disableAmount = false, - quantity = 2.0 ) private val MILK_INGREDIENT = RecipeIngredientInfo( - title = "Milk", note = "2 oz of white milk", - unit = "", - food = "", - disableAmount = true, - quantity = 3.0 ) val MIX_INSTRUCTION = RecipeInstructionInfo( - title = "Mix", text = "Mix the ingredients" ) private val BAKE_INSTRUCTION = RecipeInstructionInfo( - title = "Bake", text = "Bake the ingredients" ) private val BOIL_INSTRUCTION = RecipeInstructionInfo( - title = "Boil", text = "Boil the ingredients" ) val GET_CAKE_RESPONSE = FullRecipeInfo( remoteId = "1", name = "Cake", - slug = "cake", - image = "86", - description = "A tasty cake", - recipeCategories = listOf("dessert", "tasty"), - tags = listOf("gluten", "allergic"), - rating = 4, - dateAdded = LocalDate.parse("2021-11-13"), - dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), recipeYield = "4 servings", recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT), recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION) @@ -124,14 +98,6 @@ object RecipeImplTestData { val GET_PORRIDGE_RESPONSE = FullRecipeInfo( remoteId = "2", name = "Porridge", - slug = "porridge", - image = "89", - description = "A tasty porridge", - recipeCategories = listOf("porridge", "tasty"), - tags = listOf("gluten", "milk"), - rating = 5, - dateAdded = LocalDate.parse("2021-11-12"), - dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), recipeYield = "3 servings", recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT), recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION) @@ -140,14 +106,12 @@ object RecipeImplTestData { val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 1, recipeId = "1", - title = "Mix", text = "Mix the ingredients", ) private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 2, recipeId = "1", - title = "Bake", text = "Bake the ingredients", ) @@ -159,23 +123,13 @@ object RecipeImplTestData { private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 1, recipeId = "1", - title = "Sugar", note = "2 oz of white sugar", - unit = "", - food = "", - disableAmount = true, - quantity = 1.0 ) val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 2, recipeId = "1", - title = "Bread", note = "2 oz of white bread", - unit = "", - food = "", - disableAmount = false, - quantity = 2.0 ) val FULL_CAKE_INFO_ENTITY = FullRecipeEntity( @@ -199,36 +153,24 @@ object RecipeImplTestData { private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 4, recipeId = "2", - title = "Milk", note = "2 oz of white milk", - unit = "", - food = "", - disableAmount = true, - quantity = 3.0 ) private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity( localId = 3, recipeId = "2", - title = "Sugar", note = "2 oz of white sugar", - unit = "", - food = "", - disableAmount = true, - quantity = 1.0 ) private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 3, recipeId = "2", - title = "Mix", text = "Mix the ingredients" ) private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity( localId = 4, recipeId = "2", - title = "Boil", text = "Boil the ingredients" ) diff --git a/database/schemas/gq.kirmanak.mealient.database.AppDb/5.json b/database/schemas/gq.kirmanak.mealient.database.AppDb/5.json new file mode 100644 index 0000000..00986f8 --- /dev/null +++ b/database/schemas/gq.kirmanak.mealient.database.AppDb/5.json @@ -0,0 +1,374 @@ +{ + "formatVersion": 1, + "database": { + "version": 5, + "identityHash": "e75a1e16503fdf60c62b7f9d17ec0bc6", + "entities": [ + { + "tableName": "categories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_categories_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_categories_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "category_recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `recipe_id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "categoryId", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "category_id", + "recipe_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_category_recipe_category_id_recipe_id", + "unique": true, + "columnNames": [ + "category_id", + "recipe_id" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_category_recipe_category_id_recipe_id` ON `${TABLE_NAME}` (`category_id`, `recipe_id`)" + }, + { + "name": "index_category_recipe_recipe_id", + "unique": false, + "columnNames": [ + "recipe_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_category_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" + } + ], + "foreignKeys": [ + { + "table": "categories", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "category_id" + ], + "referencedColumns": [ + "local_id" + ] + }, + { + "table": "recipe_summaries", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "recipe_id" + ], + "referencedColumns": [ + "remote_id" + ] + } + ] + }, + { + "tableName": "tags", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_tags_name", + "unique": true, + "columnNames": [ + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tags_name` ON `${TABLE_NAME}` (`name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "tag_recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`tag_id`, `recipe_id`), FOREIGN KEY(`tag_id`) REFERENCES `tags`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "tagId", + "columnName": "tag_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "tag_id", + "recipe_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_tag_recipe_recipe_id", + "unique": false, + "columnNames": [ + "recipe_id" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_tag_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)" + } + ], + "foreignKeys": [ + { + "table": "tags", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "tag_id" + ], + "referencedColumns": [ + "local_id" + ] + }, + { + "table": "recipe_summaries", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "recipe_id" + ], + "referencedColumns": [ + "remote_id" + ] + } + ] + }, + { + "tableName": "recipe_summaries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `image` TEXT, `description` TEXT NOT NULL, `rating` INTEGER, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "rating", + "columnName": "rating", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateUpdated", + "columnName": "date_updated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageId", + "columnName": "image_id", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipeYield", + "columnName": "recipe_yield", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_ingredient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `note` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_instruction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e75a1e16503fdf60c62b7f9d17ec0bc6')" + ] + } +} \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt index b71d9d6..62d87f7 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt @@ -1,14 +1,12 @@ package gq.kirmanak.mealient.database -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters +import androidx.room.* +import androidx.room.migration.AutoMigrationSpec import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* @Database( - version = 4, + version = 5, entities = [ CategoryEntity::class, CategoryRecipeEntity::class, @@ -23,9 +21,18 @@ import gq.kirmanak.mealient.database.recipe.entity.* autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 3, to = 4), + AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class), ] ) @TypeConverters(RoomTypeConverters::class) abstract class AppDb : RoomDatabase() { abstract fun recipeDao(): RecipeDao + + @DeleteColumn(tableName = "recipe_instruction", columnName = "title") + @DeleteColumn(tableName = "recipe_ingredient", columnName = "title") + @DeleteColumn(tableName = "recipe_ingredient", columnName = "unit") + @DeleteColumn(tableName = "recipe_ingredient", columnName = "food") + @DeleteColumn(tableName = "recipe_ingredient", columnName = "disable_amount") + @DeleteColumn(tableName = "recipe_ingredient", columnName = "quantity") + class From4To5Migration : AutoMigrationSpec } \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt index 8c55f5d..31b7b6a 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeIngredientEntity.kt @@ -8,10 +8,5 @@ import androidx.room.PrimaryKey data class RecipeIngredientEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, @ColumnInfo(name = "recipe_id") val recipeId: String, - @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "note") val note: String, - @ColumnInfo(name = "unit") val unit: String, - @ColumnInfo(name = "food") val food: String, - @ColumnInfo(name = "disable_amount") val disableAmount: Boolean, - @ColumnInfo(name = "quantity") val quantity: Double, ) \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt index 1510b91..c5905fa 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeInstructionEntity.kt @@ -8,6 +8,5 @@ import androidx.room.PrimaryKey data class RecipeInstructionEntity( @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, @ColumnInfo(name = "recipe_id") val recipeId: String, - @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "text") val text: String, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt index dcec314..35f967e 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt @@ -7,48 +7,24 @@ import kotlinx.serialization.Serializable data class AddRecipeRequestV0( @SerialName("name") val name: String = "", @SerialName("description") val description: String = "", - @SerialName("image") val image: String = "", @SerialName("recipeYield") val recipeYield: String = "", @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("slug") val slug: String = "", - @SerialName("filePath") val filePath: String = "", - @SerialName("tags") val tags: List = emptyList(), - @SerialName("categories") val categories: List = emptyList(), - @SerialName("notes") val notes: List = emptyList(), - @SerialName("extras") val extras: Map = emptyMap(), - @SerialName("assets") val assets: List = emptyList(), @SerialName("settings") val settings: AddRecipeSettingsV0 = AddRecipeSettingsV0(), ) @Serializable data class AddRecipeIngredientV0( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("food") val food: String? = null, @SerialName("note") val note: String = "", - @SerialName("quantity") val quantity: Int = 1, - @SerialName("title") val title: String? = null, - @SerialName("unit") val unit: String? = null, ) @Serializable data class AddRecipeInstructionV0( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) - -@Serializable -data class AddRecipeNoteV0( - @SerialName("title") val title: String = "", @SerialName("text") val text: String = "", ) @Serializable data class AddRecipeSettingsV0( - @SerialName("disableAmount") val disableAmount: Boolean = true, @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("landscapeView") val landscapeView: Boolean = true, @SerialName("public") val public: Boolean = true, - @SerialName("showAssets") val showAssets: Boolean = true, - @SerialName("showNutrition") val showNutrition: Boolean = true, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt index f940811..414badf 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt @@ -1,7 +1,5 @@ package gq.kirmanak.mealient.datasource.v0.models -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -9,14 +7,6 @@ import kotlinx.serialization.Serializable data class GetRecipeResponseV0( @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, - @SerialName("slug") val slug: String, - @SerialName("image") val image: String, - @SerialName("description") val description: String = "", - @SerialName("recipeCategory") val recipeCategories: List, - @SerialName("tags") val tags: List, - @SerialName("rating") val rating: Int?, - @SerialName("dateAdded") val dateAdded: LocalDate, - @SerialName("dateUpdated") val dateUpdated: LocalDateTime, @SerialName("recipeYield") val recipeYield: String = "", @SerialName("recipeIngredient") val recipeIngredients: List, @SerialName("recipeInstructions") val recipeInstructions: List, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt index ce9b90f..bda326f 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt @@ -1,7 +1,5 @@ package gq.kirmanak.mealient.datasource.v1.models -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -9,14 +7,6 @@ import kotlinx.serialization.Serializable data class GetRecipeResponseV1( @SerialName("id") val remoteId: String, @SerialName("name") val name: String, - @SerialName("slug") val slug: String, - @SerialName("image") val image: String? = null, - @SerialName("description") val description: String = "", - @SerialName("recipeCategory") val recipeCategories: List, - @SerialName("tags") val tags: List, - @SerialName("rating") val rating: Int?, - @SerialName("dateAdded") val dateAdded: LocalDate, - @SerialName("dateUpdated") val dateUpdated: LocalDateTime, @SerialName("recipeYield") val recipeYield: String = "", @SerialName("recipeIngredient") val recipeIngredients: List, @SerialName("recipeInstructions") val recipeInstructions: List, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt index b4a8632..019bd8c 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt @@ -6,48 +6,25 @@ import kotlinx.serialization.Serializable @Serializable data class UpdateRecipeRequestV1( @SerialName("description") val description: String = "", - @SerialName("image") val image: String = "", @SerialName("recipeYield") val recipeYield: String = "", @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("filePath") val filePath: String = "", - @SerialName("tags") val tags: List = emptyList(), - @SerialName("categories") val categories: List = emptyList(), - @SerialName("notes") val notes: List = emptyList(), - @SerialName("extras") val extras: Map = emptyMap(), - @SerialName("assets") val assets: List = emptyList(), @SerialName("settings") val settings: AddRecipeSettingsV1 = AddRecipeSettingsV1(), ) @Serializable data class AddRecipeIngredientV1( - @SerialName("disableAmount") val disableAmount: Boolean = true, - @SerialName("food") val food: String? = null, @SerialName("note") val note: String = "", - @SerialName("quantity") val quantity: Int = 1, - @SerialName("title") val title: String? = null, - @SerialName("unit") val unit: String? = null, ) @Serializable data class AddRecipeInstructionV1( - @SerialName("title") val title: String = "", @SerialName("text") val text: String = "", @SerialName("ingredientReferences") val ingredientReferences: List = emptyList(), ) -@Serializable -data class AddRecipeNoteV1( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String = "", -) - @Serializable data class AddRecipeSettingsV1( - @SerialName("disableAmount") val disableAmount: Boolean = true, @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("landscapeView") val landscapeView: Boolean = true, @SerialName("public") val public: Boolean = true, - @SerialName("showAssets") val showAssets: Boolean = true, - @SerialName("showNutrition") val showNutrition: Boolean = true, ) \ No newline at end of file From 7d64215b63d77f15c65fe68ea5d0d97855e4f876 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:01:06 +0100 Subject: [PATCH 30/35] Remove unused data --- .../data/recipes/db/RecipeStorageImpl.kt | 62 +------ .../data/recipes/network/RecipeSummaryInfo.kt | 8 +- .../extensions/RemoteToLocalMappings.kt | 10 -- .../data/recipes/db/RecipeStorageImplTest.kt | 88 ---------- .../mealient/test/RecipeImplTestData.kt | 12 -- .../6.json | 160 ++++++++++++++++++ .../gq/kirmanak/mealient/database/AppDb.kt | 15 +- .../mealient/database/recipe/RecipeDao.kt | 36 ---- .../database/recipe/entity/CategoryEntity.kt | 12 -- .../recipe/entity/CategoryRecipeEntity.kt | 29 ---- .../recipe/entity/RecipeSummaryEntity.kt | 8 +- .../database/recipe/entity/TagEntity.kt | 12 -- .../database/recipe/entity/TagRecipeEntity.kt | 27 --- .../v0/models/GetRecipeSummaryResponseV0.kt | 10 +- .../v1/models/GetRecipeSummaryResponseV1.kt | 10 +- 15 files changed, 177 insertions(+), 322 deletions(-) create mode 100644 database/schemas/gq.kirmanak.mealient.database.AppDb/6.json delete mode 100644 database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryEntity.kt delete mode 100644 database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryRecipeEntity.kt delete mode 100644 database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagEntity.kt delete mode 100644 database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagRecipeEntity.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt index 8f5224e..de086fc 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImpl.kt @@ -6,7 +6,8 @@ import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo import gq.kirmanak.mealient.database.AppDb import gq.kirmanak.mealient.database.recipe.RecipeDao -import gq.kirmanak.mealient.database.recipe.entity.* +import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity +import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.extensions.recipeEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity @@ -27,67 +28,10 @@ class RecipeStorageImpl @Inject constructor( ) = db.withTransaction { logger.v { "saveRecipes() called with $recipes" } - val tagEntities = mutableSetOf() - tagEntities.addAll(recipeDao.queryAllTags()) - - val categoryEntities = mutableSetOf() - categoryEntities.addAll(recipeDao.queryAllCategories()) - - val tagRecipeEntities = mutableSetOf() - val categoryRecipeEntities = mutableSetOf() - for (recipe in recipes) { val recipeSummaryEntity = recipe.recipeEntity() recipeDao.insertRecipe(recipeSummaryEntity) - - for (tag in recipe.tags) { - val tagId = getIdOrInsert(tagEntities, tag) - tagRecipeEntities += TagRecipeEntity(tagId, recipeSummaryEntity.remoteId) - } - - for (category in recipe.recipeCategories) { - val categoryId = getOrInsert(categoryEntities, category) - categoryRecipeEntities += CategoryRecipeEntity( - categoryId, - recipeSummaryEntity.remoteId - ) - } } - - recipeDao.insertTagRecipeEntities(tagRecipeEntities) - recipeDao.insertCategoryRecipeEntities(categoryRecipeEntities) - } - - private suspend fun getOrInsert( - categoryEntities: MutableSet, - category: String - ): Long { - val existingCategory = categoryEntities.find { it.name == category } - val categoryId = if (existingCategory == null) { - val categoryEntity = CategoryEntity(name = category) - val newId = recipeDao.insertCategory(categoryEntity) - categoryEntities.add(categoryEntity.copy(localId = newId)) - newId - } else { - existingCategory.localId - } - return categoryId - } - - private suspend fun getIdOrInsert( - tagEntities: MutableSet, - tag: String - ): Long { - val existingTag = tagEntities.find { it.name == tag } - val tagId = if (existingTag == null) { - val tagEntity = TagEntity(name = tag) - val newId = recipeDao.insertTag(tagEntity) - tagEntities.add(tagEntity.copy(localId = newId)) - newId - } else { - existingTag.localId - } - return tagId } @@ -108,8 +52,6 @@ class RecipeStorageImpl @Inject constructor( logger.v { "clearAllLocalData() called" } db.withTransaction { recipeDao.removeAllRecipes() - recipeDao.removeAllCategories() - recipeDao.removeAllTags() } } diff --git a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt index 49a67ab..9e9cf8f 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/recipes/network/RecipeSummaryInfo.kt @@ -7,12 +7,8 @@ data class RecipeSummaryInfo( val remoteId: String, val name: String, val slug: String, - val image: String?, val description: String = "", - val recipeCategories: List, - val tags: List, - val rating: Int?, - val dateAdded: LocalDate, - val dateUpdated: LocalDateTime, val imageId: String, + val dateAdded: LocalDate, + val dateUpdated: LocalDateTime ) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index 9a95a2d..c457429 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -36,11 +36,7 @@ fun GetRecipeSummaryResponseV0.toRecipeSummaryInfo() = RecipeSummaryInfo( remoteId = remoteId.toString(), name = name, slug = slug, - image = image, description = description, - recipeCategories = recipeCategories, - tags = tags, - rating = rating, dateAdded = dateAdded, dateUpdated = dateUpdated, imageId = slug, @@ -50,11 +46,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo( remoteId = remoteId, name = name, slug = slug, - image = image, description = description, - recipeCategories = recipeCategories, - tags = tags, - rating = rating, dateAdded = dateAdded, dateUpdated = dateUpdated, imageId = remoteId, @@ -64,9 +56,7 @@ fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity( remoteId = remoteId, name = name, slug = slug, - image = image, description = description, - rating = rating, dateAdded = dateAdded, dateUpdated = dateUpdated, imageId = imageId, diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt index d432f53..8dddb8f 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/db/RecipeStorageImplTest.kt @@ -3,10 +3,6 @@ package gq.kirmanak.mealient.data.recipes.db import com.google.common.truth.Truth.assertThat import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.database.AppDb -import gq.kirmanak.mealient.database.recipe.entity.CategoryEntity -import gq.kirmanak.mealient.database.recipe.entity.CategoryRecipeEntity -import gq.kirmanak.mealient.database.recipe.entity.TagEntity -import gq.kirmanak.mealient.database.recipe.entity.TagRecipeEntity import gq.kirmanak.mealient.test.HiltRobolectricTest import gq.kirmanak.mealient.test.RecipeImplTestData.BREAD_INGREDIENT import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY @@ -36,28 +32,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() { @Inject lateinit var appDb: AppDb - @Test - fun `when saveRecipes then saves tags`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - val actualTags = appDb.recipeDao().queryAllTags() - assertThat(actualTags).containsExactly( - TagEntity(localId = 1, name = "gluten"), - TagEntity(localId = 2, name = "allergic"), - TagEntity(localId = 3, name = "milk") - ) - } - - @Test - fun `when saveRecipes then saves categories`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - val actual = appDb.recipeDao().queryAllCategories() - assertThat(actual).containsExactly( - CategoryEntity(localId = 1, name = "dessert"), - CategoryEntity(localId = 2, name = "tasty"), - CategoryEntity(localId = 3, name = "porridge") - ) - } - @Test fun `when saveRecipes then saves recipes`() = runTest { subject.saveRecipes(TEST_RECIPE_SUMMARIES) @@ -68,30 +42,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() { ) } - @Test - fun `when saveRecipes then saves category recipes`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - val actual = appDb.recipeDao().queryAllCategoryRecipes() - assertThat(actual).containsExactly( - CategoryRecipeEntity(categoryId = 1, recipeId = "1"), - CategoryRecipeEntity(categoryId = 2, recipeId = "1"), - CategoryRecipeEntity(categoryId = 3, recipeId = "2"), - CategoryRecipeEntity(categoryId = 2, recipeId = "2") - ) - } - - @Test - fun `when saveRecipes then saves tag recipes`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - val actual = appDb.recipeDao().queryAllTagRecipes() - assertThat(actual).containsExactly( - TagRecipeEntity(tagId = 1, recipeId = "1"), - TagRecipeEntity(tagId = 2, recipeId = "1"), - TagRecipeEntity(tagId = 3, recipeId = "2"), - TagRecipeEntity(tagId = 1, recipeId = "2"), - ) - } - @Test fun `when refreshAll then old recipes aren't preserved`() = runTest { subject.saveRecipes(TEST_RECIPE_SUMMARIES) @@ -100,28 +50,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() { assertThat(actual).containsExactly(CAKE_RECIPE_SUMMARY_ENTITY) } - @Test - fun `when refreshAll then old category recipes aren't preserved`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE)) - val actual = appDb.recipeDao().queryAllCategoryRecipes() - assertThat(actual).containsExactly( - CategoryRecipeEntity(categoryId = 1, recipeId = "1"), - CategoryRecipeEntity(categoryId = 2, recipeId = "1"), - ) - } - - @Test - fun `when refreshAll then old tag recipes aren't preserved`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE)) - val actual = appDb.recipeDao().queryAllTagRecipes() - assertThat(actual).containsExactly( - TagRecipeEntity(tagId = 1, recipeId = "1"), - TagRecipeEntity(tagId = 2, recipeId = "1"), - ) - } - @Test fun `when clearAllLocalData then recipes aren't preserved`() = runTest { subject.saveRecipes(TEST_RECIPE_SUMMARIES) @@ -130,22 +58,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() { assertThat(actual).isEmpty() } - @Test - fun `when clearAllLocalData then categories aren't preserved`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - subject.clearAllLocalData() - val actual = appDb.recipeDao().queryAllCategories() - assertThat(actual).isEmpty() - } - - @Test - fun `when clearAllLocalData then tags aren't preserved`() = runTest { - subject.saveRecipes(TEST_RECIPE_SUMMARIES) - subject.clearAllLocalData() - val actual = appDb.recipeDao().queryAllTags() - assertThat(actual).isEmpty() - } - @Test fun `when saveRecipeInfo then saves recipe info`() = runTest { subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE)) diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index d4ab9e1..049d6ad 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -13,11 +13,7 @@ object RecipeImplTestData { remoteId = "1", name = "Cake", slug = "cake", - image = "86", description = "A tasty cake", - recipeCategories = listOf("dessert", "tasty"), - tags = listOf("gluten", "allergic"), - rating = 4, dateAdded = LocalDate.parse("2021-11-13"), dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), imageId = "cake", @@ -27,11 +23,7 @@ object RecipeImplTestData { remoteId = "2", name = "Porridge", slug = "porridge", - image = "89", description = "A tasty porridge", - recipeCategories = listOf("porridge", "tasty"), - tags = listOf("gluten", "milk"), - rating = 5, dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), imageId = "porridge", @@ -43,9 +35,7 @@ object RecipeImplTestData { remoteId = "1", name = "Cake", slug = "cake", - image = "86", description = "A tasty cake", - rating = 4, dateAdded = LocalDate.parse("2021-11-13"), dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), imageId = "cake", @@ -55,9 +45,7 @@ object RecipeImplTestData { remoteId = "2", name = "Porridge", slug = "porridge", - image = "89", description = "A tasty porridge", - rating = 5, dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), imageId = "porridge", diff --git a/database/schemas/gq.kirmanak.mealient.database.AppDb/6.json b/database/schemas/gq.kirmanak.mealient.database.AppDb/6.json new file mode 100644 index 0000000..81e2dc6 --- /dev/null +++ b/database/schemas/gq.kirmanak.mealient.database.AppDb/6.json @@ -0,0 +1,160 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "f6e28dd617e4d4a6843a7865c9da736d", + "entities": [ + { + "tableName": "recipe_summaries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `description` TEXT NOT NULL, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dateAdded", + "columnName": "date_added", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateUpdated", + "columnName": "date_updated", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageId", + "columnName": "image_id", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipeYield", + "columnName": "recipe_yield", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "remote_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_ingredient", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `note` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "note", + "columnName": "note", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "recipe_instruction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "localId", + "columnName": "local_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "recipeId", + "columnName": "recipe_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "local_id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f6e28dd617e4d4a6843a7865c9da736d')" + ] + } +} \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt index 62d87f7..37571cd 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt @@ -6,12 +6,8 @@ import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* @Database( - version = 5, + version = 6, entities = [ - CategoryEntity::class, - CategoryRecipeEntity::class, - TagEntity::class, - TagRecipeEntity::class, RecipeSummaryEntity::class, RecipeEntity::class, RecipeIngredientEntity::class, @@ -22,6 +18,7 @@ import gq.kirmanak.mealient.database.recipe.entity.* AutoMigration(from = 1, to = 2), AutoMigration(from = 3, to = 4), AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class), + AutoMigration(from = 5, to = 6, spec = AppDb.From5To6Migration::class), ] ) @TypeConverters(RoomTypeConverters::class) @@ -35,4 +32,12 @@ abstract class AppDb : RoomDatabase() { @DeleteColumn(tableName = "recipe_ingredient", columnName = "disable_amount") @DeleteColumn(tableName = "recipe_ingredient", columnName = "quantity") class From4To5Migration : AutoMigrationSpec + + @DeleteColumn(tableName = "recipe_summaries", columnName = "image") + @DeleteColumn(tableName = "recipe_summaries", columnName = "rating") + @DeleteTable(tableName = "tag_recipe") + @DeleteTable(tableName = "tags") + @DeleteTable(tableName = "categories") + @DeleteTable(tableName = "category_recipe") + class From5To6Migration : AutoMigrationSpec } \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt index 091ec3f..b3d1f72 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/RecipeDao.kt @@ -6,54 +6,18 @@ import gq.kirmanak.mealient.database.recipe.entity.* @Dao interface RecipeDao { - @Query("SELECT * FROM tags") - suspend fun queryAllTags(): List - - @Query("SELECT * FROM categories") - suspend fun queryAllCategories(): List - @Query("SELECT * FROM recipe_summaries ORDER BY date_added DESC") fun queryRecipesByPages(): PagingSource @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity) - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertTag(tagEntity: TagEntity): Long - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertTagRecipeEntity(tagRecipeEntity: TagRecipeEntity) - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertCategory(categoryEntity: CategoryEntity): Long - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertCategoryRecipeEntity(categoryRecipeEntity: CategoryRecipeEntity) - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertTagRecipeEntities(tagRecipeEntities: Set) - - @Insert(onConflict = OnConflictStrategy.IGNORE) - suspend fun insertCategoryRecipeEntities(categoryRecipeEntities: Set) - @Query("DELETE FROM recipe_summaries") suspend fun removeAllRecipes() - @Query("DELETE FROM tags") - suspend fun removeAllTags() - - @Query("DELETE FROM categories") - suspend fun removeAllCategories() - @Query("SELECT * FROM recipe_summaries ORDER BY date_updated DESC") suspend fun queryAllRecipes(): List - @Query("SELECT * FROM category_recipe") - suspend fun queryAllCategoryRecipes(): List - - @Query("SELECT * FROM tag_recipe") - suspend fun queryAllTagRecipes(): List - @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertRecipe(recipe: RecipeEntity) diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryEntity.kt deleted file mode 100644 index 4384ded..0000000 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryEntity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gq.kirmanak.mealient.database.recipe.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey - -@Entity(tableName = "categories", indices = [Index(value = ["name"], unique = true)]) -data class CategoryEntity( - @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, - @ColumnInfo(name = "name") val name: String, -) \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryRecipeEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryRecipeEntity.kt deleted file mode 100644 index 1962af1..0000000 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/CategoryRecipeEntity.kt +++ /dev/null @@ -1,29 +0,0 @@ -package gq.kirmanak.mealient.database.recipe.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey -import androidx.room.Index - -@Entity( - tableName = "category_recipe", - primaryKeys = ["category_id", "recipe_id"], - indices = [Index(value = ["category_id", "recipe_id"], unique = true)], - foreignKeys = [ForeignKey( - entity = CategoryEntity::class, - parentColumns = ["local_id"], - childColumns = ["category_id"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.CASCADE - ), ForeignKey( - entity = RecipeSummaryEntity::class, - parentColumns = ["remote_id"], - childColumns = ["recipe_id"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.CASCADE - )] -) -data class CategoryRecipeEntity( - @ColumnInfo(name = "category_id") val categoryId: Long, - @ColumnInfo(name = "recipe_id", index = true) val recipeId: String, -) \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt index 0bde53e..afbc99d 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/RecipeSummaryEntity.kt @@ -11,14 +11,8 @@ data class RecipeSummaryEntity( @PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "slug") val slug: String, - @ColumnInfo(name = "image") val image: String?, @ColumnInfo(name = "description") val description: String, - @ColumnInfo(name = "rating") val rating: Int?, @ColumnInfo(name = "date_added") val dateAdded: LocalDate, @ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime, @ColumnInfo(name = "image_id") val imageId: String?, -) { - override fun toString(): String { - return "RecipeSummaryEntity(remoteId=$remoteId, name='$name')" - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagEntity.kt deleted file mode 100644 index 460c649..0000000 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagEntity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gq.kirmanak.mealient.database.recipe.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey - -@Entity(tableName = "tags", indices = [Index(value = ["name"], unique = true)]) -data class TagEntity( - @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0, - @ColumnInfo(name = "name") val name: String -) \ No newline at end of file diff --git a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagRecipeEntity.kt b/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagRecipeEntity.kt deleted file mode 100644 index 5d4b27b..0000000 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/recipe/entity/TagRecipeEntity.kt +++ /dev/null @@ -1,27 +0,0 @@ -package gq.kirmanak.mealient.database.recipe.entity - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.ForeignKey - -@Entity( - tableName = "tag_recipe", - primaryKeys = ["tag_id", "recipe_id"], - foreignKeys = [ForeignKey( - entity = TagEntity::class, - parentColumns = ["local_id"], - childColumns = ["tag_id"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.CASCADE - ), ForeignKey( - entity = RecipeSummaryEntity::class, - parentColumns = ["remote_id"], - childColumns = ["recipe_id"], - onDelete = ForeignKey.CASCADE, - onUpdate = ForeignKey.CASCADE - )] -) -data class TagRecipeEntity( - @ColumnInfo(name = "tag_id") val tagId: Long, - @ColumnInfo(name = "recipe_id", index = true) val recipeId: String, -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt index 6fdd0f0..cc28c55 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt @@ -10,15 +10,7 @@ data class GetRecipeSummaryResponseV0( @SerialName("id") val remoteId: Int, @SerialName("name") val name: String, @SerialName("slug") val slug: String, - @SerialName("image") val image: String?, @SerialName("description") val description: String = "", - @SerialName("recipeCategory") val recipeCategories: List, - @SerialName("tags") val tags: List, - @SerialName("rating") val rating: Int?, @SerialName("dateAdded") val dateAdded: LocalDate, @SerialName("dateUpdated") val dateUpdated: LocalDateTime -) { - override fun toString(): String { - return "GetRecipeSummaryResponse(remoteId=$remoteId, name='$name')" - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt index 3ec4a06..f4512b8 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt @@ -10,15 +10,7 @@ data class GetRecipeSummaryResponseV1( @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("slug") val slug: String, - @SerialName("image") val image: String?, @SerialName("description") val description: String = "", - @SerialName("recipeCategory") val recipeCategories: List, - @SerialName("tags") val tags: List, - @SerialName("rating") val rating: Int?, @SerialName("dateAdded") val dateAdded: LocalDate, @SerialName("dateUpdated") val dateUpdated: LocalDateTime -) { - override fun toString(): String { - return "GetRecipeSummaryResponseV1(remoteId=$remoteId, name='$name')" - } -} \ No newline at end of file +) \ No newline at end of file From 64d4d47be24bd665efa9ade3aa7e310222db1c13 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:07:50 +0100 Subject: [PATCH 31/35] Cleanup responses a bit more --- .../v0/models/GetRecipeIngredientResponseV0.kt | 14 -------------- .../v0/models/GetRecipeInstructionResponseV0.kt | 10 ---------- .../datasource/v0/models/GetRecipeResponseV0.kt | 10 ++++++++++ .../datasource/v0/models/GetTokenResponseV0.kt | 4 +++- .../datasource/v0/models/VersionResponseV0.kt | 2 -- .../mealient/datasource/v1/models/ErrorDetailV1.kt | 4 +++- .../v1/models/GetRecipeIngredientResponseV1.kt | 14 -------------- .../v1/models/GetRecipeInstructionResponseV1.kt | 10 ---------- .../datasource/v1/models/GetRecipeResponseV1.kt | 5 +++++ .../datasource/v1/models/GetRecipesResponseV1.kt | 5 +++++ .../datasource/v1/models/GetTokenResponseV1.kt | 1 - .../datasource/v1/models/VersionResponseV1.kt | 3 --- .../datasource/MealieDataSourceV0ImplTest.kt | 2 +- 13 files changed, 27 insertions(+), 57 deletions(-) delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt delete mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt deleted file mode 100644 index 8def202..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeIngredientResponseV0.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeIngredientResponseV0( - @SerialName("title") val title: String = "", - @SerialName("note") val note: String = "", - @SerialName("unit") val unit: String = "", - @SerialName("food") val food: String = "", - @SerialName("disableAmount") val disableAmount: Boolean, - @SerialName("quantity") val quantity: Double, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt deleted file mode 100644 index 1eaef94..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeInstructionResponseV0.kt +++ /dev/null @@ -1,10 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeInstructionResponseV0( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt index 414badf..2930c61 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt @@ -11,3 +11,13 @@ data class GetRecipeResponseV0( @SerialName("recipeIngredient") val recipeIngredients: List, @SerialName("recipeInstructions") val recipeInstructions: List, ) + +@Serializable +data class GetRecipeIngredientResponseV0( + @SerialName("note") val note: String = "", +) + +@Serializable +data class GetRecipeInstructionResponseV0( + @SerialName("text") val text: String, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt index 1a21be2..2e4cfa9 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt @@ -4,4 +4,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetTokenResponseV0(@SerialName("access_token") val accessToken: String) \ No newline at end of file +data class GetTokenResponseV0( + @SerialName("access_token") val accessToken: String, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt index 8cb8d25..590e5d4 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt @@ -5,7 +5,5 @@ import kotlinx.serialization.Serializable @Serializable data class VersionResponseV0( - @SerialName("production") val production: Boolean, @SerialName("version") val version: String, - @SerialName("demoStatus") val demoStatus: Boolean, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt index b3f1810..54f3370 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt @@ -4,4 +4,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ErrorDetailV1(@SerialName("detail") val detail: String? = null) \ No newline at end of file +data class ErrorDetailV1( + @SerialName("detail") val detail: String? = null, +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt deleted file mode 100644 index 68bd444..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientResponseV1.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.v1.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeIngredientResponseV1( - @SerialName("title") val title: String = "", - @SerialName("note") val note: String = "", - @SerialName("unit") val unit: String = "", - @SerialName("food") val food: String = "", - @SerialName("disableAmount") val disableAmount: Boolean, - @SerialName("quantity") val quantity: Double, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt deleted file mode 100644 index 12b5cc7..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeInstructionResponseV1.kt +++ /dev/null @@ -1,10 +0,0 @@ -package gq.kirmanak.mealient.datasource.v1.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeInstructionResponseV1( - @SerialName("title") val title: String = "", - @SerialName("text") val text: String, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt index bda326f..981c10b 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt @@ -11,3 +11,8 @@ data class GetRecipeResponseV1( @SerialName("recipeIngredient") val recipeIngredients: List, @SerialName("recipeInstructions") val recipeInstructions: List, ) + +@Serializable +data class GetRecipeIngredientResponseV1( + @SerialName("note") val note: String = "", +) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt index 08105d4..86ccf54 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt @@ -6,4 +6,9 @@ import kotlinx.serialization.Serializable @Serializable data class GetRecipesResponseV1( @SerialName("items") val items: List, +) + +@Serializable +data class GetRecipeInstructionResponseV1( + @SerialName("text") val text: String, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt index b603283..11e96f8 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt @@ -6,5 +6,4 @@ import kotlinx.serialization.Serializable @Serializable data class GetTokenResponseV1( @SerialName("access_token") val accessToken: String, - @SerialName("token_type") val tokenType: String, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt index 53e7763..45f5cb5 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt @@ -5,8 +5,5 @@ import kotlinx.serialization.Serializable @Serializable data class VersionResponseV1( - @SerialName("production") val production: Boolean, @SerialName("version") val version: String, - @SerialName("demoStatus") val demoStatus: Boolean, - @SerialName("allowSignup") val allowSignup: Boolean, ) \ No newline at end of file diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt index 799cc1e..e43d745 100644 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt +++ b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt @@ -62,7 +62,7 @@ class MealieDataSourceV0ImplTest { @Test fun `when getVersionInfo and getVersion returns result then result`() = runTest { - val versionResponse = VersionResponseV0(true, "v0.5.6", true) + val versionResponse = VersionResponseV0("v0.5.6") coEvery { service.getVersion(any()) } returns versionResponse assertThat(subject.getVersionInfo(TEST_BASE_URL)).isSameInstanceAs(versionResponse) } From 9d1fdce77a7f9d42fed6a75ca3ed9c7d97600fee Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:21:27 +0100 Subject: [PATCH 32/35] Generate random ids for instructions and ingredients --- .../extensions/RemoteToLocalMappings.kt | 3 +++ .../v0/models/AddRecipeRequestV0.kt | 20 +++++++++---------- .../v1/models/UpdateRecipeRequestV1.kt | 20 ++++++++++--------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt index c457429..705ceb5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt @@ -16,6 +16,7 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.datasource.v1.models.* import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft +import java.util.* fun FullRecipeInfo.toRecipeEntity() = RecipeEntity( remoteId = remoteId, @@ -161,10 +162,12 @@ private fun AddRecipeSettingsInfo.toV1Settings() = AddRecipeSettingsV1( ) private fun AddRecipeIngredientInfo.toV1Ingredient() = AddRecipeIngredientV1( + id = UUID.randomUUID().toString(), note = note, ) private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1( + id = UUID.randomUUID().toString(), text = text, ingredientReferences = emptyList(), ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt index 35f967e..fa6e6c7 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt @@ -5,26 +5,26 @@ import kotlinx.serialization.Serializable @Serializable data class AddRecipeRequestV0( - @SerialName("name") val name: String = "", - @SerialName("description") val description: String = "", - @SerialName("recipeYield") val recipeYield: String = "", - @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), - @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("settings") val settings: AddRecipeSettingsV0 = AddRecipeSettingsV0(), + @SerialName("name") val name: String, + @SerialName("description") val description: String, + @SerialName("recipeYield") val recipeYield: String, + @SerialName("recipeIngredient") val recipeIngredient: List, + @SerialName("recipeInstructions") val recipeInstructions: List, + @SerialName("settings") val settings: AddRecipeSettingsV0, ) @Serializable data class AddRecipeIngredientV0( - @SerialName("note") val note: String = "", + @SerialName("note") val note: String, ) @Serializable data class AddRecipeInstructionV0( - @SerialName("text") val text: String = "", + @SerialName("text") val text: String, ) @Serializable data class AddRecipeSettingsV0( - @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("public") val public: Boolean = true, + @SerialName("disableComments") val disableComments: Boolean, + @SerialName("public") val public: Boolean, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt index 019bd8c..3df266b 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt @@ -5,26 +5,28 @@ import kotlinx.serialization.Serializable @Serializable data class UpdateRecipeRequestV1( - @SerialName("description") val description: String = "", - @SerialName("recipeYield") val recipeYield: String = "", - @SerialName("recipeIngredient") val recipeIngredient: List = emptyList(), - @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("settings") val settings: AddRecipeSettingsV1 = AddRecipeSettingsV1(), + @SerialName("description") val description: String, + @SerialName("recipeYield") val recipeYield: String, + @SerialName("recipeIngredient") val recipeIngredient: List, + @SerialName("recipeInstructions") val recipeInstructions: List, + @SerialName("settings") val settings: AddRecipeSettingsV1, ) @Serializable data class AddRecipeIngredientV1( - @SerialName("note") val note: String = "", + @SerialName("referenceId") val id: String, + @SerialName("note") val note: String, ) @Serializable data class AddRecipeInstructionV1( + @SerialName("id") val id: String, @SerialName("text") val text: String = "", - @SerialName("ingredientReferences") val ingredientReferences: List = emptyList(), + @SerialName("ingredientReferences") val ingredientReferences: List, ) @Serializable data class AddRecipeSettingsV1( - @SerialName("disableComments") val disableComments: Boolean = false, - @SerialName("public") val public: Boolean = true, + @SerialName("disableComments") val disableComments: Boolean, + @SerialName("public") val public: Boolean, ) \ No newline at end of file From 039ba20c83c0c19a4d4275f948e6c9e165c4a957 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:31:12 +0100 Subject: [PATCH 33/35] Remove empty file --- .../java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt | 2 -- .../extensions/{RemoteToLocalMappings.kt => ModelMappings.kt} | 0 2 files changed, 2 deletions(-) delete mode 100644 app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt rename app/src/main/java/gq/kirmanak/mealient/extensions/{RemoteToLocalMappings.kt => ModelMappings.kt} (100%) diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt deleted file mode 100644 index 276348f..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/CoroutineExtensions.kt +++ /dev/null @@ -1,2 +0,0 @@ -package gq.kirmanak.mealient.extensions - diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt similarity index 100% rename from app/src/main/java/gq/kirmanak/mealient/extensions/RemoteToLocalMappings.kt rename to app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt From 94c030c04ddc6a9774f8380801ea28fbe01fcada Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:40:43 +0100 Subject: [PATCH 34/35] Handle server info inside of AuthDataSource --- .../mealient/data/add/AddRecipeInfo.kt | 20 +++++++------- .../mealient/data/auth/AuthDataSource.kt | 9 +------ .../data/auth/impl/AuthDataSourceImpl.kt | 13 +++++---- .../mealient/data/auth/impl/AuthRepoImpl.kt | 10 +++---- .../data/auth/impl/AuthRepoImplTest.kt | 27 +++++-------------- .../mealient/test/RecipeImplTestData.kt | 21 +++++++++++++++ .../mealient/ui/add/AddRecipeViewModelTest.kt | 12 ++++----- 7 files changed, 55 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt index a6283e3..bb51ac6 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/add/AddRecipeInfo.kt @@ -1,23 +1,23 @@ package gq.kirmanak.mealient.data.add data class AddRecipeInfo( - val name: String = "", - val description: String = "", - val recipeYield: String = "", - val recipeIngredient: List = emptyList(), - val recipeInstructions: List = emptyList(), - val settings: AddRecipeSettingsInfo = AddRecipeSettingsInfo(), + val name: String, + val description: String, + val recipeYield: String, + val recipeIngredient: List, + val recipeInstructions: List, + val settings: AddRecipeSettingsInfo, ) data class AddRecipeSettingsInfo( - val disableComments: Boolean = false, - val public: Boolean = true, + val disableComments: Boolean, + val public: Boolean, ) data class AddRecipeIngredientInfo( - val note: String = "", + val note: String, ) data class AddRecipeInstructionInfo( - val text: String = "", + val text: String, ) 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 c6a29f1..576ccab 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,15 +1,8 @@ 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, - serverVersion: ServerVersion, - ): String + suspend fun authenticate(username: String, password: String): 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 2e5f3fc..f42444b 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,6 +1,7 @@ package gq.kirmanak.mealient.data.auth.impl import gq.kirmanak.mealient.data.auth.AuthDataSource +import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.data.baseurl.ServerVersion import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 @@ -9,6 +10,7 @@ import javax.inject.Singleton @Singleton class AuthDataSourceImpl @Inject constructor( + private val serverInfoRepo: ServerInfoRepo, private val v0Source: MealieDataSourceV0, private val v1Source: MealieDataSourceV1, ) : AuthDataSource { @@ -16,10 +18,11 @@ class AuthDataSourceImpl @Inject constructor( 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) + ): String { + val baseUrl = serverInfoRepo.requireUrl() + return when (serverInfoRepo.getVersion()) { + 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 2108a00..e963a62 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,6 @@ 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.ServerInfoRepo import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.flow.Flow @@ -15,7 +14,6 @@ import javax.inject.Singleton class AuthRepoImpl @Inject constructor( private val authStorage: AuthStorage, private val authDataSource: AuthDataSource, - private val serverInfoRepo: ServerInfoRepo, private val logger: Logger, ) : AuthRepo { @@ -24,11 +22,9 @@ class AuthRepoImpl @Inject constructor( override suspend fun authenticate(email: String, password: String) { logger.v { "authenticate() called with: email = $email, password = $password" } - val version = serverInfoRepo.getVersion() - val url = serverInfoRepo.requireUrl() - authDataSource.authenticate(email, password, url, version) - .let { AUTH_HEADER_FORMAT.format(it) } - .let { authStorage.setAuthHeader(it) } + val token = authDataSource.authenticate(email, password) + val header = AUTH_HEADER_FORMAT.format(token) + authStorage.setAuthHeader(header) authStorage.setEmail(email) authStorage.setPassword(password) } 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 8fde756..e3f5b80 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 @@ -42,7 +42,7 @@ class AuthRepoImplTest { @Before fun setUp() { MockKAnnotations.init(this) - subject = AuthRepoImpl(storage, dataSource, serverInfoRepo, logger) + subject = AuthRepoImpl(storage, dataSource, logger) } @Test @@ -54,14 +54,7 @@ class AuthRepoImplTest { @Test fun `when authenticate successfully then saves to storage`() = runTest { coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION - coEvery { - dataSource.authenticate( - eq(TEST_USERNAME), - eq(TEST_PASSWORD), - eq(TEST_BASE_URL), - eq(TEST_SERVER_VERSION), - ) - } returns TEST_TOKEN + coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL subject.authenticate(TEST_USERNAME, TEST_PASSWORD) coVerifyAll { @@ -74,7 +67,7 @@ class AuthRepoImplTest { @Test fun `when authenticate fails then does not change storage`() = runTest { - coEvery { dataSource.authenticate(any(), any(), any(), any()) } throws RuntimeException() + coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException() coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL runCatchingExceptCancel { subject.authenticate("invalid", "") } confirmVerified(storage) @@ -113,17 +106,9 @@ class AuthRepoImplTest { coEvery { storage.getPassword() } returns TEST_PASSWORD coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL - coEvery { - dataSource.authenticate( - eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL), eq(TEST_SERVER_VERSION) - ) - } returns TEST_TOKEN + coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN subject.invalidateAuthHeader() - coVerifyAll { - dataSource.authenticate( - eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL), eq(TEST_SERVER_VERSION) - ) - } + coVerifyAll { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } } @Test @@ -131,7 +116,7 @@ class AuthRepoImplTest { coEvery { storage.getEmail() } returns "invalid" coEvery { storage.getPassword() } returns "" coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL - coEvery { dataSource.authenticate(any(), any(), any(), any()) } throws RuntimeException() + coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException() subject.invalidateAuthHeader() coVerify { storage.setEmail(null) } } diff --git a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt index 049d6ad..e9c5bb3 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/RecipeImplTestData.kt @@ -1,5 +1,9 @@ package gq.kirmanak.mealient.test +import gq.kirmanak.mealient.data.add.AddRecipeInfo +import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo +import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo +import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo @@ -174,4 +178,21 @@ object RecipeImplTestData { PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY, ) ) + + val PORRIDGE_ADD_RECIPE_INFO = AddRecipeInfo( + name = "Porridge", + description = "Tasty breakfast", + recipeYield = "5 servings", + recipeIngredient = listOf( + AddRecipeIngredientInfo("Milk"), + AddRecipeIngredientInfo("Sugar"), + AddRecipeIngredientInfo("Salt"), + AddRecipeIngredientInfo("Porridge"), + ), + recipeInstructions = listOf( + AddRecipeInstructionInfo("Mix"), + AddRecipeInstructionInfo("Cook"), + ), + settings = AddRecipeSettingsInfo(disableComments = false, public = true), + ) } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt index 898b2f1..d3efb46 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/add/AddRecipeViewModelTest.kt @@ -1,9 +1,9 @@ package gq.kirmanak.mealient.ui.add import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.add.AddRecipeInfo import gq.kirmanak.mealient.data.add.AddRecipeRepo import gq.kirmanak.mealient.logging.Logger +import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify @@ -61,21 +61,21 @@ class AddRecipeViewModelTest { @Test fun `when preserve then doesn't update UI`() { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeInfo()) - subject.preserve(AddRecipeInfo()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO) + subject.preserve(PORRIDGE_ADD_RECIPE_INFO) coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow } } @Test fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest { - coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeInfo()) + coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO) val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() } assertThat(actual).isNull() } @Test fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeInfo() + val expected = PORRIDGE_ADD_RECIPE_INFO coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.loadPreservedRequest() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) @@ -83,7 +83,7 @@ class AddRecipeViewModelTest { @Test fun `when clear then updates preservedAddRecipeRequest`() = runTest { - val expected = AddRecipeInfo() + val expected = PORRIDGE_ADD_RECIPE_INFO coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected) subject.clear() assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected) From 765e8877ec610cc3a72f851035b1ef9a53d68d30 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Sun, 30 Oct 2022 13:44:33 +0100 Subject: [PATCH 35/35] Move class to correct file --- .../mealient/datasource/v1/models/GetRecipeResponseV1.kt | 5 +++++ .../mealient/datasource/v1/models/GetRecipesResponseV1.kt | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt index 981c10b..87fdd31 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt @@ -15,4 +15,9 @@ data class GetRecipeResponseV1( @Serializable data class GetRecipeIngredientResponseV1( @SerialName("note") val note: String = "", +) + +@Serializable +data class GetRecipeInstructionResponseV1( + @SerialName("text") val text: String, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt index 86ccf54..c6e33f0 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt @@ -7,8 +7,3 @@ import kotlinx.serialization.Serializable data class GetRecipesResponseV1( @SerialName("items") val items: List, ) - -@Serializable -data class GetRecipeInstructionResponseV1( - @SerialName("text") val text: String, -) \ No newline at end of file