From aefb974cb3462950777483629a4de585262e36e5 Mon Sep 17 00:00:00 2001 From: Kirill Kamakin Date: Mon, 12 Dec 2022 21:26:57 +0100 Subject: [PATCH] Save isFavorite flag to DB for recipes --- .../data/configuration/AppDispatchers.kt | 11 + .../data/configuration/AppDispatchersImpl.kt | 17 ++ .../data/network/MealieDataSourceWrapper.kt | 4 + .../mealient/data/recipes/db/RecipeStorage.kt | 5 +- .../data/recipes/db/RecipeStorageImpl.kt | 10 +- .../data/recipes/impl/RecipeRepoImpl.kt | 2 +- .../recipes/impl/RecipesRemoteMediator.kt | 33 ++- .../data/recipes/network/RecipeDataSource.kt | 2 + .../mealient/di/ArchitectureModule.kt | 6 + .../mealient/extensions/ModelMappings.kt | 3 +- .../8.json | 198 ++++++++++++++++++ .../gq/kirmanak/mealient/database/AppDb.kt | 3 +- .../recipe/entity/RecipeSummaryEntity.kt | 1 + .../datasource/NetworkRequestWrapper.kt | 4 +- .../impl/NetworkRequestWrapperImpl.kt | 32 ++- .../datasource/v0/MealieDataSourceV0.kt | 6 +- .../datasource/v0/MealieDataSourceV0Impl.kt | 9 +- .../mealient/datasource/v0/MealieServiceV0.kt | 3 + .../v0/models/GetUserInfoResponseV0.kt | 9 + .../datasource/v1/MealieDataSourceV1.kt | 3 + .../datasource/v1/MealieDataSourceV1Impl.kt | 9 +- .../mealient/datasource/v1/MealieServiceV1.kt | 3 + .../v1/models/GetUserInfoResponseV1.kt | 9 + 23 files changed, 352 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchers.kt create mode 100644 app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchersImpl.kt create mode 100644 database/schemas/gq.kirmanak.mealient.database.AppDb/8.json create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.kt create mode 100644 datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt diff --git a/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchers.kt b/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchers.kt new file mode 100644 index 0000000..9b3d91a --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchers.kt @@ -0,0 +1,11 @@ +package gq.kirmanak.mealient.data.configuration + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.MainCoroutineDispatcher + +interface AppDispatchers { + val io: CoroutineDispatcher + val main: MainCoroutineDispatcher + val default: CoroutineDispatcher + val unconfined: CoroutineDispatcher +} \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchersImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchersImpl.kt new file mode 100644 index 0000000..e7e249d --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/configuration/AppDispatchersImpl.kt @@ -0,0 +1,17 @@ +package gq.kirmanak.mealient.data.configuration + +import kotlinx.coroutines.Dispatchers +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class AppDispatchersImpl @Inject constructor() : AppDispatchers { + + override val io = Dispatchers.IO + + override val main = Dispatchers.Main + + override val default = Dispatchers.Default + + override val unconfined = Dispatchers.Unconfined +} \ 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 3731ca9..92758cc 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 @@ -64,4 +64,8 @@ class MealieDataSourceWrapper @Inject constructor( ServerVersion.V1 -> v1Source.parseRecipeFromURL(parseRecipeURLInfo.toV1Request()) } + override suspend fun getFavoriteRecipes(): List = when (getVersion()) { + ServerVersion.V0 -> v0Source.requestUserInfo().favoriteRecipes + ServerVersion.V1 -> v1Source.requestUserInfo().favoriteRecipes + } } \ 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 324282d..737c417 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 @@ -2,16 +2,15 @@ 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.FullRecipeEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity interface RecipeStorage { - suspend fun saveRecipes(recipes: List) + suspend fun saveRecipes(recipes: List) fun queryRecipes(query: String?): 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 2b25783..9219e34 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 @@ -3,7 +3,6 @@ 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.FullRecipeEntity @@ -11,7 +10,6 @@ import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.extensions.toRecipeEntity import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity import gq.kirmanak.mealient.extensions.toRecipeInstructionEntity -import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject import javax.inject.Singleton @@ -23,11 +21,9 @@ class RecipeStorageImpl @Inject constructor( ) : RecipeStorage { private val recipeDao: RecipeDao by lazy { db.recipeDao() } - override suspend fun saveRecipes(recipes: List) { + override suspend fun saveRecipes(recipes: List) { logger.v { "saveRecipes() called with $recipes" } - val entities = recipes.map { it.toRecipeSummaryEntity() } - logger.v { "saveRecipes: entities = $entities" } - db.withTransaction { recipeDao.insertRecipes(entities) } + db.withTransaction { recipeDao.insertRecipes(recipes) } } override fun queryRecipes(query: String?): PagingSource { @@ -36,7 +32,7 @@ class RecipeStorageImpl @Inject constructor( else recipeDao.queryRecipesByPages(query) } - 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/impl/RecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoImpl.kt index 542cd18..80840bc 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 @@ -66,7 +66,7 @@ class RecipeRepoImpl @Inject constructor( override suspend fun refreshRecipes() { logger.v { "refreshRecipes() called" } runCatchingExceptCancel { - storage.refreshAll(dataSource.requestRecipes(0, INITIAL_LOAD_PAGE_SIZE)) + mediator.updateRecipes(0, INITIAL_LOAD_PAGE_SIZE) }.onFailure { logger.e(it) { "Can't refresh recipes" } } 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 54915d5..7c8d577 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 @@ -4,11 +4,16 @@ import androidx.annotation.VisibleForTesting import androidx.paging.* import androidx.paging.LoadType.PREPEND import androidx.paging.LoadType.REFRESH +import gq.kirmanak.mealient.data.configuration.AppDispatchers 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.runCatchingExceptCancel +import gq.kirmanak.mealient.extensions.toRecipeSummaryEntity import gq.kirmanak.mealient.logging.Logger +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.withContext import javax.inject.Inject import javax.inject.Singleton @@ -19,14 +24,14 @@ class RecipesRemoteMediator @Inject constructor( private val network: RecipeDataSource, private val pagingSourceFactory: RecipePagingSourceFactory, private val logger: Logger, + private val dispatchers: AppDispatchers, ) : RemoteMediator() { @VisibleForTesting var lastRequestEnd: Int = 0 override suspend fun load( - loadType: LoadType, - state: PagingState + loadType: LoadType, state: PagingState ): MediatorResult { logger.v { "load() called with: lastRequestEnd = $lastRequestEnd, loadType = $loadType, state = $state" } @@ -39,10 +44,7 @@ class RecipesRemoteMediator @Inject constructor( val limit = if (loadType == REFRESH) state.config.initialLoadSize else state.config.pageSize val count: Int = runCatchingExceptCancel { - val recipes = network.requestRecipes(start, limit) - if (loadType == REFRESH) storage.refreshAll(recipes) - else storage.saveRecipes(recipes) - recipes.size + updateRecipes(start, limit, loadType) }.getOrElse { logger.e(it) { "load: can't load recipes" } return MediatorResult.Error(it) @@ -58,4 +60,23 @@ class RecipesRemoteMediator @Inject constructor( lastRequestEnd = start + count return MediatorResult.Success(endOfPaginationReached = count < limit) } + + suspend fun updateRecipes( + start: Int, + limit: Int, + loadType: LoadType = REFRESH, + ): Int = coroutineScope { + val deferredRecipes = async { network.requestRecipes(start, limit) } + val favorites = network.getFavoriteRecipes().toHashSet() + val recipes = deferredRecipes.await() + val entities = withContext(dispatchers.default) { + recipes.map { recipe -> + val isFavorite = favorites.contains(recipe.slug) + recipe.toRecipeSummaryEntity(isFavorite) + } + } + if (loadType == REFRESH) storage.refreshAll(entities) + else storage.saveRecipes(entities) + recipes.size + } } \ No newline at end of file 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 4305eb5..db6f42c 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 @@ -4,4 +4,6 @@ interface RecipeDataSource { suspend fun requestRecipes(start: Int, limit: Int): List suspend fun requestRecipeInfo(slug: String): FullRecipeInfo + + suspend fun getFavoriteRecipes(): List } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/di/ArchitectureModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/ArchitectureModule.kt index c322301..11e2378 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/ArchitectureModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/ArchitectureModule.kt @@ -5,6 +5,8 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import gq.kirmanak.mealient.architecture.configuration.BuildConfiguration +import gq.kirmanak.mealient.data.configuration.AppDispatchers +import gq.kirmanak.mealient.data.configuration.AppDispatchersImpl import gq.kirmanak.mealient.data.configuration.BuildConfigurationImpl import javax.inject.Singleton @@ -15,4 +17,8 @@ interface ArchitectureModule { @Binds @Singleton fun bindBuildConfiguration(buildConfigurationImpl: BuildConfigurationImpl): BuildConfiguration + + @Binds + @Singleton + fun bindAppDispatchers(appDispatchersImpl: AppDispatchersImpl): AppDispatchers } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt b/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt index 2f8263c..13eed03 100644 --- a/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt +++ b/app/src/main/java/gq/kirmanak/mealient/extensions/ModelMappings.kt @@ -80,7 +80,7 @@ fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo( imageId = remoteId, ) -fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity( +fun RecipeSummaryInfo.toRecipeSummaryEntity(isFavorite: Boolean) = RecipeSummaryEntity( remoteId = remoteId, name = name, slug = slug, @@ -88,6 +88,7 @@ fun RecipeSummaryInfo.toRecipeSummaryEntity() = RecipeSummaryEntity( dateAdded = dateAdded, dateUpdated = dateUpdated, imageId = imageId, + isFavorite = isFavorite, ) fun VersionResponseV0.toVersionInfo() = VersionInfo(version) diff --git a/database/schemas/gq.kirmanak.mealient.database.AppDb/8.json b/database/schemas/gq.kirmanak.mealient.database.AppDb/8.json new file mode 100644 index 0000000..735d017 --- /dev/null +++ b/database/schemas/gq.kirmanak.mealient.database.AppDb/8.json @@ -0,0 +1,198 @@ +{ + "formatVersion": 1, + "database": { + "version": 8, + "identityHash": "793673e401425db36544918dae6bf4c1", + "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, `is_favorite` INTEGER NOT NULL DEFAULT false, 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 + }, + { + "fieldPath": "isFavorite", + "columnName": "is_favorite", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "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, `disable_amounts` INTEGER NOT NULL DEFAULT true, PRIMARY KEY(`remote_id`))", + "fields": [ + { + "fieldPath": "remoteId", + "columnName": "remote_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "recipeYield", + "columnName": "recipe_yield", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "disableAmounts", + "columnName": "disable_amounts", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "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, `food` TEXT, `unit` TEXT, `quantity` REAL, `title` TEXT)", + "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 + }, + { + "fieldPath": "food", + "columnName": "food", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "unit", + "columnName": "unit", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "quantity", + "columnName": "quantity", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + } + ], + "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, '793673e401425db36544918dae6bf4c1')" + ] + } +} \ 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 e801675..635e776 100644 --- a/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt +++ b/database/src/main/kotlin/gq/kirmanak/mealient/database/AppDb.kt @@ -6,7 +6,7 @@ import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.entity.* @Database( - version = 7, + version = 8, entities = [ RecipeSummaryEntity::class, RecipeEntity::class, @@ -20,6 +20,7 @@ import gq.kirmanak.mealient.database.recipe.entity.* AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class), AutoMigration(from = 5, to = 6, spec = AppDb.From5To6Migration::class), AutoMigration(from = 6, to = 7), + AutoMigration(from = 7, to = 8), ] ) @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 afbc99d..cf54bfa 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,4 +15,5 @@ data class RecipeSummaryEntity( @ColumnInfo(name = "date_added") val dateAdded: LocalDate, @ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime, @ColumnInfo(name = "image_id") val imageId: String?, + @ColumnInfo(name = "is_favorite", defaultValue = "false") val isFavorite: Boolean, ) \ 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 index 8a8dd01..aaf9323 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/NetworkRequestWrapper.kt @@ -5,13 +5,13 @@ interface NetworkRequestWrapper { suspend fun makeCall( block: suspend () -> T, logMethod: () -> String, - logParameters: () -> String, + logParameters: (() -> String)? = null, ): Result suspend fun makeCallAndHandleUnauthorized( block: suspend () -> T, logMethod: () -> String, - logParameters: () -> String, + logParameters: (() -> String)? = null, ): T } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/NetworkRequestWrapperImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/NetworkRequestWrapperImpl.kt index 6c2b5b2..c19f01b 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/NetworkRequestWrapperImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/NetworkRequestWrapperImpl.kt @@ -16,18 +16,40 @@ class NetworkRequestWrapperImpl @Inject constructor( override suspend fun makeCall( block: suspend () -> T, logMethod: () -> String, - logParameters: () -> String, + logParameters: (() -> String)?, ): Result { - logger.v { "${logMethod()} called with: ${logParameters()}" } + logger.v { + if (logParameters == null) { + "${logMethod()} called" + } else { + "${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" } } + .onFailure { + logger.e(it) { + if (logParameters == null) { + "${logMethod()} request failed" + } else { + "${logMethod()} request failed with: ${logParameters()}" + } + } + } + .onSuccess { + logger.d { + if (logParameters == null) { + "${logMethod()} request succeeded, result = $it" + } else { + "${logMethod()} request succeeded with: ${logParameters()}, result = $it" + } + } + } } override suspend fun makeCallAndHandleUnauthorized( block: suspend () -> T, logMethod: () -> String, - logParameters: () -> String + logParameters: (() -> String)? ): T = makeCall(block, logMethod, logParameters).getOrElse { throw if (it is HttpException && it.code() in listOf(401, 403)) { NetworkError.Unauthorized(it) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt index 01aeac1..a1724d1 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt @@ -4,6 +4,7 @@ import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0 import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetUserInfoResponseV0 import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0 import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 @@ -21,8 +22,7 @@ interface MealieDataSourceV0 { password: String, ): String - suspend fun getVersionInfo( - ): VersionResponseV0 + suspend fun getVersionInfo(): VersionResponseV0 suspend fun requestRecipes( start: Int, @@ -40,4 +40,6 @@ interface MealieDataSourceV0 { suspend fun createApiToken( request: CreateApiTokenRequestV0, ): String + + suspend fun requestUserInfo(): GetUserInfoResponseV0 } \ 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 7d145be..4c0ea0b 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 @@ -8,6 +8,7 @@ import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0 import gq.kirmanak.mealient.datasource.v0.models.ErrorDetailV0 import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0 import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0 +import gq.kirmanak.mealient.datasource.v0.models.GetUserInfoResponseV0 import gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0 import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 import kotlinx.serialization.SerializationException @@ -49,7 +50,6 @@ class MealieDataSourceV0Impl @Inject constructor( override suspend fun getVersionInfo(): VersionResponseV0 = networkRequestWrapper.makeCall( block = { service.getVersion() }, logMethod = { "getVersionInfo" }, - logParameters = { "" }, ).getOrElse { throw when (it) { is HttpException, is SerializationException -> NetworkError.NotMealie(it) @@ -90,4 +90,11 @@ class MealieDataSourceV0Impl @Inject constructor( logMethod = { "createApiToken" }, logParameters = { "request = $request" } ) + + override suspend fun requestUserInfo(): GetUserInfoResponseV0 { + return networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getUserSelfInfo() }, + logMethod = { "requestUserInfo" }, + ) + } } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt index a5626f0..5c139d3 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt @@ -40,4 +40,7 @@ interface MealieServiceV0 { suspend fun createApiToken( @Body request: CreateApiTokenRequestV0, ): String + + @GET("/api/users/self") + suspend fun getUserSelfInfo(): GetUserInfoResponseV0 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.kt new file mode 100644 index 0000000..6110e2e --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.kt @@ -0,0 +1,9 @@ +package gq.kirmanak.mealient.datasource.v0.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetUserInfoResponseV0( + @SerialName("favoriteRecipes") val favoriteRecipes: List = emptyList(), +) 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 332000e..d9b6a57 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 @@ -5,6 +5,7 @@ import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1 import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 +import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1 import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1 import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -47,4 +48,6 @@ interface MealieDataSourceV1 { suspend fun createApiToken( request: CreateApiTokenRequestV1, ): CreateApiTokenResponseV1 + + suspend fun requestUserInfo(): GetUserInfoResponseV1 } \ 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 18105f0..be92d74 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 @@ -9,6 +9,7 @@ import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 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.GetUserInfoResponseV1 import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1 import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1 import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 @@ -60,7 +61,6 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun getVersionInfo(): VersionResponseV1 = networkRequestWrapper.makeCall( block = { service.getVersion() }, logMethod = { "getVersionInfo" }, - logParameters = { "" }, ).getOrElse { throw when (it) { is HttpException, is SerializationException -> NetworkError.NotMealie(it) @@ -101,5 +101,12 @@ class MealieDataSourceV1Impl @Inject constructor( logMethod = { "createApiToken" }, logParameters = { "request = $request" } ) + + override suspend fun requestUserInfo(): GetUserInfoResponseV1 { + return networkRequestWrapper.makeCallAndHandleUnauthorized( + block = { service.getUserSelfInfo() }, + logMethod = { "requestUserInfo" }, + ) + } } 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 a91ef39..5e9e6f5 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 @@ -46,4 +46,7 @@ interface MealieServiceV1 { suspend fun createApiToken( @Body request: CreateApiTokenRequestV1, ): CreateApiTokenResponseV1 + + @GET("/api/users/self") + suspend fun getUserSelfInfo(): GetUserInfoResponseV1 } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt new file mode 100644 index 0000000..4fda4f4 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt @@ -0,0 +1,9 @@ +package gq.kirmanak.mealient.datasource.v1.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GetUserInfoResponseV1( + @SerialName("favoriteRecipes") val favoriteRecipes: List = emptyList(), +)