diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f27c63c..d3b182d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -16,8 +16,8 @@ plugins { android { defaultConfig { applicationId = "gq.kirmanak.mealient" - versionCode = 30 - versionName = "0.4.1" + versionCode = 31 + versionName = "0.4.2" testInstrumentationRunner = "gq.kirmanak.mealient.MealientTestRunner" testInstrumentationRunnerArguments += mapOf("clearPackageData" to "true") resourceConfigurations += listOf("en", "es", "ru", "fr", "nl", "pt", "de") @@ -55,7 +55,7 @@ android { namespace = "gq.kirmanak.mealient" - packagingOptions { + packaging { resources.excludes += "DebugProbesKt.bin" } diff --git a/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt b/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt index 51f8b14..16d6e2e 100644 --- a/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt +++ b/app/src/androidTest/kotlin/gq/kirmanak/mealient/response.VersionResponses.kt @@ -5,11 +5,15 @@ import okhttp3.mockwebserver.MockResponse import okhttp3.mockwebserver.MockWebServer import okhttp3.mockwebserver.RecordedRequest -val versionV1Response = MockResponse().setResponseCode(200).setBody( - """{"production":true,"version":"v1.0.0beta-5","demoStatus":false,"allowSignup":true}""" -) +val versionV1Response = MockResponse() + .setResponseCode(200) + .setHeader("Content-Type", "application/json") + .setBody("""{"production":true,"version":"v1.0.0beta-5","demoStatus":false,"allowSignup":true}""") -val notFoundResponse = MockResponse().setResponseCode(404).setBody("""{"detail":"Not found"}"""") +val notFoundResponse = MockResponse() + .setResponseCode(404) + .setHeader("Content-Type", "application/json") + .setBody("""{"detail":"Not found"}"""") fun MockWebServer.dispatch(block: (String, RecordedRequest) -> MockResponse) { dispatcher = object : Dispatcher() { diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt index d06d75f..55a48dc 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt @@ -9,7 +9,7 @@ interface AuthRepo : ShoppingListsAuthRepo { suspend fun authenticate(email: String, password: String) - suspend fun getAuthHeader(): String? + suspend fun getAuthToken(): String? suspend fun logout() } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt index faddce7..f1ffcbf 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/AuthStorage.kt @@ -4,9 +4,9 @@ import kotlinx.coroutines.flow.Flow interface AuthStorage { - val authHeaderFlow: Flow + val authTokenFlow: Flow - suspend fun setAuthHeader(authHeader: String?) + suspend fun setAuthToken(authToken: String?) - suspend fun getAuthHeader(): String? + suspend fun getAuthToken(): 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 a26ea37..f71c6e6 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,32 +1,19 @@ 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.v0.models.CreateApiTokenRequestV0 -import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1 +import gq.kirmanak.mealient.datasource.MealieDataSource +import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest import javax.inject.Inject class AuthDataSourceImpl @Inject constructor( - private val serverInfoRepo: ServerInfoRepo, - private val v0Source: MealieDataSourceV0, - private val v1Source: MealieDataSourceV1, + private val dataSource: MealieDataSource, ) : AuthDataSource { - private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion() - - override suspend fun authenticate( - username: String, - password: String, - ): String = when (getVersion()) { - ServerVersion.V0 -> v0Source.authenticate(username, password) - ServerVersion.V1 -> v1Source.authenticate(username, password) + override suspend fun authenticate(username: String, password: String): String { + return dataSource.authenticate(username, password) } - override suspend fun createApiToken(name: String): String = when (getVersion()) { - ServerVersion.V0 -> v0Source.createApiToken(CreateApiTokenRequestV0(name)).token - ServerVersion.V1 -> v1Source.createApiToken(CreateApiTokenRequestV1(name)).token + override suspend fun createApiToken(name: String): String { + return dataSource.createApiToken(CreateApiTokenRequest(name)).token } } \ 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 a97ee57..8d2627e 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,6 +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.datasource.AuthenticationProvider +import gq.kirmanak.mealient.datasource.SignOutHandler import gq.kirmanak.mealient.logging.Logger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @@ -13,28 +14,29 @@ class AuthRepoImpl @Inject constructor( private val authStorage: AuthStorage, private val authDataSource: AuthDataSource, private val logger: Logger, + private val signOutHandler: SignOutHandler, ) : AuthRepo, AuthenticationProvider { override val isAuthorizedFlow: Flow - get() = authStorage.authHeaderFlow.map { it != null } + get() = authStorage.authTokenFlow.map { it != null } override suspend fun authenticate(email: String, password: String) { logger.v { "authenticate() called with: email = $email, password = $password" } val token = authDataSource.authenticate(email, password) - authStorage.setAuthHeader(AUTH_HEADER_FORMAT.format(token)) + authStorage.setAuthToken(token) val apiToken = authDataSource.createApiToken(API_TOKEN_NAME) - authStorage.setAuthHeader(AUTH_HEADER_FORMAT.format(apiToken)) + authStorage.setAuthToken(apiToken) } - override suspend fun getAuthHeader(): String? = authStorage.getAuthHeader() + override suspend fun getAuthToken(): String? = authStorage.getAuthToken() override suspend fun logout() { logger.v { "logout() called" } - authStorage.setAuthHeader(null) + authStorage.setAuthToken(null) + signOutHandler.signOut() } companion object { - private const val AUTH_HEADER_FORMAT = "Bearer %s" private const val API_TOKEN_NAME = "Mealient" } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt index 820e25f..93f38c5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImpl.kt @@ -22,15 +22,15 @@ class AuthStorageImpl @Inject constructor( private val logger: Logger, ) : AuthStorage { - override val authHeaderFlow: Flow + override val authTokenFlow: Flow get() = sharedPreferences - .prefsChangeFlow(logger) { getString(AUTH_HEADER_KEY, null) } + .prefsChangeFlow(logger) { getString(AUTH_TOKEN_KEY, null) } .distinctUntilChanged() private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - override suspend fun setAuthHeader(authHeader: String?) = putString(AUTH_HEADER_KEY, authHeader) + override suspend fun setAuthToken(authToken: String?) = putString(AUTH_TOKEN_KEY, authToken) - override suspend fun getAuthHeader(): String? = getString(AUTH_HEADER_KEY) + override suspend fun getAuthToken(): String? = getString(AUTH_TOKEN_KEY) private suspend fun putString( key: String, @@ -48,6 +48,6 @@ class AuthStorageImpl @Inject constructor( companion object { @VisibleForTesting - const val AUTH_HEADER_KEY = "authHeader" + const val AUTH_TOKEN_KEY = "authToken" } } \ No newline at end of file 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 acaacb5..0928db1 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 @@ -1,16 +1,10 @@ package gq.kirmanak.mealient.data.baseurl -import kotlinx.coroutines.flow.Flow - interface ServerInfoRepo { suspend fun getUrl(): String? - suspend fun getVersion(): ServerVersion - suspend fun tryBaseURL(baseURL: String): Result - fun versionUpdates(): Flow - } 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 5e1ea59..70f72bb 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 @@ -1,11 +1,7 @@ package gq.kirmanak.mealient.data.baseurl import gq.kirmanak.mealient.datasource.ServerUrlProvider -import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map import javax.inject.Inject class ServerInfoRepoImpl @Inject constructor( @@ -20,47 +16,18 @@ class ServerInfoRepoImpl @Inject constructor( 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().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 -> { - logger.w { "Unknown server version: $version" } - ServerVersion.V1 - } - } - override suspend fun tryBaseURL(baseURL: String): Result { - val oldVersion = serverInfoStorage.getServerVersion() val oldBaseUrl = serverInfoStorage.getBaseURL() + serverInfoStorage.storeBaseURL(baseURL) - return runCatchingExceptCancel { - serverInfoStorage.storeBaseURL(baseURL) - val version = versionDataSource.getVersionInfo().version - serverInfoStorage.storeServerVersion(version) - }.onFailure { - serverInfoStorage.storeBaseURL(oldBaseUrl, oldVersion) + try { + versionDataSource.requestVersion() + } catch (e: Throwable) { + serverInfoStorage.storeBaseURL(oldBaseUrl) + return Result.failure(e) } + + return Result.success(Unit) } - override fun versionUpdates(): Flow { - return serverInfoStorage - .serverVersionUpdates() - .filterNotNull() - .map { determineServerVersion(it) } - } } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt index f08f165..ba490a5 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorage.kt @@ -1,18 +1,9 @@ package gq.kirmanak.mealient.data.baseurl -import kotlinx.coroutines.flow.Flow - interface ServerInfoStorage { suspend fun getBaseURL(): String? - suspend fun storeBaseURL(baseURL: String) + suspend fun storeBaseURL(baseURL: String?) - suspend fun storeBaseURL(baseURL: String?, version: String?) - - suspend fun storeServerVersion(version: String) - - suspend fun getServerVersion(): String? - - fun serverVersionUpdates(): Flow } \ No newline at end of file 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 deleted file mode 100644 index 0f133fc..0000000 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/ServerVersion.kt +++ /dev/null @@ -1,3 +0,0 @@ -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/VersionDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSource.kt index c5a28a3..5e2d12c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/VersionDataSource.kt @@ -1,8 +1,8 @@ package gq.kirmanak.mealient.data.baseurl -import gq.kirmanak.mealient.datasource.models.VersionInfo +import gq.kirmanak.mealient.datasource.models.VersionResponse interface VersionDataSource { - suspend fun getVersionInfo(): VersionInfo + suspend fun requestVersion(): VersionResponse } \ 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 index dceb915..656f546 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,37 +1,14 @@ package gq.kirmanak.mealient.data.baseurl -import gq.kirmanak.mealient.datasource.models.VersionInfo -import gq.kirmanak.mealient.datasource.runCatchingExceptCancel -import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 -import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.model_mapper.ModelMapper -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.coroutineScope +import gq.kirmanak.mealient.datasource.MealieDataSource +import gq.kirmanak.mealient.datasource.models.VersionResponse import javax.inject.Inject class VersionDataSourceImpl @Inject constructor( - private val v0Source: MealieDataSourceV0, - private val v1Source: MealieDataSourceV1, - private val modelMapper: ModelMapper, + private val dataSource: MealieDataSource, ) : VersionDataSource { - override suspend fun getVersionInfo(): VersionInfo { - val responses = coroutineScope { - val v0Deferred = async { - runCatchingExceptCancel { modelMapper.toVersionInfo(v0Source.getVersionInfo()) } - } - val v1Deferred = async { - runCatchingExceptCancel { modelMapper.toVersionInfo(v1Source.getVersionInfo()) } - } - listOf(v0Deferred, v1Deferred).awaitAll() - } - val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() } - if (firstSuccess == null) { - throw responses.firstNotNullOf { it.exceptionOrNull() } - } else { - return firstSuccess - } + override suspend fun requestVersion(): VersionResponse { + return dataSource.getVersionInfo() } - } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt index 579afc7..617611c 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/baseurl/impl/ServerInfoStorageImpl.kt @@ -3,7 +3,6 @@ package gq.kirmanak.mealient.data.baseurl.impl import androidx.datastore.preferences.core.Preferences import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage import gq.kirmanak.mealient.data.storage.PreferencesStorage -import kotlinx.coroutines.flow.Flow import javax.inject.Inject class ServerInfoStorageImpl @Inject constructor( @@ -13,45 +12,16 @@ class ServerInfoStorageImpl @Inject constructor( private val baseUrlKey: Preferences.Key get() = preferencesStorage.baseUrlKey - private val serverVersionKey: Preferences.Key - get() = preferencesStorage.serverVersionKey - override suspend fun getBaseURL(): String? = getValue(baseUrlKey) - override suspend fun storeBaseURL(baseURL: String) { - preferencesStorage.storeValues(Pair(baseUrlKey, baseURL)) - preferencesStorage.removeValues(serverVersionKey) - } - - override suspend fun storeBaseURL(baseURL: String?, version: String?) { - when { - baseURL == null -> { - preferencesStorage.removeValues(baseUrlKey, serverVersionKey) - } - - version != null -> { - preferencesStorage.storeValues( - Pair(baseUrlKey, baseURL), Pair(serverVersionKey, version) - ) - } - - else -> { - preferencesStorage.removeValues(serverVersionKey) - preferencesStorage.storeValues(Pair(baseUrlKey, baseURL)) - } + override suspend fun storeBaseURL(baseURL: String?) { + if (baseURL == null) { + preferencesStorage.removeValues(baseUrlKey) + } else { + preferencesStorage.storeValues(Pair(baseUrlKey, baseURL)) } } - override suspend fun getServerVersion(): String? = getValue(serverVersionKey) - - override suspend fun storeServerVersion(version: String) { - preferencesStorage.storeValues(Pair(serverVersionKey, version)) - } - - override fun serverVersionUpdates(): Flow { - return preferencesStorage.valueUpdates(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/migration/From30MigrationExecutor.kt b/app/src/main/java/gq/kirmanak/mealient/data/migration/From30MigrationExecutor.kt new file mode 100644 index 0000000..7a30289 --- /dev/null +++ b/app/src/main/java/gq/kirmanak/mealient/data/migration/From30MigrationExecutor.kt @@ -0,0 +1,32 @@ +package gq.kirmanak.mealient.data.migration + +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import gq.kirmanak.mealient.datastore.DataStoreModule +import javax.inject.Inject +import javax.inject.Named + +class From30MigrationExecutor @Inject constructor( + @Named(DataStoreModule.ENCRYPTED) private val sharedPreferences: SharedPreferences, + private val dataStore: DataStore, +) : MigrationExecutor { + + override val migratingFrom: Int = 30 + + override suspend fun executeMigration() { + dataStore.edit { prefs -> + prefs -= stringPreferencesKey("serverVersion") + } + val authHeader = sharedPreferences.getString("authHeader", null) + if (authHeader != null) { + sharedPreferences.edit { + val authToken = authHeader.removePrefix("Bearer ") + putString("authToken", authToken) + } + } + } +} \ 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 d14ac14..99bede7 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,92 +1,58 @@ package gq.kirmanak.mealient.data.network import gq.kirmanak.mealient.data.add.AddRecipeDataSource -import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo -import gq.kirmanak.mealient.data.baseurl.ServerVersion import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource import gq.kirmanak.mealient.data.share.ParseRecipeDataSource +import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource.models.AddRecipeInfo -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo -import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo -import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 -import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest import gq.kirmanak.mealient.model_mapper.ModelMapper import javax.inject.Inject class MealieDataSourceWrapper @Inject constructor( - private val serverInfoRepo: ServerInfoRepo, - private val v0Source: MealieDataSourceV0, - private val v1Source: MealieDataSourceV1, + private val dataSource: MealieDataSource, private val modelMapper: ModelMapper, ) : AddRecipeDataSource, RecipeDataSource, ParseRecipeDataSource { - private suspend fun getVersion(): ServerVersion = serverInfoRepo.getVersion() - - override suspend fun addRecipe(recipe: AddRecipeInfo): String = when (getVersion()) { - ServerVersion.V0 -> v0Source.addRecipe(modelMapper.toV0Request(recipe)) - ServerVersion.V1 -> { - val slug = v1Source.createRecipe(modelMapper.toV1CreateRequest(recipe)) - v1Source.updateRecipe(slug, modelMapper.toV1UpdateRequest(recipe)) - slug - } + override suspend fun addRecipe(recipe: AddRecipeInfo): String { + val slug = dataSource.createRecipe(modelMapper.toCreateRequest(recipe)) + dataSource.updateRecipe(slug, modelMapper.toUpdateRequest(recipe)) + return slug } override suspend fun requestRecipes( start: Int, limit: Int, - ): List = when (getVersion()) { - ServerVersion.V0 -> { - v0Source.requestRecipes(start, limit).map { modelMapper.toRecipeSummaryInfo(it) } - } - 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(page, limit).map { modelMapper.toRecipeSummaryInfo(it) } + ): 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 page = start / limit + 1 + return dataSource.requestRecipes(page, limit) + } + + override suspend fun requestRecipe(slug: String): GetRecipeResponse { + return dataSource.requestRecipeInfo(slug) + } + + override suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLRequest): String { + return dataSource.parseRecipeFromURL(parseRecipeURLInfo) + } + + override suspend fun getFavoriteRecipes(): List { + return dataSource.requestUserInfo().favoriteRecipes + } + + override suspend fun updateIsRecipeFavorite(recipeSlug: String, isFavorite: Boolean) { + val userId = dataSource.requestUserInfo().id + if (isFavorite) { + dataSource.addFavoriteRecipe(userId, recipeSlug) + } else { + dataSource.removeFavoriteRecipe(userId, recipeSlug) } } - override suspend fun requestRecipeInfo(slug: String): FullRecipeInfo = when (getVersion()) { - ServerVersion.V0 -> modelMapper.toFullRecipeInfo(v0Source.requestRecipeInfo(slug)) - ServerVersion.V1 -> modelMapper.toFullRecipeInfo(v1Source.requestRecipeInfo(slug)) - } - - override suspend fun parseRecipeFromURL( - parseRecipeURLInfo: ParseRecipeURLInfo, - ): String = when (getVersion()) { - ServerVersion.V0 -> v0Source.parseRecipeFromURL(modelMapper.toV0Request(parseRecipeURLInfo)) - ServerVersion.V1 -> v1Source.parseRecipeFromURL(modelMapper.toV1Request(parseRecipeURLInfo)) - } - - override suspend fun getFavoriteRecipes(): List = when (getVersion()) { - ServerVersion.V0 -> v0Source.requestUserInfo().favoriteRecipes - ServerVersion.V1 -> v1Source.requestUserInfo().favoriteRecipes - } - - override suspend fun updateIsRecipeFavorite( - recipeSlug: String, - isFavorite: Boolean - ) = when (getVersion()) { - ServerVersion.V0 -> { - val userId = v0Source.requestUserInfo().id - if (isFavorite) { - v0Source.addFavoriteRecipe(userId, recipeSlug) - } else { - v0Source.removeFavoriteRecipe(userId, recipeSlug) - } - } - ServerVersion.V1 -> { - val userId = v1Source.requestUserInfo().id - if (isFavorite) { - v1Source.addFavoriteRecipe(userId, recipeSlug) - } else { - v1Source.removeFavoriteRecipe(userId, recipeSlug) - } - } - } - - override suspend fun deleteRecipe(recipeSlug: String) = when (getVersion()) { - ServerVersion.V0 -> v0Source.deleteRecipe(recipeSlug) - ServerVersion.V1 -> v1Source.deleteRecipe(recipeSlug) + override suspend fun deleteRecipe(recipeSlug: String) { + dataSource.deleteRecipe(recipeSlug) } } \ No newline at end of file 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 d758c53..7b6a2b9 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,8 +1,8 @@ package gq.kirmanak.mealient.data.recipes.impl +import android.net.Uri import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo import gq.kirmanak.mealient.logging.Logger -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import javax.inject.Inject class RecipeImageUrlProviderImpl @Inject constructor( @@ -15,9 +15,11 @@ class RecipeImageUrlProviderImpl @Inject constructor( slug?.takeUnless { it.isBlank() } ?: return null val imagePath = IMAGE_PATH_FORMAT.format(slug) val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() } - val result = baseUrl?.toHttpUrlOrNull() - ?.newBuilder() - ?.addPathSegments(imagePath) + val result = baseUrl + ?.takeUnless { it.isBlank() } + ?.let { Uri.parse(it) } + ?.buildUpon() + ?.path(imagePath) ?.build() ?.toString() logger.v { "getRecipeImageUrl() returned: $result" } 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 e372ef7..78ff918 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 @@ -45,7 +45,7 @@ class RecipeRepoImpl @Inject constructor( override suspend fun refreshRecipeInfo(recipeSlug: String): Result { logger.v { "refreshRecipeInfo() called with: recipeSlug = $recipeSlug" } return runCatchingExceptCancel { - val info = dataSource.requestRecipeInfo(recipeSlug) + val info = dataSource.requestRecipe(recipeSlug) val entity = modelMapper.toRecipeEntity(info) val ingredients = info.recipeIngredients.map { modelMapper.toRecipeIngredientEntity(it, entity.remoteId) 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 4df3074..011219d 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,12 +1,12 @@ package gq.kirmanak.mealient.data.recipes.network -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse interface RecipeDataSource { - suspend fun requestRecipes(start: Int, limit: Int): List + suspend fun requestRecipes(start: Int, limit: Int): List - suspend fun requestRecipeInfo(slug: String): FullRecipeInfo + suspend fun requestRecipe(slug: String): GetRecipeResponse suspend fun getFavoriteRecipes(): List diff --git a/app/src/main/java/gq/kirmanak/mealient/data/share/ParseRecipeDataSource.kt b/app/src/main/java/gq/kirmanak/mealient/data/share/ParseRecipeDataSource.kt index 4369b0b..8b87b69 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/share/ParseRecipeDataSource.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/share/ParseRecipeDataSource.kt @@ -1,8 +1,8 @@ package gq.kirmanak.mealient.data.share -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest interface ParseRecipeDataSource { - suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLInfo): String + suspend fun parseRecipeFromURL(parseRecipeURLInfo: ParseRecipeURLRequest): String } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImpl.kt b/app/src/main/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImpl.kt index 750ac76..9570d1a 100644 --- a/app/src/main/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImpl.kt +++ b/app/src/main/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImpl.kt @@ -1,7 +1,7 @@ package gq.kirmanak.mealient.data.share import androidx.core.util.PatternsCompat -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest import gq.kirmanak.mealient.logging.Logger import javax.inject.Inject @@ -15,7 +15,7 @@ class ShareRecipeRepoImpl @Inject constructor( val matcher = PatternsCompat.WEB_URL.matcher(url) require(matcher.find()) { "Can't find URL in the text" } val urlString = matcher.group() - val request = ParseRecipeURLInfo(url = urlString, includeTags = true) + val request = ParseRecipeURLRequest(url = urlString, includeTags = true) return parseRecipeDataSource.parseRecipeFromURL(request) } } \ No newline at end of file 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 403dfd7..4c83a0e 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,8 +7,6 @@ interface PreferencesStorage { val baseUrlKey: Preferences.Key - val serverVersionKey: Preferences.Key - val isDisclaimerAcceptedKey: Preferences.Key val lastExecutedMigrationVersionKey: Preferences.Key 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 6cd6077..b5a0055 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 @@ -24,8 +24,6 @@ class PreferencesStorageImpl @Inject constructor( override val baseUrlKey = stringPreferencesKey("baseUrl") - override val serverVersionKey = stringPreferencesKey("serverVersion") - override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted") override val lastExecutedMigrationVersionKey: Preferences.Key = diff --git a/app/src/main/java/gq/kirmanak/mealient/di/MigrationModule.kt b/app/src/main/java/gq/kirmanak/mealient/di/MigrationModule.kt index 2c4a6c0..fd5dfb8 100644 --- a/app/src/main/java/gq/kirmanak/mealient/di/MigrationModule.kt +++ b/app/src/main/java/gq/kirmanak/mealient/di/MigrationModule.kt @@ -6,6 +6,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet import gq.kirmanak.mealient.data.migration.From24AuthMigrationExecutor +import gq.kirmanak.mealient.data.migration.From30MigrationExecutor import gq.kirmanak.mealient.data.migration.MigrationDetector import gq.kirmanak.mealient.data.migration.MigrationDetectorImpl import gq.kirmanak.mealient.data.migration.MigrationExecutor @@ -18,6 +19,10 @@ interface MigrationModule { @IntoSet fun bindFrom24AuthMigrationExecutor(from24AuthMigrationExecutor: From24AuthMigrationExecutor): MigrationExecutor + @Binds + @IntoSet + fun bindFrom30MigrationExecutor(impl: From30MigrationExecutor): MigrationExecutor + @Binds fun bindMigrationDetector(migrationDetectorImpl: MigrationDetectorImpl): MigrationDetector } \ No newline at end of file diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt index 95738a8..3ca005d 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivity.kt @@ -109,7 +109,6 @@ class MainActivity : BaseActivity( when (itemId) { R.id.logout -> menuItem.isVisible = uiState.canShowLogout R.id.login -> menuItem.isVisible = uiState.canShowLogin - R.id.shopping_lists -> menuItem.isVisible = uiState.v1MenuItemsVisible } menuItem.isChecked = itemId == checkedMenuItem } diff --git a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt index 9309e9d..6034326 100644 --- a/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt +++ b/app/src/main/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModel.kt @@ -8,7 +8,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.R import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo -import gq.kirmanak.mealient.data.baseurl.ServerVersion import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage import gq.kirmanak.mealient.data.recipes.RecipeRepo import gq.kirmanak.mealient.logging.Logger @@ -47,10 +46,6 @@ class MainActivityViewModel @Inject constructor( .onEach { isAuthorized -> updateUiState { it.copy(isAuthorized = isAuthorized) } } .launchIn(viewModelScope) - serverInfoRepo.versionUpdates() - .onEach { version -> updateUiState { it.copy(v1MenuItemsVisible = version == ServerVersion.V1) } } - .launchIn(viewModelScope) - viewModelScope.launch { _startDestination.value = when { !disclaimerStorage.isDisclaimerAccepted() -> { diff --git a/app/src/main/res/menu/navigation_menu.xml b/app/src/main/res/menu/navigation_menu.xml index aa02f1e..781ac62 100644 --- a/app/src/main/res/menu/navigation_menu.xml +++ b/app/src/main/res/menu/navigation_menu.xml @@ -14,7 +14,6 @@ diff --git a/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt index 5eaaa7d..267cb1c 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/add/impl/AddRecipeRepoTest.kt @@ -9,15 +9,16 @@ import gq.kirmanak.mealient.datastore_test.PORRIDGE_RECIPE_DRAFT import gq.kirmanak.mealient.model_mapper.ModelMapper import gq.kirmanak.mealient.model_mapper.ModelMapperImpl import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.* +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi +import io.mockk.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class AddRecipeRepoTest : BaseUnitTest() { @MockK(relaxUnitFun = true) 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 3bcefb2..1a5c425 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,62 +4,59 @@ 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.ServerInfoRepo +import gq.kirmanak.mealient.datasource.SignOutHandler import gq.kirmanak.mealient.datasource.runCatchingExceptCancel -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_API_TOKEN -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.* +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.confirmVerified +import io.mockk.every import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class AuthRepoImplTest : BaseUnitTest() { @MockK lateinit var dataSource: AuthDataSource - @MockK - lateinit var serverInfoRepo: ServerInfoRepo - @MockK(relaxUnitFun = true) lateinit var storage: AuthStorage + @MockK(relaxUnitFun = true) + lateinit var signOutHandler: SignOutHandler + lateinit var subject: AuthRepo @Before override fun setUp() { super.setUp() - subject = AuthRepoImpl(storage, dataSource, logger) + subject = AuthRepoImpl(storage, dataSource, logger, signOutHandler) } @Test fun `when isAuthorizedFlow then reads from storage`() = runTest { - every { storage.authHeaderFlow } returns flowOf("", null, "header") + every { storage.authTokenFlow } returns flowOf("", null, "header") assertThat(subject.isAuthorizedFlow.toList()).isEqualTo(listOf(true, false, true)) } @Test fun `when authenticate successfully then saves to storage`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { dataSource.authenticate(any(), any()) } returns TEST_TOKEN coEvery { dataSource.createApiToken(any()) } returns TEST_API_TOKEN subject.authenticate(TEST_USERNAME, TEST_PASSWORD) coVerify { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) - storage.setAuthHeader(TEST_AUTH_HEADER) + storage.setAuthToken(TEST_TOKEN) dataSource.createApiToken(eq("Mealient")) - storage.setAuthHeader(TEST_API_AUTH_HEADER) + storage.setAuthToken(TEST_API_TOKEN) } confirmVerified(storage) } @@ -74,7 +71,7 @@ class AuthRepoImplTest : BaseUnitTest() { @Test fun `when logout expect header removal`() = runTest { subject.logout() - coVerify { storage.setAuthHeader(null) } + coVerify { storage.setAuthToken(null) } confirmVerified(storage) } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt index 8076179..1d5b10b 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/auth/impl/AuthStorageImplTest.kt @@ -7,18 +7,16 @@ import com.google.common.truth.Truth.assertThat import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.data.auth.AuthStorage -import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.AUTH_HEADER_KEY -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER +import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl.Companion.AUTH_TOKEN_KEY +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN import gq.kirmanak.mealient.test.HiltRobolectricTest import io.mockk.MockKAnnotations -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import javax.inject.Inject -@OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest class AuthStorageImplTest : HiltRobolectricTest() { @@ -39,12 +37,12 @@ class AuthStorageImplTest : HiltRobolectricTest() { @Test fun `when authHeaderFlow is observed then sends value immediately`() = runTest { - sharedPreferences.edit(commit = true) { putString(AUTH_HEADER_KEY, TEST_AUTH_HEADER) } - assertThat(subject.authHeaderFlow.first()).isEqualTo(TEST_AUTH_HEADER) + sharedPreferences.edit(commit = true) { putString(AUTH_TOKEN_KEY, TEST_TOKEN) } + assertThat(subject.authTokenFlow.first()).isEqualTo(TEST_TOKEN) } @Test fun `when authHeader is observed then sends null if nothing saved`() = runTest { - assertThat(subject.authHeaderFlow.first()).isEqualTo(null) + assertThat(subject.authTokenFlow.first()).isEqualTo(null) } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt index 07cf0e3..a330905 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoRepoTest.kt @@ -1,21 +1,15 @@ package gq.kirmanak.mealient.data.baseurl import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.models.VersionInfo -import gq.kirmanak.mealient.datasource_test.VERSION_INFO_V0 import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION import gq.kirmanak.mealient.test.BaseUnitTest import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -import java.io.IOException -@OptIn(ExperimentalCoroutinesApi::class) class ServerInfoRepoTest : BaseUnitTest() { private lateinit var subject: ServerInfoRepo @@ -54,104 +48,10 @@ class ServerInfoRepoTest : BaseUnitTest() { @Test fun `when tryBaseURL succeeds expect call to storage`() = runTest { - coEvery { storage.getServerVersion() } returns null coEvery { storage.getBaseURL() } returns null - coEvery { dataSource.getVersionInfo() } returns VersionInfo(TEST_VERSION) subject.tryBaseURL(TEST_BASE_URL) coVerify { storage.storeBaseURL(eq(TEST_BASE_URL)) - dataSource.getVersionInfo() - storage.storeServerVersion(TEST_VERSION) } } - - @Test - fun `when tryBaseURL fails expect call to storage`() = runTest { - coEvery { storage.getServerVersion() } returns "serverVersion" - coEvery { storage.getBaseURL() } returns "baseUrl" - coEvery { dataSource.getVersionInfo() } throws IOException() - subject.tryBaseURL(TEST_BASE_URL) - coVerify { - storage.storeBaseURL(eq(TEST_BASE_URL)) - dataSource.getVersionInfo() - storage.storeBaseURL(eq("baseUrl"), eq("serverVersion")) - } - } - - @Test - fun `when storage is empty expect getVersion to call data source`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VERSION_INFO_V0 - subject.getVersion() - coVerify { dataSource.getVersionInfo() } - } - - @Test - fun `when storage is empty and data source has value expect getVersion to save it`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VersionInfo(TEST_VERSION) - subject.getVersion() - coVerify { storage.storeServerVersion(TEST_VERSION) } - } - - @Test - fun `when data source has invalid value expect getVersion to return v1`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VersionInfo("v2.0.0") - assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1) - } - - @Test - fun `when data source has invalid value expect getVersion to save value`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VersionInfo("v2.0.0") - subject.getVersion() - coVerify { storage.storeServerVersion("v2.0.0") } - } - - @Test - fun `when storage has value expect getVersion to not get URL`() = runTest { - coEvery { storage.getServerVersion() } returns TEST_VERSION - subject.getVersion() - coVerify(inverse = true) { storage.getBaseURL() } - } - - @Test - fun `when storage has value expect getVersion to not call data source`() = runTest { - coEvery { storage.getServerVersion() } returns TEST_VERSION - subject.getVersion() - coVerify(inverse = true) { dataSource.getVersionInfo() } - } - - @Test - fun `when storage has v0 value expect getVersion to return parsed`() = runTest { - coEvery { storage.getServerVersion() } returns "v0.5.6" - assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0) - } - - @Test - fun `when storage has v1 value expect getVersion to return parsed`() = runTest { - coEvery { storage.getServerVersion() } returns "v1.0.0-beta05" - assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1) - } - - @Test - fun `when data source has valid v0 value expect getVersion to return it`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VersionInfo("v0.5.6") - assertThat(subject.getVersion()).isEqualTo(ServerVersion.V0) - } - - @Test - fun `when data source has valid v1 value expect getVersion to return it`() = runTest { - coEvery { storage.getServerVersion() } returns null - coEvery { storage.getBaseURL() } returns TEST_BASE_URL - coEvery { dataSource.getVersionInfo() } returns VersionInfo("v1.0.0-beta05") - assertThat(subject.getVersion()).isEqualTo(ServerVersion.V1) - } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt index 5df7a3c..c903a08 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/baseurl/ServerInfoStorageTest.kt @@ -5,18 +5,15 @@ import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl import gq.kirmanak.mealient.data.storage.PreferencesStorage import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION import gq.kirmanak.mealient.test.BaseUnitTest import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class ServerInfoStorageTest : BaseUnitTest() { @MockK(relaxUnitFun = true) @@ -25,14 +22,12 @@ class ServerInfoStorageTest : BaseUnitTest() { lateinit var subject: ServerInfoStorage private val baseUrlKey = stringPreferencesKey("baseUrlKey") - private val serverVersionKey = stringPreferencesKey("serverVersionKey") @Before override fun setUp() { super.setUp() subject = ServerInfoStorageImpl(preferencesStorage) every { preferencesStorage.baseUrlKey } returns baseUrlKey - every { preferencesStorage.serverVersionKey } returns serverVersionKey } @Test @@ -49,30 +44,11 @@ class ServerInfoStorageTest : BaseUnitTest() { @Test fun `when storeBaseURL expect call to preferences storage`() = runTest { - subject.storeBaseURL(TEST_BASE_URL, TEST_VERSION) + subject.storeBaseURL(TEST_BASE_URL) coVerify { preferencesStorage.storeValues( eq(Pair(baseUrlKey, TEST_BASE_URL)), - eq(Pair(serverVersionKey, TEST_VERSION)), ) } } - - @Test - fun `when preference storage is empty expect getServerVersion return null`() = runTest { - coEvery { preferencesStorage.getValue(eq(serverVersionKey)) } returns null - assertThat(subject.getServerVersion()).isNull() - } - - @Test - fun `when preference storage has value expect getServerVersion return value`() = runTest { - coEvery { preferencesStorage.getValue(eq(serverVersionKey)) } returns TEST_VERSION - assertThat(subject.getServerVersion()).isEqualTo(TEST_VERSION) - } - - @Test - fun `when storeServerVersion then calls preferences storage`() = runTest { - subject.storeServerVersion(TEST_VERSION) - coVerify { preferencesStorage.storeValues(eq(Pair(serverVersionKey, TEST_VERSION))) } - } } diff --git a/app/src/test/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImplTest.kt index dbf6dd1..188a58b 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/disclaimer/DisclaimerStorageImplTest.kt @@ -3,12 +3,10 @@ package gq.kirmanak.mealient.data.disclaimer import com.google.common.truth.Truth.assertThat import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.test.HiltRobolectricTest -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import javax.inject.Inject -@OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest class DisclaimerStorageImplTest : HiltRobolectricTest() { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/migration/From24AuthMigrationExecutorTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/migration/From24AuthMigrationExecutorTest.kt index 23098a9..3e045c5 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/migration/From24AuthMigrationExecutorTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/migration/From24AuthMigrationExecutorTest.kt @@ -12,14 +12,12 @@ import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import java.io.IOException import javax.inject.Inject -@OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest class From24AuthMigrationExecutorTest : HiltRobolectricTest() { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/migration/MigrationDetectorImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/migration/MigrationDetectorImplTest.kt index ab74f95..7b8e769 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/migration/MigrationDetectorImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/migration/MigrationDetectorImplTest.kt @@ -9,11 +9,9 @@ import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class MigrationDetectorImplTest : BaseUnitTest() { @MockK(relaxUnitFun = true) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt index a320678..c159bef 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/network/MealieDataSourceWrapperTest.kt @@ -2,50 +2,35 @@ package gq.kirmanak.mealient.data.network import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.data.auth.AuthRepo -import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo -import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0 -import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 +import gq.kirmanak.mealient.datasource.MealieDataSource import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_INFO -import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_REQUEST_V0 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_CREATE_RECIPE_REQUEST_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_FULL_RECIPE_INFO -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_UPDATE_RECIPE_REQUEST_V1 -import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V0 -import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V1 +import gq.kirmanak.mealient.datasource_test.PORRIDGE_CREATE_RECIPE_REQUEST +import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE +import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE +import gq.kirmanak.mealient.datasource_test.PORRIDGE_UPDATE_RECIPE_REQUEST +import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE import gq.kirmanak.mealient.model_mapper.ModelMapper import gq.kirmanak.mealient.model_mapper.ModelMapperImpl import gq.kirmanak.mealient.test.AuthImplTestData.FAVORITE_RECIPES_LIST -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V0 -import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION_V1 -import gq.kirmanak.mealient.test.AuthImplTestData.USER_INFO_V0 -import gq.kirmanak.mealient.test.AuthImplTestData.USER_INFO_V1 +import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN +import gq.kirmanak.mealient.test.AuthImplTestData.USER_INFO import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.* +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.coVerifySequence import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import java.io.IOException -@OptIn(ExperimentalCoroutinesApi::class) class MealieDataSourceWrapperTest : BaseUnitTest() { - @MockK - lateinit var serverInfoRepo: ServerInfoRepo - @MockK(relaxUnitFun = true) lateinit var authRepo: AuthRepo @MockK(relaxUnitFun = true) - lateinit var v0Source: MealieDataSourceV0 - - @MockK(relaxUnitFun = true) - lateinit var v1Source: MealieDataSourceV1 + lateinit var dataSource: MealieDataSource private val modelMapper: ModelMapper = ModelMapperImpl() @@ -54,111 +39,68 @@ class MealieDataSourceWrapperTest : BaseUnitTest() { @Before override fun setUp() { super.setUp() - subject = MealieDataSourceWrapper(serverInfoRepo, v0Source, v1Source, modelMapper) - coEvery { v0Source.requestUserInfo() } returns USER_INFO_V0 - coEvery { v1Source.requestUserInfo() } returns USER_INFO_V1 + subject = MealieDataSourceWrapper(dataSource, modelMapper) + coEvery { dataSource.requestUserInfo() } returns USER_INFO } @Test - fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest { + fun `when requestRecipeInfo expect a valid network call`() = runTest { val slug = "porridge" - coEvery { v1Source.requestRecipeInfo(eq(slug)) } returns PORRIDGE_RECIPE_RESPONSE_V1 - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + coEvery { dataSource.requestRecipeInfo(eq(slug)) } returns PORRIDGE_RECIPE_RESPONSE + coEvery { authRepo.getAuthToken() } returns TEST_TOKEN - val actual = subject.requestRecipeInfo(slug) + val actual = subject.requestRecipe(slug) - coVerify { v1Source.requestRecipeInfo(eq(slug)) } + coVerify { dataSource.requestRecipeInfo(eq(slug)) } - assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) + assertThat(actual).isEqualTo(PORRIDGE_RECIPE_RESPONSE) } @Test - fun `when server version v1 expect requestRecipes to call v1`() = runTest { + fun `when requestRecipes expect valid network request`() = runTest { coEvery { - v1Source.requestRecipes(any(), any()) - } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1) - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + dataSource.requestRecipes(any(), any()) + } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE) + coEvery { authRepo.getAuthToken() } returns TEST_TOKEN val actual = subject.requestRecipes(40, 10) val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5) val perPage = 10 coVerify { - v1Source.requestRecipes(eq(page), eq(perPage)) + dataSource.requestRecipes(eq(page), eq(perPage)) } - assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1)) - } - - @Test - fun `when server version v0 expect requestRecipes to call v0`() = runTest { - coEvery { - v0Source.requestRecipes(any(), any()) - } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0) - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER - - val start = 40 - val limit = 10 - val actual = subject.requestRecipes(start, limit) - - coVerify { - v0Source.requestRecipes(eq(start), eq(limit)) - } - - assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0)) + assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE)) } @Test(expected = IOException::class) - fun `when request fails expect addRecipe to rethrow`() = runTest { - coEvery { v0Source.addRecipe(any()) } throws IOException() - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + fun `when request fails expect createRecipe to rethrow`() = runTest { + coEvery { dataSource.createRecipe(any()) } throws IOException() + coEvery { authRepo.getAuthToken() } returns TEST_TOKEN subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) } @Test - fun `when server version v0 expect addRecipe to call v0`() = runTest { + fun `when create recipe expect createRecipe to call in sequence`() = runTest { val slug = "porridge" - coEvery { v0Source.addRecipe(any()) } returns slug - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER - - val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) - - coVerify { - v0Source.addRecipe( - eq(PORRIDGE_ADD_RECIPE_REQUEST_V0), - ) - } - - assertThat(actual).isEqualTo(slug) - } - - @Test - fun `when server version v1 expect addRecipe to call v1`() = runTest { - val slug = "porridge" - - coEvery { v1Source.createRecipe(any()) } returns slug + coEvery { dataSource.createRecipe(any()) } returns slug coEvery { - v1Source.updateRecipe(any(), any()) - } returns PORRIDGE_RECIPE_RESPONSE_V1 - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER + dataSource.updateRecipe(any(), any()) + } returns PORRIDGE_RECIPE_RESPONSE + coEvery { authRepo.getAuthToken() } returns TEST_TOKEN val actual = subject.addRecipe(PORRIDGE_ADD_RECIPE_INFO) coVerifySequence { - v1Source.createRecipe( - eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1), + dataSource.createRecipe( + eq(PORRIDGE_CREATE_RECIPE_REQUEST), ) - v1Source.updateRecipe( + dataSource.updateRecipe( eq(slug), - eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1), + eq(PORRIDGE_UPDATE_RECIPE_REQUEST), ) } @@ -166,68 +108,31 @@ class MealieDataSourceWrapperTest : BaseUnitTest() { } @Test - fun `when remove favorite recipe info with v0 expect correct sequence`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + fun `when remove favorite recipe info expect correct sequence`() = runTest { subject.updateIsRecipeFavorite(recipeSlug = "cake", isFavorite = false) coVerify { - v0Source.requestUserInfo() - v0Source.removeFavoriteRecipe(eq(3), eq("cake")) + dataSource.requestUserInfo() + dataSource.removeFavoriteRecipe(eq("userId"), eq("cake")) } } @Test - fun `when remove favorite recipe info with v1 expect correct sequence`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - subject.updateIsRecipeFavorite(recipeSlug = "cake", isFavorite = false) - coVerify { - v1Source.requestUserInfo() - v1Source.removeFavoriteRecipe(eq("userId"), eq("cake")) - } - } - - @Test - fun `when add favorite recipe info with v0 expect correct sequence`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + fun `when add favorite recipe info expect correct sequence`() = runTest { subject.updateIsRecipeFavorite(recipeSlug = "cake", isFavorite = true) coVerify { - v0Source.requestUserInfo() - v0Source.addFavoriteRecipe(eq(3), eq("cake")) + dataSource.requestUserInfo() + dataSource.addFavoriteRecipe(eq("userId"), eq("cake")) } } @Test - fun `when add favorite recipe info with v1 expect correct sequence`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - subject.updateIsRecipeFavorite(recipeSlug = "cake", isFavorite = true) - coVerify { - v1Source.requestUserInfo() - v1Source.addFavoriteRecipe(eq("userId"), eq("cake")) - } - } - - @Test - fun `when get favorite recipes with v1 expect correct call`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 + fun `when get favorite recipes expect correct call`() = runTest { subject.getFavoriteRecipes() - coVerify { v1Source.requestUserInfo() } + coVerify { dataSource.requestUserInfo() } } @Test - fun `when get favorite recipes with v0 expect correct call`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 - subject.getFavoriteRecipes() - coVerify { v0Source.requestUserInfo() } - } - - @Test - fun `when get favorite recipes with v1 expect correct result`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 - assertThat(subject.getFavoriteRecipes()).isEqualTo(FAVORITE_RECIPES_LIST) - } - - @Test - fun `when get favorite recipes with v0 expect correct result`() = runTest { - coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 + fun `when get favorite recipes expect correct result`() = runTest { assertThat(subject.getFavoriteRecipes()).isEqualTo(FAVORITE_RECIPES_LIST) } } \ No newline at end of file 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 f10ae92..e460441 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,27 +1,26 @@ package gq.kirmanak.mealient.data.recipes.impl import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo -import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi +import dagger.hilt.android.testing.HiltAndroidTest +import gq.kirmanak.mealient.data.baseurl.ServerInfoStorage +import gq.kirmanak.mealient.test.HiltRobolectricTest +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import javax.inject.Inject -@OptIn(ExperimentalCoroutinesApi::class) -class RecipeImageUrlProviderImplTest : BaseUnitTest() { +@HiltAndroidTest +class RecipeImageUrlProviderImplTest : HiltRobolectricTest() { + @Inject lateinit var subject: RecipeImageUrlProvider - @MockK - lateinit var serverInfoRepo: ServerInfoRepo + @Inject + lateinit var serverInfoStorage: ServerInfoStorage @Before - override fun setUp() { - super.setUp() - subject = RecipeImageUrlProviderImpl(serverInfoRepo, logger) + fun setUp() { prepareBaseURL("https://google.com/") } @@ -76,7 +75,7 @@ class RecipeImageUrlProviderImplTest : BaseUnitTest() { assertThat(actual).isNull() } - private fun prepareBaseURL(baseURL: String?) { - coEvery { serverInfoRepo.getUrl() } returns baseURL + private fun prepareBaseURL(baseURL: String?) = runBlocking { + serverInfoStorage.storeBaseURL(baseURL) } } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipePagingSourceFactoryImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipePagingSourceFactoryImplTest.kt index c5f3b13..7ad5aaf 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipePagingSourceFactoryImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipePagingSourceFactoryImplTest.kt @@ -9,13 +9,11 @@ import gq.kirmanak.mealient.database.TEST_RECIPE_SUMMARY_ENTITIES import gq.kirmanak.mealient.database.recipe.RecipeStorage import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.test.HiltRobolectricTest -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import javax.inject.Inject @HiltAndroidTest -@OptIn(ExperimentalCoroutinesApi::class) class RecipePagingSourceFactoryImplTest : HiltRobolectricTest() { @Inject diff --git a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt index 165b1cc..24c90da 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/recipes/impl/RecipeRepoTest.kt @@ -13,7 +13,7 @@ import gq.kirmanak.mealient.database.FULL_CAKE_INFO_ENTITY import gq.kirmanak.mealient.database.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY import gq.kirmanak.mealient.database.recipe.RecipeStorage import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized -import gq.kirmanak.mealient.datasource_test.CAKE_FULL_RECIPE_INFO +import gq.kirmanak.mealient.datasource_test.CAKE_RECIPE_RESPONSE import gq.kirmanak.mealient.model_mapper.ModelMapper import gq.kirmanak.mealient.model_mapper.ModelMapperImpl import gq.kirmanak.mealient.test.BaseUnitTest @@ -22,13 +22,11 @@ import io.mockk.coVerify import io.mockk.coVerifyOrder import io.mockk.impl.annotations.MockK import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import java.io.IOException -@OptIn(ExperimentalCoroutinesApi::class) class RecipeRepoTest : BaseUnitTest() { @MockK(relaxUnitFun = true) @@ -69,7 +67,7 @@ class RecipeRepoTest : BaseUnitTest() { @Test fun `when refreshRecipeInfo expect call to storage`() = runTest { - coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns CAKE_FULL_RECIPE_INFO + coEvery { dataSource.requestRecipe(eq("cake")) } returns CAKE_RECIPE_RESPONSE subject.refreshRecipeInfo("cake") coVerify { storage.saveRecipeInfo( 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 4961520..3392ead 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 @@ -16,13 +16,11 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import io.mockk.verify -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import java.io.IOException -@ExperimentalCoroutinesApi @OptIn(ExperimentalPagingApi::class) class RecipesRemoteMediatorTest : BaseUnitTest() { diff --git a/app/src/test/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImplTest.kt index 2427f0e..1bd7f02 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/share/ShareRecipeRepoImplTest.kt @@ -1,15 +1,13 @@ package gq.kirmanak.mealient.data.share -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest import gq.kirmanak.mealient.test.BaseUnitTest import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class ShareRecipeRepoImplTest : BaseUnitTest() { @@ -32,7 +30,7 @@ class ShareRecipeRepoImplTest : BaseUnitTest() { @Test fun `when url is correct expect saveRecipeByURL saves it`() = runTest { subject.saveRecipeByURL("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/") - val expected = ParseRecipeURLInfo( + val expected = ParseRecipeURLRequest( url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/", includeTags = true ) @@ -42,7 +40,7 @@ class ShareRecipeRepoImplTest : BaseUnitTest() { @Test fun `when url has prefix expect saveRecipeByURL removes it`() = runTest { subject.saveRecipeByURL("My favorite recipe: https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/") - val expected = ParseRecipeURLInfo( + val expected = ParseRecipeURLRequest( url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/", includeTags = true ) @@ -52,7 +50,7 @@ class ShareRecipeRepoImplTest : BaseUnitTest() { @Test fun `when url has suffix expect saveRecipeByURL removes it`() = runTest { subject.saveRecipeByURL("https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/ is my favorite recipe") - val expected = ParseRecipeURLInfo( + val expected = ParseRecipeURLRequest( url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie", includeTags = true ) @@ -62,7 +60,7 @@ class ShareRecipeRepoImplTest : BaseUnitTest() { @Test fun `when url has prefix and suffix expect saveRecipeByURL removes them`() = runTest { subject.saveRecipeByURL("Actually, https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie/ is my favorite recipe") - val expected = ParseRecipeURLInfo( + val expected = ParseRecipeURLRequest( url = "https://www.allrecipes.com/recipe/215447/dads-leftover-turkey-pot-pie", includeTags = true ) diff --git a/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt b/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt index e9ecca5..efb7f50 100644 --- a/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/data/storage/PreferencesStorageImplTest.kt @@ -3,13 +3,11 @@ package gq.kirmanak.mealient.data.storage import com.google.common.truth.Truth.assertThat import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.test.HiltRobolectricTest -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Test import javax.inject.Inject -@OptIn(ExperimentalCoroutinesApi::class) @HiltAndroidTest class PreferencesStorageImplTest : HiltRobolectricTest() { 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 640a559..ac25d1c 100644 --- a/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt +++ b/app/src/test/java/gq/kirmanak/mealient/test/AuthImplTestData.kt @@ -1,22 +1,14 @@ package gq.kirmanak.mealient.test -import gq.kirmanak.mealient.data.baseurl.ServerVersion -import gq.kirmanak.mealient.datasource.v0.models.GetUserInfoResponseV0 -import gq.kirmanak.mealient.datasource.v1.models.GetUserInfoResponseV1 +import gq.kirmanak.mealient.datasource.models.GetUserInfoResponse 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_API_TOKEN = "TEST_API_TOKEN" - const val TEST_API_AUTH_HEADER = "Bearer TEST_API_TOKEN" - const val TEST_VERSION = "v0.5.6" - val TEST_SERVER_VERSION_V0 = ServerVersion.V0 - val TEST_SERVER_VERSION_V1 = ServerVersion.V1 val FAVORITE_RECIPES_LIST = listOf("cake", "porridge") - val USER_INFO_V1 = GetUserInfoResponseV1("userId", FAVORITE_RECIPES_LIST) - val USER_INFO_V0 = GetUserInfoResponseV0(3, FAVORITE_RECIPES_LIST) + val USER_INFO = GetUserInfoResponse("userId", FAVORITE_RECIPES_LIST) } \ No newline at end of file diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModelTest.kt index a700c7f..9b655c7 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/activity/MainActivityViewModelTest.kt @@ -45,7 +45,6 @@ class MainActivityViewModelTest : BaseUnitTest() { every { activityUiStateController.getUiStateFlow() } returns MutableStateFlow( ActivityUiState() ) - coEvery { serverInfoRepo.versionUpdates() } returns emptyFlow() subject = MainActivityViewModel( authRepo = authRepo, logger = logger, 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 f869174..32072c0 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 @@ -7,7 +7,6 @@ import gq.kirmanak.mealient.test.BaseUnitTest import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flowOf @@ -16,7 +15,6 @@ import kotlinx.coroutines.withTimeoutOrNull import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class AddRecipeViewModelTest : BaseUnitTest() { @MockK(relaxUnitFun = true) diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModelTest.kt index 0914de4..f7bb354 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/recipes/info/RecipeInfoViewModelTest.kt @@ -7,12 +7,10 @@ import gq.kirmanak.mealient.database.FULL_CAKE_INFO_ENTITY import gq.kirmanak.mealient.test.BaseUnitTest import io.mockk.coEvery import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class RecipeInfoViewModelTest : BaseUnitTest() { @MockK diff --git a/app/src/test/java/gq/kirmanak/mealient/ui/share/ShareRecipeViewModelTest.kt b/app/src/test/java/gq/kirmanak/mealient/ui/share/ShareRecipeViewModelTest.kt index 5767628..bbc938b 100644 --- a/app/src/test/java/gq/kirmanak/mealient/ui/share/ShareRecipeViewModelTest.kt +++ b/app/src/test/java/gq/kirmanak/mealient/ui/share/ShareRecipeViewModelTest.kt @@ -9,7 +9,6 @@ import io.mockk.coEvery import io.mockk.coVerify import io.mockk.impl.annotations.MockK import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList @@ -17,7 +16,6 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class ShareRecipeViewModelTest : BaseUnitTest() { @MockK(relaxUnitFun = true) diff --git a/architecture/src/test/kotlin/gq/kirmanak/mealient/architecture/FlowExtensionsKtTest.kt b/architecture/src/test/kotlin/gq/kirmanak/mealient/architecture/FlowExtensionsKtTest.kt index d61815a..60af332 100644 --- a/architecture/src/test/kotlin/gq/kirmanak/mealient/architecture/FlowExtensionsKtTest.kt +++ b/architecture/src/test/kotlin/gq/kirmanak/mealient/architecture/FlowExtensionsKtTest.kt @@ -2,13 +2,11 @@ package gq.kirmanak.mealient.architecture import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.test.BaseUnitTest -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.toList import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class FlowExtensionsKtTest : BaseUnitTest() { @Test diff --git a/database/src/test/kotlin/gq/kirmanak/mealient/database/RecipeStorageImplTest.kt b/database/src/test/kotlin/gq/kirmanak/mealient/database/RecipeStorageImplTest.kt index e60e3af..3a1450f 100644 --- a/database/src/test/kotlin/gq/kirmanak/mealient/database/RecipeStorageImplTest.kt +++ b/database/src/test/kotlin/gq/kirmanak/mealient/database/RecipeStorageImplTest.kt @@ -5,13 +5,11 @@ import dagger.hilt.android.testing.HiltAndroidTest import gq.kirmanak.mealient.database.recipe.RecipeDao import gq.kirmanak.mealient.database.recipe.RecipeStorageImpl import gq.kirmanak.mealient.test.HiltRobolectricTest -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import javax.inject.Inject @HiltAndroidTest -@OptIn(ExperimentalCoroutinesApi::class) internal class RecipeStorageImplTest : HiltRobolectricTest() { @Inject diff --git a/database_test/src/main/kotlin/gq/kirmanak/mealient/database/TestData.kt b/database_test/src/main/kotlin/gq/kirmanak/mealient/database/TestData.kt index fe6e429..0fdf0ba 100644 --- a/database_test/src/main/kotlin/gq/kirmanak/mealient/database/TestData.kt +++ b/database_test/src/main/kotlin/gq/kirmanak/mealient/database/TestData.kt @@ -15,7 +15,7 @@ val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity( description = "A tasty cake", dateAdded = LocalDate.parse("2021-11-13"), dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), - imageId = "cake", + imageId = "1", isFavorite = false, ) @@ -26,7 +26,7 @@ val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity( description = "A tasty porridge", dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), - imageId = "porridge", + imageId = "2", isFavorite = false, ) diff --git a/datasource/build.gradle.kts b/datasource/build.gradle.kts index e882678..b4b2bc4 100644 --- a/datasource/build.gradle.kts +++ b/datasource/build.gradle.kts @@ -25,16 +25,19 @@ dependencies { implementation(libs.jetbrains.kotlinx.serialization) - implementation(libs.squareup.retrofit) - - implementation(libs.jakewharton.retrofitSerialization) + implementation(libs.jetbrains.kotlinx.coroutinesAndroid) + testImplementation(libs.jetbrains.kotlinx.coroutinesTest) implementation(platform(libs.okhttp3.bom)) implementation(libs.okhttp3.okhttp) debugImplementation(libs.okhttp3.loggingInterceptor) - implementation(libs.jetbrains.kotlinx.coroutinesAndroid) - testImplementation(libs.jetbrains.kotlinx.coroutinesTest) + implementation(libs.ktor.core) + implementation(libs.ktor.auth) + implementation(libs.ktor.encoding) + implementation(libs.ktor.negotiation) + implementation(libs.ktor.json) + implementation(libs.ktor.okhttp) testImplementation(libs.androidx.test.junit) diff --git a/datasource/src/debug/AndroidManifest.xml b/datasource/src/debug/AndroidManifest.xml index 1cc35f3..2c639c1 100644 --- a/datasource/src/debug/AndroidManifest.xml +++ b/datasource/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - + + diff --git a/datasource/src/debug/kotlin/gq/kirmanak/mealient/DebugModule.kt b/datasource/src/debug/kotlin/gq/kirmanak/mealient/DebugModule.kt index 8969796..6ca1b86 100644 --- a/datasource/src/debug/kotlin/gq/kirmanak/mealient/DebugModule.kt +++ b/datasource/src/debug/kotlin/gq/kirmanak/mealient/DebugModule.kt @@ -17,7 +17,8 @@ import okhttp3.logging.HttpLoggingInterceptor @Module @InstallIn(SingletonComponent::class) -object DebugModule { +internal object DebugModule { + @Provides @IntoSet fun provideLoggingInterceptor(logger: Logger): Interceptor { diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/AuthenticationProvider.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/AuthenticationProvider.kt index e8d17a8..8216bbe 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/AuthenticationProvider.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/AuthenticationProvider.kt @@ -2,8 +2,7 @@ package gq.kirmanak.mealient.datasource interface AuthenticationProvider { - suspend fun getAuthHeader(): String? + suspend fun getAuthToken(): String? suspend fun logout() - } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/CacheBuilder.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/CacheBuilder.kt deleted file mode 100644 index 91aec03..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/CacheBuilder.kt +++ /dev/null @@ -1,8 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import okhttp3.Cache - -interface CacheBuilder { - - fun buildCache(): Cache -} \ No newline at end of file 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 6852007..fe664e7 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceExtensions.kt @@ -1,10 +1,6 @@ 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 @@ -18,9 +14,6 @@ inline fun runCatchingExceptCancel(block: () -> T): Result = try { Result.failure(e) } -@OptIn(ExperimentalSerializationApi::class) -inline fun ResponseBody.decode(json: Json): R = json.decodeFromStream(byteStream()) - inline fun Throwable.findCauseAsInstanceOf(): T? { var cause: Throwable? = this var previousCause: Throwable? = null 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 656833b..cc564d5 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/DataSourceModule.kt @@ -1,32 +1,17 @@ package gq.kirmanak.mealient.datasource -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import dagger.multibindings.IntoSet -import gq.kirmanak.mealient.datasource.impl.AuthInterceptor -import gq.kirmanak.mealient.datasource.impl.BaseUrlInterceptor -import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl +import gq.kirmanak.mealient.datasource.impl.MealieDataSourceImpl +import gq.kirmanak.mealient.datasource.impl.MealieServiceKtor import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl -import gq.kirmanak.mealient.datasource.impl.RetrofitBuilder import gq.kirmanak.mealient.datasource.impl.TrustedCertificatesStoreImpl -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 -import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient -import retrofit2.Converter -import retrofit2.Retrofit -import retrofit2.create import javax.inject.Singleton @Module @@ -43,59 +28,22 @@ internal interface DataSourceModule { encodeDefaults = true } - @OptIn(ExperimentalSerializationApi::class) @Provides @Singleton - fun provideConverterFactory(json: Json): Converter.Factory = - json.asConverterFactory("application/json".toMediaType()) - - @Provides - @Singleton - fun provideOkHttp(okHttpBuilder: OkHttpBuilder): OkHttpClient = + fun provideOkHttp(okHttpBuilder: OkHttpBuilderImpl): OkHttpClient = okHttpBuilder.buildOkHttp() - @Provides - @Singleton - fun provideRetrofit(retrofitBuilder: RetrofitBuilder): Retrofit { - // Fake base URL which will be replaced later by BaseUrlInterceptor - // Solution was suggested here https://github.com/square/retrofit/issues/2161#issuecomment-274204152 - return retrofitBuilder.buildRetrofit("http://localhost/") - } - - @Provides - @Singleton - fun provideMealieService(retrofit: Retrofit): MealieServiceV0 = - retrofit.create() - - @Provides - @Singleton - fun provideMealieServiceV1(retrofit: Retrofit): MealieServiceV1 = - retrofit.create() } @Binds - fun bindCacheBuilder(cacheBuilderImpl: CacheBuilderImpl): CacheBuilder + fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceImpl): MealieDataSource @Binds - fun bindOkHttpBuilder(okHttpBuilderImpl: OkHttpBuilderImpl): OkHttpBuilder - - @Binds - fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceV0Impl): MealieDataSourceV0 - - @Binds - fun bindMealieDataSourceV1(mealientDataSourceImpl: MealieDataSourceV1Impl): MealieDataSourceV1 + fun bindMealieService(impl: MealieServiceKtor): MealieService @Binds fun bindNetworkRequestWrapper(networkRequestWrapperImpl: NetworkRequestWrapperImpl): NetworkRequestWrapper - @Binds - @IntoSet - fun bindAuthInterceptor(authInterceptor: AuthInterceptor): LocalInterceptor - - @Binds - @IntoSet - fun bindBaseUrlInterceptor(baseUrlInterceptor: BaseUrlInterceptor): LocalInterceptor - @Binds fun bindTrustedCertificatesStore(impl: TrustedCertificatesStoreImpl): TrustedCertificatesStore } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/LocalInterceptor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/LocalInterceptor.kt deleted file mode 100644 index 351a48a..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/LocalInterceptor.kt +++ /dev/null @@ -1,12 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import okhttp3.Interceptor -import okhttp3.OkHttpClient - -/** - * Marker interface which is different from [Interceptor] only in how it is handled. - * [Interceptor]s are added as network interceptors to OkHttpClient whereas [LocalInterceptor]s - * are added via [OkHttpClient.Builder.addInterceptor] function. They will observe the - * full call lifecycle, whereas network interceptors will see only the network part. - */ -interface LocalInterceptor : Interceptor \ 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/MealieDataSource.kt new file mode 100644 index 0000000..a0bc64e --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieDataSource.kt @@ -0,0 +1,78 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest +import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodsResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsResponse +import gq.kirmanak.mealient.datasource.models.GetUnitsResponse +import gq.kirmanak.mealient.datasource.models.GetUserInfoResponse +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest +import gq.kirmanak.mealient.datasource.models.UpdateRecipeRequest +import gq.kirmanak.mealient.datasource.models.VersionResponse + +interface MealieDataSource { + + suspend fun createRecipe( + recipe: CreateRecipeRequest, + ): String + + suspend fun updateRecipe( + slug: String, + recipe: UpdateRecipeRequest, + ): GetRecipeResponse + + /** + * Tries to acquire authentication token using the provided credentials + */ + suspend fun authenticate( + username: String, + password: String, + ): String + + suspend fun getVersionInfo(): VersionResponse + + suspend fun requestRecipes( + page: Int, + perPage: Int, + ): List + + suspend fun requestRecipeInfo( + slug: String, + ): GetRecipeResponse + + suspend fun parseRecipeFromURL( + request: ParseRecipeURLRequest, + ): String + + suspend fun createApiToken( + request: CreateApiTokenRequest, + ): CreateApiTokenResponse + + suspend fun requestUserInfo(): GetUserInfoResponse + + suspend fun removeFavoriteRecipe(userId: String, recipeSlug: String) + + suspend fun addFavoriteRecipe(userId: String, recipeSlug: String) + + suspend fun deleteRecipe(slug: String) + + suspend fun getShoppingLists(page: Int, perPage: Int): GetShoppingListsResponse + + suspend fun getShoppingList(id: String): GetShoppingListResponse + + suspend fun deleteShoppingListItem(id: String) + + suspend fun updateShoppingListItem(item: GetShoppingListItemResponse) + + suspend fun getFoods(): GetFoodsResponse + + suspend fun getUnits(): GetUnitsResponse + + suspend fun addShoppingListItem(request: CreateShoppingListItemRequest) +} \ 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 new file mode 100644 index 0000000..c6cb086 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/MealieService.kt @@ -0,0 +1,64 @@ +package gq.kirmanak.mealient.datasource + +import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest +import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodsResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipesResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsResponse +import gq.kirmanak.mealient.datasource.models.GetTokenResponse +import gq.kirmanak.mealient.datasource.models.GetUnitsResponse +import gq.kirmanak.mealient.datasource.models.GetUserInfoResponse +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest +import gq.kirmanak.mealient.datasource.models.UpdateRecipeRequest +import gq.kirmanak.mealient.datasource.models.VersionResponse +import kotlinx.serialization.json.JsonElement + +internal interface MealieService { + + suspend fun getToken(username: String, password: String): GetTokenResponse + + suspend fun createRecipe(addRecipeRequest: CreateRecipeRequest): String + + suspend fun updateRecipe( + addRecipeRequest: UpdateRecipeRequest, + slug: String, + ): GetRecipeResponse + + suspend fun getVersion(): VersionResponse + + suspend fun getRecipeSummary(page: Int, perPage: Int): GetRecipesResponse + + suspend fun getRecipe(slug: String): GetRecipeResponse + + suspend fun createRecipeFromURL(request: ParseRecipeURLRequest): String + + suspend fun createApiToken(request: CreateApiTokenRequest): CreateApiTokenResponse + + suspend fun getUserSelfInfo(): GetUserInfoResponse + + suspend fun removeFavoriteRecipe(userId: String, recipeSlug: String) + + suspend fun addFavoriteRecipe(userId: String, recipeSlug: String) + + suspend fun deleteRecipe(slug: String) + + suspend fun getShoppingLists(page: Int, perPage: Int): GetShoppingListsResponse + + suspend fun getShoppingList(id: String): GetShoppingListResponse + + suspend fun getShoppingListItem(id: String): JsonElement + + suspend fun updateShoppingListItem(id: String, request: JsonElement) + + suspend fun deleteShoppingListItem(id: String) + + suspend fun getFoods(perPage: Int): GetFoodsResponse + + suspend fun getUnits(perPage: Int): GetUnitsResponse + + suspend fun createShoppingListItem(request: CreateShoppingListItemRequest) +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/OkHttpBuilder.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/OkHttpBuilder.kt deleted file mode 100644 index e31090d..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/OkHttpBuilder.kt +++ /dev/null @@ -1,8 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import okhttp3.OkHttpClient - -interface OkHttpBuilder { - - fun buildOkHttp(): OkHttpClient -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/SignOutHandler.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/SignOutHandler.kt new file mode 100644 index 0000000..d0346c3 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/SignOutHandler.kt @@ -0,0 +1,6 @@ +package gq.kirmanak.mealient.datasource + +interface SignOutHandler { + + fun signOut() +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptor.kt deleted file mode 100644 index 1d98d8f..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptor.kt +++ /dev/null @@ -1,42 +0,0 @@ -package gq.kirmanak.mealient.datasource.impl - -import androidx.annotation.VisibleForTesting -import gq.kirmanak.mealient.datasource.AuthenticationProvider -import gq.kirmanak.mealient.datasource.LocalInterceptor -import gq.kirmanak.mealient.logging.Logger -import kotlinx.coroutines.runBlocking -import okhttp3.Interceptor -import okhttp3.Response -import javax.inject.Inject -import javax.inject.Provider - -internal class AuthInterceptor @Inject constructor( - private val logger: Logger, - private val authenticationProviderProvider: Provider, -) : LocalInterceptor { - - private val authenticationProvider: AuthenticationProvider - get() = authenticationProviderProvider.get() - - override fun intercept(chain: Interceptor.Chain): Response { - logger.v { "intercept() was called with: request = ${chain.request()}" } - val header = getAuthHeader() - val request = chain.request().let { - if (header == null) it else it.newBuilder().header(HEADER_NAME, header).build() - } - logger.d { "Sending header $HEADER_NAME=${request.header(HEADER_NAME)}" } - return chain.proceed(request).also { - logger.v { "Response code is ${it.code}" } - if (it.code == 401 && header != null) logout() - } - } - - private fun getAuthHeader() = runBlocking { authenticationProvider.getAuthHeader() } - - private fun logout() = runBlocking { authenticationProvider.logout() } - - companion object { - @VisibleForTesting - const val HEADER_NAME = "Authorization" - } -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt deleted file mode 100644 index c5bb54a..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptor.kt +++ /dev/null @@ -1,46 +0,0 @@ -package gq.kirmanak.mealient.datasource.impl - -import gq.kirmanak.mealient.datasource.LocalInterceptor -import gq.kirmanak.mealient.datasource.ServerUrlProvider -import gq.kirmanak.mealient.logging.Logger -import kotlinx.coroutines.runBlocking -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Interceptor -import okhttp3.Response -import java.io.IOException -import javax.inject.Inject -import javax.inject.Provider - -internal class BaseUrlInterceptor @Inject constructor( - private val logger: Logger, - private val serverUrlProviderProvider: Provider, -) : LocalInterceptor { - - private val serverUrlProvider: ServerUrlProvider - get() = serverUrlProviderProvider.get() - - override fun intercept(chain: Interceptor.Chain): Response { - logger.v { "intercept() was called with: request = ${chain.request()}" } - val oldRequest = chain.request() - val baseUrl = getBaseUrl() - val correctUrl = oldRequest.url - .newBuilder() - .host(baseUrl.host) - .scheme(baseUrl.scheme) - .port(baseUrl.port) - .build() - val newRequest = oldRequest.newBuilder().url(correctUrl).build() - logger.d { "Replaced ${oldRequest.url} with ${newRequest.url}" } - return chain.proceed(newRequest) - } - - private fun getBaseUrl() = runBlocking { - val url = serverUrlProvider.getUrl() ?: throw IOException("Base URL is unknown") - url.runCatching { - toHttpUrl() - }.fold( - onSuccess = { it }, - onFailure = { throw IOException(it.message, it) }, - ) - } -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/CacheBuilderImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/CacheBuilderImpl.kt index 2e5f011..fbf79be 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/CacheBuilderImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/CacheBuilderImpl.kt @@ -3,7 +3,6 @@ package gq.kirmanak.mealient.datasource.impl import android.content.Context import android.os.StatFs import dagger.hilt.android.qualifiers.ApplicationContext -import gq.kirmanak.mealient.datasource.CacheBuilder import gq.kirmanak.mealient.logging.Logger import okhttp3.Cache import java.io.File @@ -12,9 +11,9 @@ import javax.inject.Inject internal class CacheBuilderImpl @Inject constructor( @ApplicationContext private val context: Context, private val logger: Logger, -) : CacheBuilder { +) { - override fun buildCache(): Cache { + fun buildCache(): Cache { val dir = findCacheDir() return Cache(dir, calculateDiskCacheSize(dir)) } diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieDataSourceImpl.kt similarity index 67% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieDataSourceImpl.kt index 6443c10..0c3c80b 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1Impl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieDataSourceImpl.kt @@ -1,53 +1,53 @@ -package gq.kirmanak.mealient.datasource.v1 +package gq.kirmanak.mealient.datasource.impl +import gq.kirmanak.mealient.datasource.MealieDataSource +import gq.kirmanak.mealient.datasource.MealieService import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper -import gq.kirmanak.mealient.datasource.decode -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.ErrorDetailV1 -import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1 -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 -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json +import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest +import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.ErrorDetail +import gq.kirmanak.mealient.datasource.models.GetFoodsResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsResponse +import gq.kirmanak.mealient.datasource.models.GetUnitsResponse +import gq.kirmanak.mealient.datasource.models.GetUserInfoResponse +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest +import gq.kirmanak.mealient.datasource.models.UpdateRecipeRequest +import gq.kirmanak.mealient.datasource.models.VersionResponse +import io.ktor.client.call.NoTransformationFoundException +import io.ktor.client.call.body +import io.ktor.client.plugins.ResponseException import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.jsonObject -import retrofit2.HttpException -import java.net.ConnectException +import java.net.SocketException import java.net.SocketTimeoutException import javax.inject.Inject -class MealieDataSourceV1Impl @Inject constructor( +internal class MealieDataSourceImpl @Inject constructor( private val networkRequestWrapper: NetworkRequestWrapper, - private val service: MealieServiceV1, - private val json: Json, -) : MealieDataSourceV1 { + private val service: MealieService, +) : MealieDataSource { override suspend fun createRecipe( - recipe: CreateRecipeRequestV1 + recipe: CreateRecipeRequest ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.createRecipe(recipe) }, logMethod = { "createRecipe" }, logParameters = { "recipe = $recipe" } - ) + ).trim('"') override suspend fun updateRecipe( slug: String, - recipe: UpdateRecipeRequestV1 - ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + recipe: UpdateRecipeRequest + ): GetRecipeResponse = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.updateRecipe(recipe, slug) }, logMethod = { "updateRecipe" }, logParameters = { "slug = $slug, recipe = $recipe" } @@ -61,18 +61,17 @@ class MealieDataSourceV1Impl @Inject constructor( logMethod = { "authenticate" }, logParameters = { "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 + val errorDetail = (it as? ResponseException)?.response?.body() ?: throw it + throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it } - override suspend fun getVersionInfo(): VersionResponseV1 = networkRequestWrapper.makeCall( + override suspend fun getVersionInfo(): VersionResponse = networkRequestWrapper.makeCall( block = { service.getVersion() }, logMethod = { "getVersionInfo" }, ).getOrElse { throw when (it) { - is HttpException, is SerializationException -> NetworkError.NotMealie(it) - is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it) + is ResponseException, is NoTransformationFoundException -> NetworkError.NotMealie(it) + is SocketTimeoutException, is SocketException -> NetworkError.NoServerConnection(it) else -> NetworkError.MalformedUrl(it) } } @@ -80,7 +79,7 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun requestRecipes( page: Int, perPage: Int - ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( + ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getRecipeSummary(page, perPage) }, logMethod = { "requestRecipes" }, logParameters = { "page = $page, perPage = $perPage" } @@ -88,14 +87,14 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun requestRecipeInfo( slug: String - ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + ): GetRecipeResponse = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getRecipe(slug) }, logMethod = { "requestRecipeInfo" }, logParameters = { "slug = $slug" } ) override suspend fun parseRecipeFromURL( - request: ParseRecipeURLRequestV1 + request: ParseRecipeURLRequest ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.createRecipeFromURL(request) }, logMethod = { "parseRecipeFromURL" }, @@ -103,14 +102,14 @@ class MealieDataSourceV1Impl @Inject constructor( ) override suspend fun createApiToken( - request: CreateApiTokenRequestV1 - ): CreateApiTokenResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + request: CreateApiTokenRequest + ): CreateApiTokenResponse = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.createApiToken(request) }, logMethod = { "createApiToken" }, logParameters = { "request = $request" } ) - override suspend fun requestUserInfo(): GetUserInfoResponseV1 { + override suspend fun requestUserInfo(): GetUserInfoResponse { return networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getUserSelfInfo() }, logMethod = { "requestUserInfo" }, @@ -146,7 +145,7 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun getShoppingLists( page: Int, perPage: Int, - ): GetShoppingListsResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + ): GetShoppingListsResponse = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getShoppingLists(page, perPage) }, logMethod = { "getShoppingLists" }, logParameters = { "page = $page, perPage = $perPage" } @@ -154,7 +153,7 @@ class MealieDataSourceV1Impl @Inject constructor( override suspend fun getShoppingList( id: String - ): GetShoppingListResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( + ): GetShoppingListResponse = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getShoppingList(id) }, logMethod = { "getShoppingList" }, logParameters = { "id = $id" } @@ -186,7 +185,7 @@ class MealieDataSourceV1Impl @Inject constructor( ) override suspend fun updateShoppingListItem( - item: ShoppingListItemInfo + item: GetShoppingListItemResponse ) { // Has to be done in two steps because we can't specify only the changed fields val remoteItem = getShoppingListItem(item.id) @@ -203,14 +202,14 @@ class MealieDataSourceV1Impl @Inject constructor( updateShoppingListItem(item.id, JsonObject(updatedItem)) } - override suspend fun getFoods(): GetFoodsResponseV1 { + override suspend fun getFoods(): GetFoodsResponse { return networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getFoods(perPage = -1) }, logMethod = { "getFoods" }, ) } - override suspend fun getUnits(): GetUnitsResponseV1 { + override suspend fun getUnits(): GetUnitsResponse { return networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.getUnits(perPage = -1) }, logMethod = { "getUnits" }, @@ -218,7 +217,7 @@ class MealieDataSourceV1Impl @Inject constructor( } override suspend fun addShoppingListItem( - request: CreateShoppingListItemRequestV1 + request: CreateShoppingListItemRequest ) = networkRequestWrapper.makeCallAndHandleUnauthorized( block = { service.createShoppingListItem(request) }, logMethod = { "addShoppingListItem" }, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieServiceKtor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieServiceKtor.kt new file mode 100644 index 0000000..d7afd2c --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/MealieServiceKtor.kt @@ -0,0 +1,210 @@ +package gq.kirmanak.mealient.datasource.impl + +import gq.kirmanak.mealient.datasource.MealieService +import gq.kirmanak.mealient.datasource.ServerUrlProvider +import gq.kirmanak.mealient.datasource.models.CreateApiTokenRequest +import gq.kirmanak.mealient.datasource.models.CreateApiTokenResponse +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodsResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeResponse +import gq.kirmanak.mealient.datasource.models.GetRecipesResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsResponse +import gq.kirmanak.mealient.datasource.models.GetTokenResponse +import gq.kirmanak.mealient.datasource.models.GetUnitsResponse +import gq.kirmanak.mealient.datasource.models.GetUserInfoResponse +import gq.kirmanak.mealient.datasource.models.ParseRecipeURLRequest +import gq.kirmanak.mealient.datasource.models.UpdateRecipeRequest +import gq.kirmanak.mealient.datasource.models.VersionResponse +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.delete +import io.ktor.client.request.forms.FormDataContent +import io.ktor.client.request.get +import io.ktor.client.request.patch +import io.ktor.client.request.post +import io.ktor.client.request.put +import io.ktor.client.request.setBody +import io.ktor.http.ContentType +import io.ktor.http.URLBuilder +import io.ktor.http.contentType +import io.ktor.http.parameters +import io.ktor.http.path +import io.ktor.http.takeFrom +import kotlinx.serialization.json.JsonElement +import javax.inject.Inject +import javax.inject.Provider + +internal class MealieServiceKtor @Inject constructor( + private val httpClient: HttpClient, + private val serverUrlProviderProvider: Provider, +) : MealieService { + + private val serverUrlProvider: ServerUrlProvider + get() = serverUrlProviderProvider.get() + + override suspend fun getToken(username: String, password: String): GetTokenResponse { + val formParameters = parameters { + append("username", username) + append("password", password) + } + + return httpClient.post { + endpoint("/api/auth/token") + setBody(FormDataContent(formParameters)) + }.body() + } + + override suspend fun createRecipe(addRecipeRequest: CreateRecipeRequest): String { + return httpClient.post { + endpoint("/api/recipes") + contentType(ContentType.Application.Json) + setBody(addRecipeRequest) + }.body() + } + + override suspend fun updateRecipe( + addRecipeRequest: UpdateRecipeRequest, + slug: String, + ): GetRecipeResponse { + return httpClient.patch { + endpoint("/api/recipes/$slug") + contentType(ContentType.Application.Json) + setBody(addRecipeRequest) + }.body() + } + + override suspend fun getVersion(): VersionResponse { + return httpClient.get { + endpoint("/api/app/about") + }.body() + } + + override suspend fun getRecipeSummary(page: Int, perPage: Int): GetRecipesResponse { + return httpClient.get { + endpoint("/api/recipes") { + parameters.append("page", page.toString()) + parameters.append("perPage", perPage.toString()) + } + }.body() + } + + override suspend fun getRecipe(slug: String): GetRecipeResponse { + return httpClient.get { + endpoint("/api/recipes/$slug") + }.body() + } + + override suspend fun createRecipeFromURL(request: ParseRecipeURLRequest): String { + return httpClient.post { + endpoint("/api/recipes/create-url") + contentType(ContentType.Application.Json) + setBody(request) + }.body() + } + + override suspend fun createApiToken(request: CreateApiTokenRequest): CreateApiTokenResponse { + return httpClient.post { + endpoint("/api/users/api-tokens") + contentType(ContentType.Application.Json) + setBody(request) + }.body() + } + + override suspend fun getUserSelfInfo(): GetUserInfoResponse { + return httpClient.get { + endpoint("/api/users/self") + }.body() + } + + override suspend fun removeFavoriteRecipe(userId: String, recipeSlug: String) { + httpClient.delete { + endpoint("/api/users/$userId/favorites/$recipeSlug") + } + } + + override suspend fun addFavoriteRecipe(userId: String, recipeSlug: String) { + httpClient.post { + endpoint("/api/users/$userId/favorites/$recipeSlug") + } + } + + override suspend fun deleteRecipe(slug: String) { + httpClient.delete { + endpoint("/api/recipes/$slug") + } + } + + override suspend fun getShoppingLists(page: Int, perPage: Int): GetShoppingListsResponse { + return httpClient.get { + endpoint("/api/groups/shopping/lists") { + parameters.append("page", page.toString()) + parameters.append("perPage", perPage.toString()) + } + }.body() + } + + override suspend fun getShoppingList(id: String): GetShoppingListResponse { + return httpClient.get { + endpoint("/api/groups/shopping/lists/$id") + }.body() + } + + override suspend fun getShoppingListItem(id: String): JsonElement { + return httpClient.get { + endpoint("/api/groups/shopping/items/$id") + }.body() + } + + override suspend fun updateShoppingListItem(id: String, request: JsonElement) { + httpClient.put { + endpoint("/api/groups/shopping/items/$id") + contentType(ContentType.Application.Json) + setBody(request) + } + } + + override suspend fun deleteShoppingListItem(id: String) { + httpClient.delete { + endpoint("/api/groups/shopping/items/$id") + } + } + + override suspend fun getFoods(perPage: Int): GetFoodsResponse { + return httpClient.get { + endpoint("/api/foods") { + parameters.append("perPage", perPage.toString()) + } + }.body() + } + + override suspend fun getUnits(perPage: Int): GetUnitsResponse { + return httpClient.get { + endpoint("/api/units") { + parameters.append("perPage", perPage.toString()) + } + }.body() + } + + override suspend fun createShoppingListItem(request: CreateShoppingListItemRequest) { + httpClient.post { + endpoint("/api/groups/shopping/items") + contentType(ContentType.Application.Json) + setBody(request) + } + } + + private suspend fun HttpRequestBuilder.endpoint( + path: String, + block: URLBuilder.() -> Unit = {} + ) { + val baseUrl = checkNotNull(serverUrlProvider.getUrl()) { "Server URL is not set" } + url { + takeFrom(baseUrl) + path(path) + block() + } + } +} \ 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 7005fd6..77d9c4e 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 @@ -4,7 +4,7 @@ import gq.kirmanak.mealient.datasource.NetworkError import gq.kirmanak.mealient.datasource.NetworkRequestWrapper import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger -import retrofit2.HttpException +import io.ktor.client.plugins.ResponseException import javax.inject.Inject internal class NetworkRequestWrapperImpl @Inject constructor( @@ -49,7 +49,8 @@ internal class NetworkRequestWrapperImpl @Inject constructor( logMethod: () -> String, logParameters: (() -> String)? ): T = makeCall(block, logMethod, logParameters).getOrElse { - throw if (it is HttpException && it.code() in listOf(401, 403)) { + val code = (it as? ResponseException)?.response?.status?.value + throw if (code in listOf(401, 403)) { NetworkError.Unauthorized(it) } else { it diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/OkHttpBuilderImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/OkHttpBuilderImpl.kt index 739813f..3cb0047 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/OkHttpBuilderImpl.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/OkHttpBuilderImpl.kt @@ -1,55 +1,28 @@ package gq.kirmanak.mealient.datasource.impl -import gq.kirmanak.mealient.datasource.CacheBuilder -import gq.kirmanak.mealient.datasource.LocalInterceptor -import gq.kirmanak.mealient.datasource.OkHttpBuilder import gq.kirmanak.mealient.logging.Logger import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.TlsVersion import javax.inject.Inject -import javax.net.ssl.SSLContext -import javax.net.ssl.TrustManager internal class OkHttpBuilderImpl @Inject constructor( - private val cacheBuilder: CacheBuilder, + private val cacheBuilder: CacheBuilderImpl, // Use @JvmSuppressWildcards because otherwise dagger can't inject it (https://stackoverflow.com/a/43149382) private val interceptors: Set<@JvmSuppressWildcards Interceptor>, - private val localInterceptors: Set<@JvmSuppressWildcards LocalInterceptor>, private val advancedX509TrustManager: AdvancedX509TrustManager, + private val sslSocketFactoryFactory: SslSocketFactoryFactory, private val logger: Logger, -) : OkHttpBuilder { +) { - override fun buildOkHttp(): OkHttpClient { - logger.v { "buildOkHttp() was called with cacheBuilder = $cacheBuilder, interceptors = $interceptors, localInterceptors = $localInterceptors" } + fun buildOkHttp(): OkHttpClient { + logger.v { "buildOkHttp() was called with cacheBuilder = $cacheBuilder, interceptors = $interceptors" } - val sslContext = buildSSLContext() - sslContext.init(null, arrayOf(advancedX509TrustManager), null) - val sslSocketFactory = sslContext.socketFactory + val sslSocketFactory = sslSocketFactoryFactory.create() return OkHttpClient.Builder().apply { - localInterceptors.forEach(::addInterceptor) interceptors.forEach(::addNetworkInterceptor) sslSocketFactory(sslSocketFactory, advancedX509TrustManager) cache(cacheBuilder.buildCache()) }.build() } - - private fun buildSSLContext(): SSLContext { - return runCatching { - SSLContext.getInstance(TlsVersion.TLS_1_3.javaName) - }.recoverCatching { - logger.w { "TLSv1.3 is not supported in this device; falling through TLSv1.2" } - SSLContext.getInstance(TlsVersion.TLS_1_2.javaName) - }.recoverCatching { - logger.w { "TLSv1.2 is not supported in this device; falling through TLSv1.1" } - SSLContext.getInstance(TlsVersion.TLS_1_1.javaName) - }.recoverCatching { - logger.w { "TLSv1.1 is not supported in this device; falling through TLSv1.0" } - // should be available in any device; see reference of supported protocols in - // http://developer.android.com/reference/javax/net/ssl/SSLSocket.html - SSLContext.getInstance(TlsVersion.TLS_1_0.javaName) - }.getOrThrow() - } - } \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/RetrofitBuilder.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/RetrofitBuilder.kt deleted file mode 100644 index f2830f4..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/RetrofitBuilder.kt +++ /dev/null @@ -1,23 +0,0 @@ -package gq.kirmanak.mealient.datasource.impl - -import gq.kirmanak.mealient.logging.Logger -import okhttp3.OkHttpClient -import retrofit2.Converter.Factory -import retrofit2.Retrofit -import javax.inject.Inject - -internal class RetrofitBuilder @Inject constructor( - private val okHttpClient: OkHttpClient, - private val converterFactory: Factory, - private val logger: Logger, -) { - - fun buildRetrofit(baseUrl: String): Retrofit { - logger.v { "buildRetrofit() called with: baseUrl = $baseUrl" } - return Retrofit.Builder() - .baseUrl(baseUrl) - .client(okHttpClient) - .addConverterFactory(converterFactory) - .build() - } -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/SslSocketFactoryFactory.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/SslSocketFactoryFactory.kt new file mode 100644 index 0000000..65e856d --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/impl/SslSocketFactoryFactory.kt @@ -0,0 +1,36 @@ +package gq.kirmanak.mealient.datasource.impl + +import gq.kirmanak.mealient.logging.Logger +import javax.inject.Inject +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSocketFactory +import javax.net.ssl.TrustManager + +internal class SslSocketFactoryFactory @Inject constructor( + private val advancedX509TrustManager: AdvancedX509TrustManager, + private val logger: Logger, +) { + + fun create(): SSLSocketFactory { + val sslContext = buildSSLContext() + sslContext.init(null, arrayOf(advancedX509TrustManager), null) + return sslContext.socketFactory + } + + private fun buildSSLContext(): SSLContext { + return runCatching { + SSLContext.getInstance("TLSv1.3") + }.recoverCatching { + logger.w { "TLSv1.3 is not supported in this device; falling through TLSv1.2" } + SSLContext.getInstance("TLSv1.2") + }.recoverCatching { + logger.w { "TLSv1.2 is not supported in this device; falling through TLSv1.1" } + SSLContext.getInstance("TLSv1.1") + }.recoverCatching { + logger.w { "TLSv1.1 is not supported in this device; falling through TLSv1.0" } + // should be available in any device; see reference of supported protocols in + // http://developer.android.com/reference/javax/net/ssl/SSLSocket.html + SSLContext.getInstance("TLSv1") + }.getOrThrow() + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/AuthKtorConfiguration.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/AuthKtorConfiguration.kt new file mode 100644 index 0000000..176a6df --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/AuthKtorConfiguration.kt @@ -0,0 +1,50 @@ +package gq.kirmanak.mealient.datasource.ktor + +import gq.kirmanak.mealient.datasource.AuthenticationProvider +import gq.kirmanak.mealient.logging.Logger +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.auth.providers.BearerTokens +import io.ktor.client.plugins.auth.providers.bearer +import io.ktor.http.HttpStatusCode +import javax.inject.Inject +import javax.inject.Provider + +internal class AuthKtorConfiguration @Inject constructor( + private val authenticationProviderProvider: Provider, + private val logger: Logger, +) : KtorConfiguration { + + private val authenticationProvider: AuthenticationProvider + get() = authenticationProviderProvider.get() + + override fun configure(config: HttpClientConfig) { + config.install(Auth) { + bearer { + loadTokens { + getTokens() + } + + refreshTokens { + val newTokens = getTokens() + val sameAccessToken = newTokens?.accessToken == oldTokens?.accessToken + if (sameAccessToken && response.status == HttpStatusCode.Unauthorized) { + authenticationProvider.logout() + null + } else { + newTokens + } + } + + sendWithoutRequest { true } + } + } + } + + private suspend fun getTokens(): BearerTokens? { + val token = authenticationProvider.getAuthToken() + logger.v { "getTokens(): token = $token" } + return token?.let { BearerTokens(accessToken = it, refreshToken = "") } + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/ContentNegotiationConfiguration.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/ContentNegotiationConfiguration.kt new file mode 100644 index 0000000..a061325 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/ContentNegotiationConfiguration.kt @@ -0,0 +1,19 @@ +package gq.kirmanak.mealient.datasource.ktor + +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import javax.inject.Inject + +internal class ContentNegotiationConfiguration @Inject constructor( + private val json: Json, +) : KtorConfiguration { + + override fun configure(config: HttpClientConfig) { + config.install(ContentNegotiation) { + json(json) + } + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/EncodingKtorConfiguration.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/EncodingKtorConfiguration.kt new file mode 100644 index 0000000..ac4ae11 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/EncodingKtorConfiguration.kt @@ -0,0 +1,17 @@ +package gq.kirmanak.mealient.datasource.ktor + +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngineConfig +import io.ktor.client.plugins.compression.ContentEncoding +import javax.inject.Inject + +internal class EncodingKtorConfiguration @Inject constructor() : KtorConfiguration { + + override fun configure(config: HttpClientConfig) { + config.install(ContentEncoding) { + gzip() + deflate() + identity() + } + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilder.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilder.kt new file mode 100644 index 0000000..a502a33 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilder.kt @@ -0,0 +1,8 @@ +package gq.kirmanak.mealient.datasource.ktor + +import io.ktor.client.HttpClient + +internal interface KtorClientBuilder { + + fun buildKtorClient(): HttpClient +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilderImpl.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilderImpl.kt new file mode 100644 index 0000000..41738ce --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorClientBuilderImpl.kt @@ -0,0 +1,32 @@ +package gq.kirmanak.mealient.datasource.ktor + +import gq.kirmanak.mealient.logging.Logger +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import okhttp3.OkHttpClient +import javax.inject.Inject + +internal class KtorClientBuilderImpl @Inject constructor( + private val configurators: Set<@JvmSuppressWildcards KtorConfiguration>, + private val logger: Logger, + private val okHttpClient: OkHttpClient, +) : KtorClientBuilder { + + override fun buildKtorClient(): HttpClient { + logger.v { "buildKtorClient() called" } + + val client = HttpClient(OkHttp) { + expectSuccess = true + + configurators.forEach { + it.configure(config = this) + } + + engine { + preconfigured = okHttpClient + } + } + + return client + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorConfiguration.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorConfiguration.kt new file mode 100644 index 0000000..2b39788 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorConfiguration.kt @@ -0,0 +1,9 @@ +package gq.kirmanak.mealient.datasource.ktor + +import io.ktor.client.HttpClientConfig +import io.ktor.client.engine.HttpClientEngineConfig + +internal interface KtorConfiguration { + + fun configure(config: HttpClientConfig) +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorModule.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorModule.kt new file mode 100644 index 0000000..a568c29 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/KtorModule.kt @@ -0,0 +1,41 @@ +package gq.kirmanak.mealient.datasource.ktor + +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import dagger.multibindings.IntoSet +import gq.kirmanak.mealient.datasource.SignOutHandler +import io.ktor.client.HttpClient +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface KtorModule { + + companion object { + + @Provides + @Singleton + fun provideClient(builder: KtorClientBuilder): HttpClient = builder.buildKtorClient() + } + + @Binds + @IntoSet + fun bindAuthKtorConfiguration(impl: AuthKtorConfiguration) : KtorConfiguration + + @Binds + @IntoSet + fun bindEncodingKtorConfiguration(impl: EncodingKtorConfiguration) : KtorConfiguration + + @Binds + @IntoSet + fun bindContentNegotiationConfiguration(impl: ContentNegotiationConfiguration) : KtorConfiguration + + @Binds + fun bindKtorClientBuilder(impl: KtorClientBuilderImpl) : KtorClientBuilder + + @Binds + fun bindSignOutHandler(impl: SignOutHandlerKtor) : SignOutHandler +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/SignOutHandlerKtor.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/SignOutHandlerKtor.kt new file mode 100644 index 0000000..d40b414 --- /dev/null +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/ktor/SignOutHandlerKtor.kt @@ -0,0 +1,20 @@ +package gq.kirmanak.mealient.datasource.ktor + +import gq.kirmanak.mealient.datasource.SignOutHandler +import io.ktor.client.HttpClient +import io.ktor.client.plugins.auth.Auth +import io.ktor.client.plugins.auth.providers.BearerAuthProvider +import io.ktor.client.plugins.plugin +import javax.inject.Inject + +internal class SignOutHandlerKtor @Inject constructor( + private val httpClient: HttpClient, +) : SignOutHandler { + + override fun signOut() { + httpClient.plugin(Auth) + .providers + .filterIsInstance() + .forEach { it.clearToken() } + } +} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenRequest.kt similarity index 62% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenRequest.kt index bf5bc90..60b5a01 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenRequest.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class CreateRecipeRequestV1( +data class CreateApiTokenRequest( @SerialName("name") val name: String, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenResponse.kt similarity index 62% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenResponse.kt index 6c10baa..0b184a0 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateApiTokenResponse.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class CreateApiTokenResponseV1( +data class CreateApiTokenResponse( @SerialName("token") val token: String, ) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateRecipeRequest.kt similarity index 62% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateRecipeRequest.kt index a50325e..a15733a 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateApiTokenRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateRecipeRequest.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class CreateApiTokenRequestV1( +data class CreateRecipeRequest( @SerialName("name") val name: String, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateShoppingListItemRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateShoppingListItemRequest.kt similarity index 84% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateShoppingListItemRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateShoppingListItemRequest.kt index c52272d..e76f57a 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/CreateShoppingListItemRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/CreateShoppingListItemRequest.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class CreateShoppingListItemRequestV1( +data class CreateShoppingListItemRequest( @SerialName("shopping_list_id") val shoppingListId: String, @SerialName("checked") val checked: Boolean, @SerialName("position") val position: Int?, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt similarity index 66% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt index 54f3370..407af76 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ErrorDetailV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ErrorDetail.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ErrorDetailV1( +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/FoodInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FoodInfo.kt deleted file mode 100644 index 0b8b05b..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FoodInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class FoodInfo( - val name: String, - val id: String -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullRecipeInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullRecipeInfo.kt deleted file mode 100644 index 3e38561..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullRecipeInfo.kt +++ /dev/null @@ -1,26 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class FullRecipeInfo( - val remoteId: String, - val name: String, - val recipeYield: String, - val recipeIngredients: List, - val recipeInstructions: List, - val settings: RecipeSettingsInfo, -) - -data class RecipeSettingsInfo( - val disableAmounts: Boolean, -) - -data class RecipeIngredientInfo( - val note: String, - val quantity: Double?, - val unit: String?, - val food: String?, - val title: String?, -) - -data class RecipeInstructionInfo( - val text: String, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullShoppingListInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullShoppingListInfo.kt deleted file mode 100644 index b7417f7..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/FullShoppingListInfo.kt +++ /dev/null @@ -1,28 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class FullShoppingListInfo( - val id: String, - val name: String, - val items: List, -) - -data class ShoppingListItemInfo( - val shoppingListId: String, - val id: String, - val checked: Boolean, - val position: Int, - val isFood: Boolean, - val note: String, - val quantity: Double, - val unit: UnitInfo?, - val food: FoodInfo?, - val recipeReferences: List, -) - -data class ShoppingListItemRecipeReferenceInfo( - val recipeId: String, - val recipeQuantity: Double, - val id: String, - val shoppingListId: String, - val recipe: FullRecipeInfo, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetFoodsResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetFoodsResponse.kt similarity index 53% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetFoodsResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetFoodsResponse.kt index 851eaab..307a3ef 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetFoodsResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetFoodsResponse.kt @@ -1,15 +1,15 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetFoodsResponseV1( - @SerialName("items") val items: List, +data class GetFoodsResponse( + @SerialName("items") val items: List, ) @Serializable -data class GetFoodResponseV1( +data class GetFoodResponse( @SerialName("name") val name: String, @SerialName("id") val id: 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/models/GetRecipeResponse.kt similarity index 61% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt index 54e70de..5e80975 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeResponse.kt @@ -1,33 +1,33 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeResponseV1( +data class GetRecipeResponse( @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("recipeYield") val recipeYield: String = "", - @SerialName("recipeIngredient") val recipeIngredients: List = emptyList(), - @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), - @SerialName("settings") val settings: GetRecipeSettingsResponseV1? = null, + @SerialName("recipeIngredient") val recipeIngredients: List = emptyList(), + @SerialName("recipeInstructions") val recipeInstructions: List = emptyList(), + @SerialName("settings") val settings: GetRecipeSettingsResponse? = null, ) @Serializable -data class GetRecipeSettingsResponseV1( +data class GetRecipeSettingsResponse( @SerialName("disableAmount") val disableAmount: Boolean, ) @Serializable -data class GetRecipeIngredientResponseV1( +data class GetRecipeIngredientResponse( @SerialName("note") val note: String = "", - @SerialName("unit") val unit: GetRecipeIngredientUnitResponseV1?, - @SerialName("food") val food: GetRecipeIngredientFoodResponseV1?, + @SerialName("unit") val unit: GetUnitResponse?, + @SerialName("food") val food: GetFoodResponse?, @SerialName("quantity") val quantity: Double?, @SerialName("title") val title: String?, ) @Serializable -data class GetRecipeInstructionResponseV1( +data class GetRecipeInstructionResponse( @SerialName("text") val text: String, ) \ 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/models/GetRecipeSummaryResponse.kt similarity index 84% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt index f4512b8..c01bab5 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeSummaryResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipeSummaryResponse.kt @@ -1,4 +1,4 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime @@ -6,7 +6,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipeSummaryResponseV1( +data class GetRecipeSummaryResponse( @SerialName("id") val remoteId: String, @SerialName("name") val name: String, @SerialName("slug") val slug: String, diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponse.kt similarity index 64% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponse.kt index c6e33f0..bf32d87 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipesResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetRecipesResponse.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetRecipesResponseV1( - @SerialName("items") val items: List, +data class GetRecipesResponse( + @SerialName("items") val items: List, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt similarity index 66% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt index 6f6739e..c946534 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListResponse.kt @@ -1,19 +1,19 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetShoppingListResponseV1( +data class GetShoppingListResponse( @SerialName("id") val id: String, @SerialName("groupId") val groupId: String, @SerialName("name") val name: String = "", - @SerialName("listItems") val listItems: List = emptyList(), - @SerialName("recipeReferences") val recipeReferences: List, + @SerialName("listItems") val listItems: List = emptyList(), + @SerialName("recipeReferences") val recipeReferences: List, ) @Serializable -data class GetShoppingListItemResponseV1( +data class GetShoppingListItemResponse( @SerialName("shoppingListId") val shoppingListId: String, @SerialName("id") val id: String, @SerialName("checked") val checked: Boolean = false, @@ -21,22 +21,22 @@ data class GetShoppingListItemResponseV1( @SerialName("isFood") val isFood: Boolean = false, @SerialName("note") val note: String = "", @SerialName("quantity") val quantity: Double = 0.0, - @SerialName("unit") val unit: GetRecipeIngredientUnitResponseV1? = null, - @SerialName("food") val food: GetRecipeIngredientFoodResponseV1? = null, - @SerialName("recipeReferences") val recipeReferences: List = emptyList(), + @SerialName("unit") val unit: GetUnitResponse? = null, + @SerialName("food") val food: GetFoodResponse? = null, + @SerialName("recipeReferences") val recipeReferences: List = emptyList(), ) @Serializable -data class GetShoppingListItemRecipeReferenceResponseV1( +data class GetShoppingListItemRecipeReferenceResponse( @SerialName("recipeId") val recipeId: String, @SerialName("recipeQuantity") val recipeQuantity: Double = 0.0 ) @Serializable -data class GetShoppingListItemRecipeReferenceFullResponseV1( +data class GetShoppingListItemRecipeReferenceFullResponse( @SerialName("id") val id: String, @SerialName("shoppingListId") val shoppingListId: String, @SerialName("recipeId") val recipeId: String, @SerialName("recipeQuantity") val recipeQuantity: Double = 0.0, - @SerialName("recipe") val recipe: GetRecipeResponseV1, + @SerialName("recipe") val recipe: GetRecipeResponse, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsResponse.kt similarity index 77% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsResponse.kt index 19bad53..2a69b69 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsResponse.kt @@ -1,13 +1,13 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetShoppingListsResponseV1( +data class GetShoppingListsResponse( @SerialName("page") val page: Int, @SerialName("per_page") val perPage: Int, @SerialName("total") val total: Int, @SerialName("total_pages") val totalPages: Int, - @SerialName("items") val items: List, + @SerialName("items") val items: List, ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsSummaryResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsSummaryResponse.kt similarity index 65% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsSummaryResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsSummaryResponse.kt index addbce9..408dfe3 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetShoppingListsSummaryResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetShoppingListsSummaryResponse.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetShoppingListsSummaryResponseV1( +data class GetShoppingListsSummaryResponse( @SerialName("id") val id: String, @SerialName("name") val name: 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/models/GetTokenResponse.kt similarity index 65% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt index 11e96f8..0ecd9b4 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetTokenResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetTokenResponse.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetTokenResponseV1( +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/v1/models/GetUnitsResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUnitsResponse.kt similarity index 53% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUnitsResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUnitsResponse.kt index 921d046..df02677 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUnitsResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUnitsResponse.kt @@ -1,15 +1,15 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetUnitsResponseV1( - @SerialName("items") val items: List +data class GetUnitsResponse( + @SerialName("items") val items: List ) @Serializable -data class GetUnitResponseV1( +data class GetUnitResponse( @SerialName("name") val name: String, @SerialName("id") val id: String ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUserInfoResponse.kt similarity index 72% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUserInfoResponse.kt index ee97e60..e3aefee 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetUserInfoResponseV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/GetUserInfoResponse.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class GetUserInfoResponseV1( +data class GetUserInfoResponse( @SerialName("id") val id: String, @SerialName("favoriteRecipes") val favoriteRecipes: List = emptyList(), ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NewShoppingListItemInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NewShoppingListItemInfo.kt deleted file mode 100644 index b1901cf..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/NewShoppingListItemInfo.kt +++ /dev/null @@ -1,11 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class NewShoppingListItemInfo( - val shoppingListId: String, - val isFood: Boolean, - val note: String, - val quantity: Double, - val unit: UnitInfo?, - val food: FoodInfo?, - val position: Int, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLInfo.kt deleted file mode 100644 index 59358c3..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class ParseRecipeURLInfo( - val url: String, - val includeTags: Boolean -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ParseRecipeURLRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLRequest.kt similarity index 69% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ParseRecipeURLRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLRequest.kt index e1f7671..853ab08 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/ParseRecipeURLRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ParseRecipeURLRequest.kt @@ -1,10 +1,10 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ParseRecipeURLRequestV1( +data class ParseRecipeURLRequest( @SerialName("url") val url: String, @SerialName("includeTags") val includeTags: Boolean ) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/RecipeSummaryInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/RecipeSummaryInfo.kt deleted file mode 100644 index 8a99361..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/RecipeSummaryInfo.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime - -data class RecipeSummaryInfo( - val remoteId: String, - val name: String, - val slug: String, - val description: String = "", - val imageId: String, - val dateAdded: LocalDate, - val dateUpdated: LocalDateTime -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ShoppingListsInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ShoppingListsInfo.kt deleted file mode 100644 index bec5bb9..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/ShoppingListsInfo.kt +++ /dev/null @@ -1,14 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class ShoppingListsInfo( - val page: Int, - val perPage: Int, - val totalPages: Int, - val totalItems: Int, - val items: List, -) - -data class ShoppingListInfo( - val name: String, - val id: String, -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UnitInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UnitInfo.kt deleted file mode 100644 index 39e1947..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UnitInfo.kt +++ /dev/null @@ -1,6 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class UnitInfo( - val name: String, - val id: String -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UpdateRecipeRequest.kt similarity index 80% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UpdateRecipeRequest.kt index 4e04b07..7a41b40 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/UpdateRecipeRequestV1.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/UpdateRecipeRequest.kt @@ -1,19 +1,19 @@ -package gq.kirmanak.mealient.datasource.v1.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class UpdateRecipeRequestV1( +data class UpdateRecipeRequest( @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, + @SerialName("recipeIngredient") val recipeIngredient: List, + @SerialName("recipeInstructions") val recipeInstructions: List, + @SerialName("settings") val settings: AddRecipeSettings, ) @Serializable -data class AddRecipeIngredientV1( +data class AddRecipeIngredient( @SerialName("referenceId") val id: String, @SerialName("note") val note: String, ) { @@ -21,7 +21,7 @@ data class AddRecipeIngredientV1( if (this === other) return true if (javaClass != other?.javaClass) return false - other as AddRecipeIngredientV1 + other as AddRecipeIngredient if (note != other.note) return false @@ -34,7 +34,7 @@ data class AddRecipeIngredientV1( } @Serializable -data class AddRecipeInstructionV1( +data class AddRecipeInstruction( @SerialName("id") val id: String, @SerialName("text") val text: String = "", @SerialName("ingredientReferences") val ingredientReferences: List, @@ -43,7 +43,7 @@ data class AddRecipeInstructionV1( if (this === other) return true if (javaClass != other?.javaClass) return false - other as AddRecipeInstructionV1 + other as AddRecipeInstruction if (text != other.text) return false if (ingredientReferences != other.ingredientReferences) return false @@ -59,7 +59,7 @@ data class AddRecipeInstructionV1( } @Serializable -data class AddRecipeSettingsV1( +data class AddRecipeSettings( @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/models/VersionInfo.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionInfo.kt deleted file mode 100644 index a746dc2..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionInfo.kt +++ /dev/null @@ -1,5 +0,0 @@ -package gq.kirmanak.mealient.datasource.models - -data class VersionInfo( - val version: 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/models/VersionResponse.kt similarity index 64% rename from datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt rename to datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt index 590e5d4..22cd45d 100644 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/VersionResponseV0.kt +++ b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/models/VersionResponse.kt @@ -1,9 +1,9 @@ -package gq.kirmanak.mealient.datasource.v0.models +package gq.kirmanak.mealient.datasource.models import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class VersionResponseV0( +data class VersionResponse( @SerialName("version") val version: String, ) \ No newline at end of file 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 deleted file mode 100644 index b760abf..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0.kt +++ /dev/null @@ -1,52 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0 - -import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenResponseV0 -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 - -interface MealieDataSourceV0 { - - suspend fun addRecipe( - recipe: AddRecipeRequestV0, - ): String - - /** - * Tries to acquire authentication token using the provided credentials - */ - suspend fun authenticate( - username: String, - password: String, - ): String - - suspend fun getVersionInfo(): VersionResponseV0 - - suspend fun requestRecipes( - start: Int, - limit: Int, - ): List - - suspend fun requestRecipeInfo( - slug: String, - ): GetRecipeResponseV0 - - suspend fun parseRecipeFromURL( - request: ParseRecipeURLRequestV0, - ): String - - suspend fun createApiToken( - request: CreateApiTokenRequestV0, - ): CreateApiTokenResponseV0 - - suspend fun requestUserInfo(): GetUserInfoResponseV0 - - suspend fun removeFavoriteRecipe(userId: Int, recipeSlug: String) - - suspend fun addFavoriteRecipe(userId: Int, recipeSlug: String) - - suspend fun deleteRecipe(slug: String) -} \ 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 deleted file mode 100644 index 9aebca7..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieDataSourceV0Impl.kt +++ /dev/null @@ -1,125 +0,0 @@ -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.AddRecipeRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.CreateApiTokenResponseV0 -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 -import kotlinx.serialization.json.Json -import retrofit2.HttpException -import java.net.ConnectException -import java.net.SocketTimeoutException -import javax.inject.Inject - -class MealieDataSourceV0Impl @Inject constructor( - private val networkRequestWrapper: NetworkRequestWrapper, - private val service: MealieServiceV0, - private val json: Json, -) : MealieDataSourceV0 { - - override suspend fun addRecipe( - recipe: AddRecipeRequestV0, - ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.addRecipe(recipe) }, - logMethod = { "addRecipe" }, - logParameters = { "recipe = $recipe" } - ) - - override suspend fun authenticate( - username: String, - password: String, - ): String = networkRequestWrapper.makeCall( - block = { service.getToken(username, password) }, - logMethod = { "authenticate" }, - logParameters = { "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(): VersionResponseV0 = networkRequestWrapper.makeCall( - block = { service.getVersion() }, - logMethod = { "getVersionInfo" }, - ).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( - start: Int, - limit: Int, - ): List = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.getRecipeSummary(start, limit) }, - logMethod = { "requestRecipes" }, - logParameters = { "start = $start, limit = $limit" } - ) - - override suspend fun requestRecipeInfo( - slug: String, - ): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.getRecipe(slug) }, - logMethod = { "requestRecipeInfo" }, - logParameters = { "slug = $slug" } - ) - - override suspend fun parseRecipeFromURL( - request: ParseRecipeURLRequestV0 - ): String = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.createRecipeFromURL(request) }, - logMethod = { "parseRecipeFromURL" }, - logParameters = { "request = $request" }, - ) - - override suspend fun createApiToken( - request: CreateApiTokenRequestV0, - ): CreateApiTokenResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.createApiToken(request) }, - logMethod = { "createApiToken" }, - logParameters = { "request = $request" } - ) - - override suspend fun requestUserInfo(): GetUserInfoResponseV0 { - return networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.getUserSelfInfo() }, - logMethod = { "requestUserInfo" }, - ) - } - - override suspend fun removeFavoriteRecipe( - userId: Int, - recipeSlug: String - ): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.removeFavoriteRecipe(userId, recipeSlug) }, - logMethod = { "removeFavoriteRecipe" }, - logParameters = { "userId = $userId, recipeSlug = $recipeSlug" } - ) - - override suspend fun addFavoriteRecipe( - userId: Int, - recipeSlug: String - ): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.addFavoriteRecipe(userId, recipeSlug) }, - logMethod = { "addFavoriteRecipe" }, - logParameters = { "userId = $userId, recipeSlug = $recipeSlug" } - ) - - override suspend fun deleteRecipe( - slug: String - ): Unit = networkRequestWrapper.makeCallAndHandleUnauthorized( - block = { service.deleteRecipe(slug) }, - logMethod = { "deleteRecipe" }, - logParameters = { "slug = $slug" } - ) -} 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 deleted file mode 100644 index 1f291b8..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/MealieServiceV0.kt +++ /dev/null @@ -1,63 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0 - -import gq.kirmanak.mealient.datasource.v0.models.* -import retrofit2.http.* - -interface MealieServiceV0 { - - @FormUrlEncoded - @POST("/api/auth/token") - suspend fun getToken( - @Field("username") username: String, - @Field("password") password: String, - ): GetTokenResponseV0 - - @POST("/api/recipes/create") - suspend fun addRecipe( - @Body addRecipeRequestV0: AddRecipeRequestV0, - ): String - - @GET("/api/debug/version") - suspend fun getVersion(): VersionResponseV0 - - @GET("/api/recipes/summary") - suspend fun getRecipeSummary( - @Query("start") start: Int, - @Query("limit") limit: Int, - ): List - - @GET("/api/recipes/{slug}") - suspend fun getRecipe( - @Path("slug") slug: String, - ): GetRecipeResponseV0 - - @POST("/api/recipes/create-url") - suspend fun createRecipeFromURL( - @Body request: ParseRecipeURLRequestV0, - ): String - - @POST("/api/users/api-tokens") - suspend fun createApiToken( - @Body request: CreateApiTokenRequestV0, - ): CreateApiTokenResponseV0 - - @GET("/api/users/self") - suspend fun getUserSelfInfo(): GetUserInfoResponseV0 - - @DELETE("/api/users/{userId}/favorites/{recipeSlug}") - suspend fun removeFavoriteRecipe( - @Path("userId") userId: Int, - @Path("recipeSlug") recipeSlug: String - ) - - @POST("/api/users/{userId}/favorites/{recipeSlug}") - suspend fun addFavoriteRecipe( - @Path("userId") userId: Int, - @Path("recipeSlug") recipeSlug: String - ) - - @DELETE("/api/recipes/{slug}") - suspend fun deleteRecipe( - @Path("slug") slug: 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 deleted file mode 100644 index fa6e6c7..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/AddRecipeRequestV0.kt +++ /dev/null @@ -1,30 +0,0 @@ -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("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, -) - -@Serializable -data class AddRecipeInstructionV0( - @SerialName("text") val text: String, -) - -@Serializable -data class AddRecipeSettingsV0( - @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/v0/models/CreateApiTokenRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/CreateApiTokenRequestV0.kt deleted file mode 100644 index 67315ec..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/CreateApiTokenRequestV0.kt +++ /dev/null @@ -1,9 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CreateApiTokenRequestV0( - @SerialName("name") val name: String, -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/CreateApiTokenResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/CreateApiTokenResponseV0.kt deleted file mode 100644 index a04d5f7..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/CreateApiTokenResponseV0.kt +++ /dev/null @@ -1,9 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class CreateApiTokenResponseV0( - @SerialName("token") val token: String -) 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 deleted file mode 100644 index 50bce5e..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ErrorDetailV0.kt +++ /dev/null @@ -1,7 +0,0 @@ -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/v0/models/GetRecipeResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt deleted file mode 100644 index 2930c61..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeResponseV0.kt +++ /dev/null @@ -1,23 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeResponseV0( - @SerialName("id") val remoteId: Int, - @SerialName("name") val name: String, - @SerialName("recipeYield") val recipeYield: String = "", - @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/GetRecipeSummaryResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt deleted file mode 100644 index cc28c55..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetRecipeSummaryResponseV0.kt +++ /dev/null @@ -1,16 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.datetime.LocalDate -import kotlinx.datetime.LocalDateTime -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class GetRecipeSummaryResponseV0( - @SerialName("id") val remoteId: Int, - @SerialName("name") val name: String, - @SerialName("slug") val slug: String, - @SerialName("description") val description: String = "", - @SerialName("dateAdded") val dateAdded: LocalDate, - @SerialName("dateUpdated") val dateUpdated: LocalDateTime -) \ 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 deleted file mode 100644 index 2e4cfa9..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetTokenResponseV0.kt +++ /dev/null @@ -1,9 +0,0 @@ -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/GetUserInfoResponseV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.kt deleted file mode 100644 index 005ccf1..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/GetUserInfoResponseV0.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 GetUserInfoResponseV0( - @SerialName("id") val id: Int, - @SerialName("favoriteRecipes") val favoriteRecipes: List = emptyList(), -) diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ParseRecipeURLRequestV0.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ParseRecipeURLRequestV0.kt deleted file mode 100644 index 7f46ece..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v0/models/ParseRecipeURLRequestV0.kt +++ /dev/null @@ -1,9 +0,0 @@ -package gq.kirmanak.mealient.datasource.v0.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class ParseRecipeURLRequestV0( - @SerialName("url") val url: String, -) 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 deleted file mode 100644 index f9c17b1..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieDataSourceV1.kt +++ /dev/null @@ -1,79 +0,0 @@ -package gq.kirmanak.mealient.datasource.v1 - -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateApiTokenResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1 -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 - -interface MealieDataSourceV1 { - - suspend fun createRecipe( - recipe: CreateRecipeRequestV1, - ): String - - suspend fun updateRecipe( - slug: String, - recipe: UpdateRecipeRequestV1, - ): GetRecipeResponseV1 - - /** - * Tries to acquire authentication token using the provided credentials - */ - suspend fun authenticate( - username: String, - password: String, - ): String - - suspend fun getVersionInfo( - ): VersionResponseV1 - - suspend fun requestRecipes( - page: Int, - perPage: Int, - ): List - - suspend fun requestRecipeInfo( - slug: String, - ): GetRecipeResponseV1 - - suspend fun parseRecipeFromURL( - request: ParseRecipeURLRequestV1, - ): String - - suspend fun createApiToken( - request: CreateApiTokenRequestV1, - ): CreateApiTokenResponseV1 - - suspend fun requestUserInfo(): GetUserInfoResponseV1 - - suspend fun removeFavoriteRecipe(userId: String, recipeSlug: String) - - suspend fun addFavoriteRecipe(userId: String, recipeSlug: String) - - suspend fun deleteRecipe(slug: String) - - suspend fun getShoppingLists(page: Int, perPage: Int): GetShoppingListsResponseV1 - - suspend fun getShoppingList(id: String): GetShoppingListResponseV1 - - suspend fun deleteShoppingListItem(id: String) - - suspend fun updateShoppingListItem(item: ShoppingListItemInfo) - - suspend fun getFoods(): GetFoodsResponseV1 - - suspend fun getUnits(): GetUnitsResponseV1 - - suspend fun addShoppingListItem(request: CreateShoppingListItemRequestV1) -} \ No newline at end of file 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 deleted file mode 100644 index 78c7f1b..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/MealieServiceV1.kt +++ /dev/null @@ -1,112 +0,0 @@ -package gq.kirmanak.mealient.datasource.v1 - -import gq.kirmanak.mealient.datasource.v1.models.* -import kotlinx.serialization.json.JsonElement -import retrofit2.http.* - -interface MealieServiceV1 { - - @FormUrlEncoded - @POST("/api/auth/token") - suspend fun getToken( - @Field("username") username: String, - @Field("password") password: String, - ): GetTokenResponseV1 - - @POST("/api/recipes") - suspend fun createRecipe( - @Body addRecipeRequest: CreateRecipeRequestV1, - ): String - - @PATCH("/api/recipes/{slug}") - suspend fun updateRecipe( - @Body addRecipeRequest: UpdateRecipeRequestV1, - @Path("slug") slug: String, - ): GetRecipeResponseV1 - - @GET("/api/app/about") - suspend fun getVersion(): VersionResponseV1 - - @GET("/api/recipes") - suspend fun getRecipeSummary( - @Query("page") page: Int, - @Query("perPage") perPage: Int, - ): GetRecipesResponseV1 - - @GET("/api/recipes/{slug}") - suspend fun getRecipe( - @Path("slug") slug: String, - ): GetRecipeResponseV1 - - @POST("/api/recipes/create-url") - suspend fun createRecipeFromURL( - @Body request: ParseRecipeURLRequestV1, - ): String - - @POST("/api/users/api-tokens") - suspend fun createApiToken( - @Body request: CreateApiTokenRequestV1, - ): CreateApiTokenResponseV1 - - @GET("/api/users/self") - suspend fun getUserSelfInfo(): GetUserInfoResponseV1 - - @DELETE("/api/users/{userId}/favorites/{recipeSlug}") - suspend fun removeFavoriteRecipe( - @Path("userId") userId: String, - @Path("recipeSlug") recipeSlug: String - ) - - @POST("/api/users/{userId}/favorites/{recipeSlug}") - suspend fun addFavoriteRecipe( - @Path("userId") userId: String, - @Path("recipeSlug") recipeSlug: String - ) - - @DELETE("/api/recipes/{slug}") - suspend fun deleteRecipe( - @Path("slug") slug: String - ) - - @GET("/api/groups/shopping/lists") - suspend fun getShoppingLists( - @Query("page") page: Int, - @Query("perPage") perPage: Int, - ): GetShoppingListsResponseV1 - - @GET("/api/groups/shopping/lists/{id}") - suspend fun getShoppingList( - @Path("id") id: String, - ): GetShoppingListResponseV1 - - @GET("/api/groups/shopping/items/{id}") - suspend fun getShoppingListItem( - @Path("id") id: String, - ): JsonElement - - @PUT("/api/groups/shopping/items/{id}") - suspend fun updateShoppingListItem( - @Path("id") id: String, - @Body request: JsonElement, - ) - - @DELETE("/api/groups/shopping/items/{id}") - suspend fun deleteShoppingListItem( - @Path("id") id: String, - ) - - @GET("/api/foods") - suspend fun getFoods( - @Query("perPage") perPage: Int, - ): GetFoodsResponseV1 - - @GET("/api/units") - suspend fun getUnits( - @Query("perPage") perPage: Int, - ): GetUnitsResponseV1 - - @POST("/api/groups/shopping/items") - suspend fun createShoppingListItem( - @Body request: CreateShoppingListItemRequestV1, - ) -} \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientFoodResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientFoodResponseV1.kt deleted file mode 100644 index 3ec2d90..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientFoodResponseV1.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 GetRecipeIngredientFoodResponseV1( - @SerialName("name") val name: String = "", - @SerialName("id") val id: String = "", -) \ No newline at end of file diff --git a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientUnitResponseV1.kt b/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientUnitResponseV1.kt deleted file mode 100644 index 189a028..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/GetRecipeIngredientUnitResponseV1.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 GetRecipeIngredientUnitResponseV1( - @SerialName("name") val name: String = "", - @SerialName("id") val id: 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 deleted file mode 100644 index 45f5cb5..0000000 --- a/datasource/src/main/kotlin/gq/kirmanak/mealient/datasource/v1/models/VersionResponseV1.kt +++ /dev/null @@ -1,9 +0,0 @@ -package gq.kirmanak.mealient.datasource.v1.models - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -data class VersionResponseV1( - @SerialName("version") val version: String, -) \ 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 deleted file mode 100644 index 0f2528a..0000000 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/MealieDataSourceV0ImplTest.kt +++ /dev/null @@ -1,113 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl -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.test.BaseUnitTest -import io.mockk.coEvery -import io.mockk.impl.annotations.MockK -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import kotlinx.serialization.SerializationException -import kotlinx.serialization.json.Json -import org.junit.Before -import org.junit.Test -import retrofit2.HttpException -import retrofit2.Response -import java.io.IOException -import java.net.ConnectException - -@OptIn(ExperimentalCoroutinesApi::class) -class MealieDataSourceV0ImplTest : BaseUnitTest() { - - @MockK - lateinit var service: MealieServiceV0 - - lateinit var subject: MealieDataSourceV0Impl - - @Before - override fun setUp() { - super.setUp() - val networkRequestWrapper: NetworkRequestWrapper = NetworkRequestWrapperImpl(logger) - subject = MealieDataSourceV0Impl(networkRequestWrapper, service, Json) - } - - @Test(expected = NetworkError.NotMealie::class) - fun `when getVersionInfo and getVersion throws HttpException then NotMealie`() = runTest { - val error = HttpException(Response.error(404, "".toJsonResponseBody())) - coEvery { service.getVersion() } throws error - subject.getVersionInfo() - } - - @Test(expected = NetworkError.NotMealie::class) - fun `when getVersionInfo and getVersion throws SerializationException then NotMealie`() = - runTest { - coEvery { service.getVersion() } throws SerializationException() - subject.getVersionInfo() - } - - @Test(expected = NetworkError.NoServerConnection::class) - fun `when getVersionInfo and getVersion throws IOException then NoServerConnection`() = - runTest { - coEvery { service.getVersion() } throws ConnectException() - subject.getVersionInfo() - } - - @Test - fun `when getVersionInfo and getVersion returns result then result`() = runTest { - val versionResponse = VersionResponseV0("v0.5.6") - coEvery { service.getVersion() } returns versionResponse - assertThat(subject.getVersionInfo()).isSameInstanceAs(versionResponse) - } - - @Test - fun `when authentication is successful then token is correct`() = runTest { - coEvery { service.getToken(any(), any()) } returns GetTokenResponseV0(TEST_TOKEN) - assertThat(callAuthenticate()).isEqualTo(TEST_TOKEN) - } - - @Test(expected = NetworkError.Unauthorized::class) - fun `when authenticate receives 401 and Unauthorized then throws Unauthorized`() = runTest { - val body = "{\"detail\":\"Unauthorized\"}".toJsonResponseBody() - coEvery { - service.getToken(any(), any()) - } throws HttpException(Response.error(401, body)) - callAuthenticate() - } - - @Test(expected = HttpException::class) - fun `when authenticate receives 401 but not Unauthorized then throws NotMealie`() = runTest { - val body = "{\"detail\":\"Something\"}".toJsonResponseBody() - coEvery { - service.getToken(any(), any()) - } throws HttpException(Response.error(401, body)) - callAuthenticate() - } - - @Test(expected = SerializationException::class) - fun `when authenticate receives 404 and empty body then throws NotMealie`() = runTest { - val body = "".toJsonResponseBody() - coEvery { - service.getToken(any(), any()) - } throws HttpException(Response.error(401, body)) - callAuthenticate() - } - - @Test(expected = IOException::class) - fun `when authenticate and getToken throws then throws NoServerConnection`() = runTest { - coEvery { service.getToken(any(), any()) } throws IOException("Server not found") - callAuthenticate() - } - - private suspend fun callAuthenticate(): String = - subject.authenticate(TEST_PASSWORD, TEST_BASE_URL) - - companion object { - const val TEST_PASSWORD = "TEST_PASSWORD" - const val TEST_BASE_URL = "https://example.com/" - const val TEST_TOKEN = "TEST_TOKEN" - } -} \ No newline at end of file diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/TestExtensions.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/TestExtensions.kt deleted file mode 100644 index 024b89f..0000000 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/TestExtensions.kt +++ /dev/null @@ -1,7 +0,0 @@ -package gq.kirmanak.mealient.datasource - -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.ResponseBody.Companion.toResponseBody - -fun String.toJsonResponseBody() = toResponseBody("application/json".toMediaType()) - diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptorTest.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptorTest.kt deleted file mode 100644 index 16cb895..0000000 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/AuthInterceptorTest.kt +++ /dev/null @@ -1,111 +0,0 @@ -package gq.kirmanak.mealient.datasource.impl - -import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.AuthenticationProvider -import gq.kirmanak.mealient.datasource.impl.AuthInterceptor.Companion.HEADER_NAME -import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.impl.annotations.MockK -import io.mockk.slot -import okhttp3.Interceptor -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import org.junit.Before -import org.junit.Test - -class AuthInterceptorTest : BaseUnitTest() { - - private lateinit var subject: Interceptor - - @MockK(relaxed = true) - lateinit var authenticationProvider: AuthenticationProvider - - @MockK(relaxed = true) - lateinit var chain: Interceptor.Chain - - @Before - override fun setUp() { - super.setUp() - subject = AuthInterceptor(logger) { authenticationProvider } - } - - @Test - fun `when intercept is called expect header to be retrieved`() { - subject.intercept(chain) - coVerify { authenticationProvider.getAuthHeader() } - } - - @Test - fun `when intercept is called and no header expect no header`() { - coEvery { authenticationProvider.getAuthHeader() } returns null - coEvery { chain.request() } returns buildRequest() - val requestSlot = slot() - coEvery { chain.proceed(capture(requestSlot)) } returns buildResponse() - subject.intercept(chain) - assertThat(requestSlot.captured.header(HEADER_NAME)).isNull() - } - - @Test - fun `when intercept is called and no header expect no logout`() { - coEvery { authenticationProvider.getAuthHeader() } returns null - coEvery { chain.request() } returns buildRequest() - coEvery { chain.proceed(any()) } returns buildResponse(code = 200) - subject.intercept(chain) - coVerify(inverse = true) { authenticationProvider.logout() } - } - - @Test - fun `when intercept is called with no header and auth fails expect no logout`() { - coEvery { authenticationProvider.getAuthHeader() } returns null - coEvery { chain.request() } returns buildRequest() - coEvery { chain.proceed(any()) } returns buildResponse(code = 401) - subject.intercept(chain) - coVerify(inverse = true) { authenticationProvider.logout() } - } - - @Test - fun `when intercept is called and there is a header expect a header`() { - coEvery { authenticationProvider.getAuthHeader() } returns "header" - coEvery { chain.request() } returns buildRequest() - val requestSlot = slot() - coEvery { chain.proceed(capture(requestSlot)) } returns buildResponse() - subject.intercept(chain) - assertThat(requestSlot.captured.header(HEADER_NAME)).isEqualTo("header") - } - - @Test - fun `when intercept is called and there is a header that authenticates expect no logout`() { - coEvery { authenticationProvider.getAuthHeader() } returns "header" - coEvery { chain.request() } returns buildRequest() - coEvery { chain.proceed(any()) } returns buildResponse(code = 200) - subject.intercept(chain) - coVerify(inverse = true) { authenticationProvider.logout() } - } - - @Test - fun `when intercept is called and there was a header but still 401 expect logout`() { - coEvery { authenticationProvider.getAuthHeader() } returns "header" - coEvery { chain.request() } returns buildRequest() - coEvery { chain.proceed(any()) } returns buildResponse(code = 401) - subject.intercept(chain) - coVerify { authenticationProvider.logout() } - } - - private fun buildResponse( - url: String = "http://localhost", - code: Int = 200, - message: String = if (code == 200) "OK" else "Unauthorized", - protocol: Protocol = Protocol.HTTP_2, - ) = Response.Builder().apply { - request(buildRequest(url)) - code(code) - message(message) - protocol(protocol) - }.build() - - private fun buildRequest( - url: String = "http://localhost", - ) = Request.Builder().url(url).build() -} \ No newline at end of file diff --git a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptorTest.kt b/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptorTest.kt deleted file mode 100644 index 539bef0..0000000 --- a/datasource/src/test/kotlin/gq/kirmanak/mealient/datasource/impl/BaseUrlInterceptorTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package gq.kirmanak.mealient.datasource.impl - -import com.google.common.truth.Truth.assertThat -import gq.kirmanak.mealient.datasource.ServerUrlProvider -import gq.kirmanak.mealient.test.BaseUnitTest -import io.mockk.coEvery -import io.mockk.every -import io.mockk.impl.annotations.MockK -import io.mockk.mockk -import io.mockk.slot -import okhttp3.Interceptor -import okhttp3.Request -import org.junit.Before -import org.junit.Test - -class BaseUrlInterceptorTest : BaseUnitTest() { - - private lateinit var subject: Interceptor - - @MockK - lateinit var serverUrlProvider: ServerUrlProvider - - @MockK(relaxUnitFun = true) - lateinit var chain: Interceptor.Chain - - @Before - override fun setUp() { - super.setUp() - subject = BaseUrlInterceptor(logger) { serverUrlProvider } - } - - @Test - fun `when intercept is called expect it changes the url`() { - val requestSlot = slot() - every { chain.proceed(capture(requestSlot)) } returns mockk() - every { chain.request() } returns buildRequest() - coEvery { serverUrlProvider.getUrl() } returns "https://mealie:3241/" - subject.intercept(chain) - assertThat(requestSlot.captured.url.toString()).isEqualTo("https://mealie:3241/") - } - - private fun buildRequest( - url: String = "http://localhost", - ) = Request.Builder().apply { - url(url) - }.build() -} \ No newline at end of file diff --git a/datasource_test/src/main/kotlin/gq/kirmanak/mealient/datasource_test/TestData.kt b/datasource_test/src/main/kotlin/gq/kirmanak/mealient/datasource_test/TestData.kt index d0ff5c1..3aa9564 100644 --- a/datasource_test/src/main/kotlin/gq/kirmanak/mealient/datasource_test/TestData.kt +++ b/datasource_test/src/main/kotlin/gq/kirmanak/mealient/datasource_test/TestData.kt @@ -1,122 +1,41 @@ package gq.kirmanak.mealient.datasource_test import gq.kirmanak.mealient.datasource.models.AddRecipeInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient import gq.kirmanak.mealient.datasource.models.AddRecipeIngredientInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction import gq.kirmanak.mealient.datasource.models.AddRecipeInstructionInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeSettings import gq.kirmanak.mealient.datasource.models.AddRecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo -import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo -import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo -import gq.kirmanak.mealient.datasource.models.VersionInfo -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.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 gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeIngredientV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeInstructionV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeSettingsV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +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.GetRecipeSettingsResponse +import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse +import gq.kirmanak.mealient.datasource.models.UpdateRecipeRequest import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime -val RECIPE_SUMMARY_CAKE = RecipeSummaryInfo( +val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponse( remoteId = "1", name = "Cake", slug = "cake", description = "A tasty cake", dateAdded = LocalDate.parse("2021-11-13"), dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"), - imageId = "cake", ) -val RECIPE_SUMMARY_PORRIDGE_V0 = RecipeSummaryInfo( +val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponse( remoteId = "2", name = "Porridge", slug = "porridge", description = "A tasty porridge", dateAdded = LocalDate.parse("2021-11-12"), dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), - imageId = "porridge", ) -val RECIPE_SUMMARY_PORRIDGE_V1 = RecipeSummaryInfo( - remoteId = "2", - name = "Porridge", - slug = "porridge", - description = "A tasty porridge", - dateAdded = LocalDate.parse("2021-11-12"), - dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), - imageId = "2", -) - -val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE_V0) - -val SUGAR_INGREDIENT = RecipeIngredientInfo( - note = "2 oz of white sugar", - quantity = 1.0, - unit = null, - food = null, - title = null, -) - -val BREAD_INGREDIENT = RecipeIngredientInfo( - note = "2 oz of white bread", - quantity = 1.0, - unit = null, - food = null, - title = null, -) - -private val MILK_INGREDIENT = RecipeIngredientInfo( - note = "2 oz of white milk", - quantity = 1.0, - unit = null, - food = null, - title = null, -) - -val MIX_INSTRUCTION = RecipeInstructionInfo( - text = "Mix the ingredients" -) - -private val BAKE_INSTRUCTION = RecipeInstructionInfo( - text = "Bake the ingredients" -) - -private val BOIL_INSTRUCTION = RecipeInstructionInfo( - text = "Boil the ingredients" -) - -val CAKE_FULL_RECIPE_INFO = FullRecipeInfo( - remoteId = "1", - name = "Cake", - recipeYield = "4 servings", - recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT), - recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION), - settings = RecipeSettingsInfo(disableAmounts = true) -) - -val PORRIDGE_FULL_RECIPE_INFO = FullRecipeInfo( - remoteId = "2", - name = "Porridge", - recipeYield = "3 servings", - recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT), - recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION), - settings = RecipeSettingsInfo(disableAmounts = true) -) +val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE) val SUGAR_ADD_RECIPE_INGREDIENT_INFO = AddRecipeIngredientInfo("2 oz of white sugar") @@ -143,16 +62,7 @@ val PORRIDGE_ADD_RECIPE_INFO = AddRecipeInfo( settings = ADD_RECIPE_INFO_SETTINGS, ) -val PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 = GetRecipeSummaryResponseV0( - remoteId = 2, - name = "Porridge", - slug = "porridge", - description = "A tasty porridge", - dateAdded = LocalDate.parse("2021-11-12"), - dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), -) - -val PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 = GetRecipeSummaryResponseV1( +val PORRIDGE_RECIPE_SUMMARY_RESPONSE = GetRecipeSummaryResponse( remoteId = "2", name = "Porridge", slug = "porridge", @@ -161,19 +71,7 @@ val PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 = GetRecipeSummaryResponseV1( dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"), ) -val VERSION_RESPONSE_V0 = VersionResponseV0("v0.5.6") - -val VERSION_INFO_V0 = VersionInfo("v0.5.6") - -val VERSION_RESPONSE_V1 = VersionResponseV1("v1.0.0-beta05") - -val VERSION_INFO_V1 = VersionInfo("v1.0.0-beta05") - -val MILK_RECIPE_INGREDIENT_RESPONSE_V0 = GetRecipeIngredientResponseV0("2 oz of white milk") - -val SUGAR_RECIPE_INGREDIENT_RESPONSE_V0 = GetRecipeIngredientResponseV0("2 oz of white sugar") - -val MILK_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1( +val MILK_RECIPE_INGREDIENT_RESPONSE = GetRecipeIngredientResponse( note = "2 oz of white milk", quantity = 1.0, unit = null, @@ -181,7 +79,7 @@ val MILK_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1( title = null, ) -val SUGAR_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1( +val SUGAR_RECIPE_INGREDIENT_RESPONSE = GetRecipeIngredientResponse( note = "2 oz of white sugar", quantity = 1.0, unit = null, @@ -189,113 +87,81 @@ val SUGAR_RECIPE_INGREDIENT_RESPONSE_V1 = GetRecipeIngredientResponseV1( title = null, ) -val MILK_RECIPE_INGREDIENT_INFO = RecipeIngredientInfo( - note = "2 oz of white milk", +val BREAD_RECIPE_INGREDIENT_RESPONSE = GetRecipeIngredientResponse( + note = "2 oz of white bread", quantity = 1.0, unit = null, food = null, title = null, ) -val MIX_RECIPE_INSTRUCTION_RESPONSE_V0 = GetRecipeInstructionResponseV0("Mix the ingredients") +val MIX_RECIPE_INSTRUCTION_RESPONSE = GetRecipeInstructionResponse("Mix the ingredients") -val BOIL_RECIPE_INSTRUCTION_RESPONSE_V0 = GetRecipeInstructionResponseV0("Boil the ingredients") +val BAKE_RECIPE_INSTRUCTION_RESPONSE = GetRecipeInstructionResponse("Bake the ingredients") -val MIX_RECIPE_INSTRUCTION_RESPONSE_V1 = GetRecipeInstructionResponseV1("Mix the ingredients") +val BOIL_RECIPE_INSTRUCTION_RESPONSE = GetRecipeInstructionResponse("Boil the ingredients") -val BOIL_RECIPE_INSTRUCTION_RESPONSE_V1 = GetRecipeInstructionResponseV1("Boil the ingredients") +val NO_AMOUNT_RECIPE_SETTINGS_RESPONSE = GetRecipeSettingsResponse(disableAmount = true) -val MIX_RECIPE_INSTRUCTION_INFO = RecipeInstructionInfo("Mix the ingredients") - -val PORRIDGE_RECIPE_RESPONSE_V0 = GetRecipeResponseV0( - remoteId = 2, - name = "Porridge", - recipeYield = "3 servings", - recipeIngredients = listOf( - SUGAR_RECIPE_INGREDIENT_RESPONSE_V0, - MILK_RECIPE_INGREDIENT_RESPONSE_V0, - ), - recipeInstructions = listOf( - MIX_RECIPE_INSTRUCTION_RESPONSE_V0, - BOIL_RECIPE_INSTRUCTION_RESPONSE_V0 - ), +val CAKE_RECIPE_RESPONSE = GetRecipeResponse( + remoteId = "1", + name = "Cake", + recipeYield = "4 servings", + recipeIngredients = listOf(SUGAR_RECIPE_INGREDIENT_RESPONSE, BREAD_RECIPE_INGREDIENT_RESPONSE), + recipeInstructions = listOf(MIX_RECIPE_INSTRUCTION_RESPONSE, BAKE_RECIPE_INSTRUCTION_RESPONSE), + settings = NO_AMOUNT_RECIPE_SETTINGS_RESPONSE, ) -val PORRIDGE_RECIPE_RESPONSE_V1 = GetRecipeResponseV1( +val PORRIDGE_RECIPE_RESPONSE = GetRecipeResponse( remoteId = "2", recipeYield = "3 servings", name = "Porridge", recipeIngredients = listOf( - SUGAR_RECIPE_INGREDIENT_RESPONSE_V1, - MILK_RECIPE_INGREDIENT_RESPONSE_V1, + SUGAR_RECIPE_INGREDIENT_RESPONSE, + MILK_RECIPE_INGREDIENT_RESPONSE, ), recipeInstructions = listOf( - MIX_RECIPE_INSTRUCTION_RESPONSE_V1, - BOIL_RECIPE_INSTRUCTION_RESPONSE_V1 + MIX_RECIPE_INSTRUCTION_RESPONSE, + BOIL_RECIPE_INSTRUCTION_RESPONSE ), ) -val MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V0 = AddRecipeInstructionV0("Mix the ingredients") - -val BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V0 = AddRecipeInstructionV0("Boil the ingredients") - -val SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V0 = AddRecipeIngredientV0("2 oz of white sugar") - -val MILK_ADD_RECIPE_INGREDIENT_REQUEST_V0 = AddRecipeIngredientV0("2 oz of white milk") - -val ADD_RECIPE_REQUEST_SETTINGS_V0 = AddRecipeSettingsV0(disableComments = false, public = true) - -val PORRIDGE_ADD_RECIPE_REQUEST_V0 = AddRecipeRequestV0( - name = "Porridge", - description = "A tasty porridge", - recipeYield = "3 servings", - recipeInstructions = listOf( - MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V0, - BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V0, - ), - recipeIngredient = listOf( - MILK_ADD_RECIPE_INGREDIENT_REQUEST_V0, - SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V0, - ), - settings = ADD_RECIPE_REQUEST_SETTINGS_V0 -) - -val MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V1 = AddRecipeInstructionV1( +val MIX_ADD_RECIPE_INSTRUCTION_REQUEST = AddRecipeInstruction( id = "1", text = "Mix the ingredients", ingredientReferences = emptyList() ) -val BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V1 = AddRecipeInstructionV1( +val BOIL_ADD_RECIPE_INSTRUCTION_REQUEST = AddRecipeInstruction( id = "2", text = "Boil the ingredients", ingredientReferences = emptyList() ) -val SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V1 = AddRecipeIngredientV1( +val SUGAR_ADD_RECIPE_INGREDIENT_REQUEST = AddRecipeIngredient( id = "3", note = "2 oz of white sugar" ) -val MILK_ADD_RECIPE_INGREDIENT_REQUEST_V1 = AddRecipeIngredientV1( +val MILK_ADD_RECIPE_INGREDIENT_REQUEST = AddRecipeIngredient( id = "4", note = "2 oz of white milk" ) -val ADD_RECIPE_REQUEST_SETTINGS_V1 = AddRecipeSettingsV1(disableComments = false, public = true) +val ADD_RECIPE_REQUEST_SETTINGS = AddRecipeSettings(disableComments = false, public = true) -val PORRIDGE_CREATE_RECIPE_REQUEST_V1 = CreateRecipeRequestV1(name = "Porridge") +val PORRIDGE_CREATE_RECIPE_REQUEST = CreateRecipeRequest(name = "Porridge") -val PORRIDGE_UPDATE_RECIPE_REQUEST_V1 = UpdateRecipeRequestV1( +val PORRIDGE_UPDATE_RECIPE_REQUEST = UpdateRecipeRequest( description = "A tasty porridge", recipeYield = "3 servings", recipeInstructions = listOf( - MIX_ADD_RECIPE_INSTRUCTION_REQUEST_V1, - BOIL_ADD_RECIPE_INSTRUCTION_REQUEST_V1, + MIX_ADD_RECIPE_INSTRUCTION_REQUEST, + BOIL_ADD_RECIPE_INSTRUCTION_REQUEST, ), recipeIngredient = listOf( - MILK_ADD_RECIPE_INGREDIENT_REQUEST_V1, - SUGAR_ADD_RECIPE_INGREDIENT_REQUEST_V1, + MILK_ADD_RECIPE_INGREDIENT_REQUEST, + SUGAR_ADD_RECIPE_INGREDIENT_REQUEST, ), - settings = ADD_RECIPE_REQUEST_SETTINGS_V1 + settings = ADD_RECIPE_REQUEST_SETTINGS ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSource.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSource.kt index 5da685b..a11eb3e 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSource.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSource.kt @@ -1,25 +1,25 @@ package gq.kirmanak.mealient.shopping_lists.network -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse interface ShoppingListsDataSource { - suspend fun getAllShoppingLists(): List + suspend fun getAllShoppingLists(): List - suspend fun getShoppingList(id: String): FullShoppingListInfo + suspend fun getShoppingList(id: String): GetShoppingListResponse suspend fun deleteShoppingListItem(id: String) - suspend fun updateShoppingListItem(item: ShoppingListItemInfo) + suspend fun updateShoppingListItem(item: GetShoppingListItemResponse) - suspend fun getFoods(): List + suspend fun getFoods(): List - suspend fun getUnits(): List + suspend fun getUnits(): List - suspend fun addShoppingListItem(item: NewShoppingListItemInfo) + suspend fun addShoppingListItem(item: CreateShoppingListItemRequest) } \ No newline at end of file diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSourceImpl.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSourceImpl.kt index 403b27b..693d7d9 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSourceImpl.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/network/ShoppingListsDataSourceImpl.kt @@ -1,43 +1,41 @@ package gq.kirmanak.mealient.shopping_lists.network -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo -import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1 -import gq.kirmanak.mealient.model_mapper.ModelMapper +import gq.kirmanak.mealient.datasource.MealieDataSource +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse import javax.inject.Inject class ShoppingListsDataSourceImpl @Inject constructor( - private val v1Source: MealieDataSourceV1, - private val modelMapper: ModelMapper, + private val dataSource: MealieDataSource, ) : ShoppingListsDataSource { - override suspend fun getAllShoppingLists(): List { - val response = v1Source.getShoppingLists(1, -1) - return response.items.map { modelMapper.toShoppingListInfo(it) } + override suspend fun getAllShoppingLists(): List { + val response = dataSource.getShoppingLists(1, -1) + return response.items } override suspend fun getShoppingList( id: String - ): FullShoppingListInfo = modelMapper.toFullShoppingListInfo(v1Source.getShoppingList(id)) + ): GetShoppingListResponse = dataSource.getShoppingList(id) override suspend fun deleteShoppingListItem( id: String - ) = v1Source.deleteShoppingListItem(id) + ) = dataSource.deleteShoppingListItem(id) override suspend fun updateShoppingListItem( - item: ShoppingListItemInfo - ) = v1Source.updateShoppingListItem(item) + item: GetShoppingListItemResponse + ) = dataSource.updateShoppingListItem(item) - override suspend fun getFoods(): List = modelMapper.toFoodInfo(v1Source.getFoods()) + override suspend fun getFoods(): List = dataSource.getFoods().items - override suspend fun getUnits(): List = modelMapper.toUnitInfo(v1Source.getUnits()) + override suspend fun getUnits(): List = dataSource.getUnits().items override suspend fun addShoppingListItem( - item: NewShoppingListItemInfo - ) = v1Source.addShoppingListItem(modelMapper.toV1CreateRequest(item)) + item: CreateShoppingListItemRequest + ) = dataSource.addShoppingListItem(item) } diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepo.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepo.kt index 7f64255..c82f03c 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepo.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepo.kt @@ -1,25 +1,25 @@ package gq.kirmanak.mealient.shopping_lists.repo -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse interface ShoppingListsRepo { - suspend fun getShoppingLists(): List + suspend fun getShoppingLists(): List - suspend fun getShoppingList(id: String): FullShoppingListInfo + suspend fun getShoppingList(id: String): GetShoppingListResponse suspend fun deleteShoppingListItem(id: String) - suspend fun updateShoppingListItem(item: ShoppingListItemInfo) + suspend fun updateShoppingListItem(item: GetShoppingListItemResponse) - suspend fun getFoods(): List + suspend fun getFoods(): List - suspend fun getUnits(): List + suspend fun getUnits(): List - suspend fun addShoppingListItem(item: NewShoppingListItemInfo) + suspend fun addShoppingListItem(item: CreateShoppingListItemRequest) } \ No newline at end of file diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepoImpl.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepoImpl.kt index ddd38fc..dc4f23a 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepoImpl.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/repo/ShoppingListsRepoImpl.kt @@ -1,11 +1,11 @@ package gq.kirmanak.mealient.shopping_lists.repo -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.shopping_lists.network.ShoppingListsDataSource import javax.inject.Inject @@ -15,12 +15,12 @@ class ShoppingListsRepoImpl @Inject constructor( private val logger: Logger, ) : ShoppingListsRepo { - override suspend fun getShoppingLists(): List { + override suspend fun getShoppingLists(): List { logger.v { "getShoppingLists() called" } return dataSource.getAllShoppingLists() } - override suspend fun getShoppingList(id: String): FullShoppingListInfo { + override suspend fun getShoppingList(id: String): GetShoppingListResponse { logger.v { "getShoppingListItems() called with: id = $id" } return dataSource.getShoppingList(id) } @@ -30,22 +30,22 @@ class ShoppingListsRepoImpl @Inject constructor( dataSource.deleteShoppingListItem(id) } - override suspend fun updateShoppingListItem(item: ShoppingListItemInfo) { + override suspend fun updateShoppingListItem(item: GetShoppingListItemResponse) { logger.v { "updateShoppingListItem() called with: item = $item" } dataSource.updateShoppingListItem(item) } - override suspend fun getFoods(): List { + override suspend fun getFoods(): List { logger.v { "getFoods() called" } return dataSource.getFoods() } - override suspend fun getUnits(): List { + override suspend fun getUnits(): List { logger.v { "getUnits() called" } return dataSource.getUnits() } - override suspend fun addShoppingListItem(item: NewShoppingListItemInfo) { + override suspend fun addShoppingListItem(item: CreateShoppingListItemRequest) { logger.v { "addShoppingListItem() called with: item = $item" } dataSource.addShoppingListItem(item) } diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListData.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListData.kt index 5710215..b2e636e 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListData.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListData.kt @@ -1,11 +1,11 @@ package gq.kirmanak.mealient.shopping_lists.ui -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse data class ShoppingListData( - val foods: List, - val units: List, - val shoppingList: FullShoppingListInfo, + val foods: List, + val units: List, + val shoppingList: GetShoppingListResponse, ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListEditingState.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListEditingState.kt index a29921e..840dec0 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListEditingState.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListEditingState.kt @@ -1,10 +1,10 @@ package gq.kirmanak.mealient.shopping_lists.ui -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse data class ShoppingListEditingState( val deletedItemIds: Set = emptySet(), val editingItemIds: Set = emptySet(), - val modifiedItems: Map = emptyMap(), + val modifiedItems: Map = emptyMap(), val newItems: List = emptyList(), ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreen.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreen.kt index 8f79f10..3523d38 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreen.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreen.kt @@ -54,14 +54,10 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.ramcosta.composedestinations.annotation.Destination import gq.kirmanak.mealient.AppTheme import gq.kirmanak.mealient.Dimens -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo -import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo -import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemRecipeReferenceInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemRecipeReferenceResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse import gq.kirmanak.mealient.shopping_list.R import gq.kirmanak.mealient.shopping_lists.ui.composables.LazyColumnWithLoadingState import gq.kirmanak.mealient.shopping_lists.util.data @@ -401,21 +397,21 @@ private fun ShoppingListItemEditorFoodRow( } class ShoppingListItemEditorState( - val foods: List, - val units: List, + val foods: List, + val units: List, val position: Int, val listId: String, note: String = "", quantity: String = "1.0", isFood: Boolean = false, - food: FoodInfo? = null, - unit: UnitInfo? = null, + food: GetFoodResponse? = null, + unit: GetUnitResponse? = null, ) { constructor( state: ShoppingListItemState.ExistingItem, - foods: List, - units: List, + foods: List, + units: List, ) : this( foods = foods, units = units, @@ -434,9 +430,9 @@ class ShoppingListItemEditorState( var isFood: Boolean by mutableStateOf(isFood) - var food: FoodInfo? by mutableStateOf(food) + var food: GetFoodResponse? by mutableStateOf(food) - var unit: UnitInfo? by mutableStateOf(unit) + var unit: GetUnitResponse? by mutableStateOf(unit) var foodsExpanded: Boolean by mutableStateOf(false) @@ -624,40 +620,8 @@ fun PreviewShoppingListItemEditing() { } private object PreviewData { - val teaWithMilkRecipe = FullRecipeInfo( - remoteId = "1", - name = "Tea with milk", - recipeYield = "1 serving", - recipeIngredients = listOf( - RecipeIngredientInfo( - note = "Tea bag", - food = "", - unit = "", - quantity = 1.0, - title = "", - ), - RecipeIngredientInfo( - note = "", - food = "Milk", - unit = "ml", - quantity = 500.0, - title = "", - ), - ), - recipeInstructions = listOf( - RecipeInstructionInfo("Boil water"), - RecipeInstructionInfo("Put tea bag in a cup"), - RecipeInstructionInfo("Pour water into the cup"), - RecipeInstructionInfo("Wait for 5 minutes"), - RecipeInstructionInfo("Remove tea bag"), - RecipeInstructionInfo("Add milk"), - ), - settings = RecipeSettingsInfo( - disableAmounts = false - ), - ) - val blackTeaBags = ShoppingListItemInfo( + val blackTeaBags = GetShoppingListItemResponse( id = "1", shoppingListId = "1", checked = false, @@ -668,17 +632,14 @@ private object PreviewData { unit = null, food = null, recipeReferences = listOf( - ShoppingListItemRecipeReferenceInfo( - shoppingListId = "1", - id = "1", + GetShoppingListItemRecipeReferenceResponse( recipeId = "1", recipeQuantity = 1.0, - recipe = teaWithMilkRecipe, ), ), ) - val milk = ShoppingListItemInfo( + val milk = GetShoppingListItemResponse( id = "2", shoppingListId = "1", checked = true, @@ -686,15 +647,12 @@ private object PreviewData { isFood = true, note = "Cold", quantity = 500.0, - unit = UnitInfo("ml", ""), - food = FoodInfo("Milk", ""), + unit = GetUnitResponse("ml", ""), + food = GetFoodResponse("Milk", ""), recipeReferences = listOf( - ShoppingListItemRecipeReferenceInfo( - shoppingListId = "1", - id = "2", + GetShoppingListItemRecipeReferenceResponse( recipeId = "1", recipeQuantity = 500.0, - recipe = teaWithMilkRecipe, ), ), ) diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreenState.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreenState.kt index a39e97f..05b7646 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreenState.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListScreenState.kt @@ -1,22 +1,22 @@ package gq.kirmanak.mealient.shopping_lists.ui -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo +import gq.kirmanak.mealient.datasource.models.GetFoodResponse +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse +import gq.kirmanak.mealient.datasource.models.GetUnitResponse import java.util.UUID internal data class ShoppingListScreenState( val name: String, val listId: String, val items: List, - val foods: List, - val units: List, + val foods: List, + val units: List, ) sealed class ShoppingListItemState { data class ExistingItem( - val item: ShoppingListItemInfo, + val item: GetShoppingListItemResponse, val isEditing: Boolean = false, ) : ShoppingListItemState() diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListViewModel.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListViewModel.kt index 7e29466..662eac9 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListViewModel.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListViewModel.kt @@ -8,8 +8,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.architecture.valueUpdatesOnly -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo +import gq.kirmanak.mealient.datasource.models.CreateShoppingListItemRequest +import gq.kirmanak.mealient.datasource.models.GetShoppingListItemResponse import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo @@ -113,7 +113,7 @@ internal class ShoppingListViewModel @Inject constructor( ): LoadingState { logger.v { "buildLoadingState() called with: loadingState = $loadingState, editingState = $editingState" } return loadingState.map { data -> - val existingItems = data.shoppingList.items + val existingItems = data.shoppingList.listItems .filter { it.id !in editingState.deletedItemIds } .map { ShoppingListItemState.ExistingItem( @@ -208,7 +208,7 @@ internal class ShoppingListViewModel @Inject constructor( updateItemInformation(updatedItem) } - private fun updateItemInformation(updatedItem: ShoppingListItemInfo) { + private fun updateItemInformation(updatedItem: GetShoppingListItemResponse) { logger.v { "updateItemInformation() called with: updatedItem = $updatedItem" } val id = updatedItem.id viewModelScope.launch { @@ -259,14 +259,15 @@ internal class ShoppingListViewModel @Inject constructor( ) { logger.v { "onAddConfirm() called with: state = $state" } val item = state.item - val newItem = NewShoppingListItemInfo( + val newItem = CreateShoppingListItemRequest( shoppingListId = item.listId, note = item.note, quantity = item.quantity.toDouble(), isFood = item.isFood, - unit = item.unit, - food = item.food, + unitId = item.unit?.id, + foodId = item.food?.id, position = item.position, + checked = false, ) viewModelScope.launch { val result = runCatchingExceptCancel { diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsScreen.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsScreen.kt index 64f7d1f..88ec478 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsScreen.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsScreen.kt @@ -22,7 +22,7 @@ import com.ramcosta.composedestinations.annotation.RootNavGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import gq.kirmanak.mealient.AppTheme import gq.kirmanak.mealient.Dimens -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse import gq.kirmanak.mealient.shopping_list.R import gq.kirmanak.mealient.shopping_lists.ui.composables.LazyColumnWithLoadingState import gq.kirmanak.mealient.shopping_lists.ui.destinations.ShoppingListScreenDestination @@ -46,7 +46,7 @@ fun ShoppingListsScreen( lazyColumnContent = { items -> items(items) { shoppingList -> ShoppingListCard( - shoppingListEntity = shoppingList, + shoppingList = shoppingList, onItemClick = { clickedEntity -> val shoppingListId = clickedEntity.id navigator.navigate(ShoppingListScreenDestination(shoppingListId)) @@ -61,21 +61,23 @@ fun ShoppingListsScreen( @Preview private fun PreviewShoppingListCard() { AppTheme { - ShoppingListCard(shoppingListEntity = ShoppingListInfo("1", "Weekend shopping")) + ShoppingListCard( + shoppingList = GetShoppingListsSummaryResponse("1", "Weekend shopping"), + ) } } @Composable private fun ShoppingListCard( - shoppingListEntity: ShoppingListInfo?, + shoppingList: GetShoppingListsSummaryResponse?, modifier: Modifier = Modifier, - onItemClick: (ShoppingListInfo) -> Unit = {}, + onItemClick: (GetShoppingListsSummaryResponse) -> Unit = {}, ) { Card( modifier = modifier .padding(horizontal = Dimens.Medium, vertical = Dimens.Small) .fillMaxWidth() - .clickable { shoppingListEntity?.let { onItemClick(it) } }, + .clickable { shoppingList?.let { onItemClick(it) } }, ) { Row( modifier = Modifier.padding(Dimens.Medium), @@ -87,7 +89,7 @@ private fun ShoppingListCard( modifier = Modifier.height(Dimens.Large), ) Text( - text = shoppingListEntity?.name.orEmpty(), + text = shoppingList?.name.orEmpty(), modifier = Modifier.padding(start = Dimens.Medium), ) } diff --git a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsViewModel.kt b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsViewModel.kt index 6ed10b6..f88f484 100644 --- a/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsViewModel.kt +++ b/features/shopping_lists/src/main/kotlin/gq/kirmanak/mealient/shopping_lists/ui/ShoppingListsViewModel.kt @@ -7,10 +7,14 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import gq.kirmanak.mealient.architecture.valueUpdatesOnly +import gq.kirmanak.mealient.datasource.models.GetShoppingListsSummaryResponse import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsAuthRepo import gq.kirmanak.mealient.shopping_lists.repo.ShoppingListsRepo +import gq.kirmanak.mealient.shopping_lists.util.LoadingHelper import gq.kirmanak.mealient.shopping_lists.util.LoadingHelperFactory +import gq.kirmanak.mealient.shopping_lists.util.LoadingState +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -22,10 +26,11 @@ class ShoppingListsViewModel @Inject constructor( loadingHelperFactory: LoadingHelperFactory, ) : ViewModel() { - private val loadingHelper = loadingHelperFactory.create(viewModelScope) { - shoppingListsRepo.getShoppingLists() - } - val loadingState = loadingHelper.loadingState + private val loadingHelper: LoadingHelper> = + loadingHelperFactory.create(viewModelScope) { shoppingListsRepo.getShoppingLists() } + + val loadingState: StateFlow>> = + loadingHelper.loadingState private var _errorToShowInSnackbar by mutableStateOf(null) val errorToShowInSnackBar: Throwable? get() = _errorToShowInSnackbar diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bf64cc5..a25a180 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,20 +31,16 @@ splashScreen = "1.0.1" lifecycle = "2.6.2" # https://developer.android.com/jetpack/androidx/releases/arch-core coreTesting = "2.2.0" -# https://github.com/square/retrofit/tags -retrofit = "2.9.0" -# https://github.com/JakeWharton/retrofit2-kotlinx-serialization-converter/tags -retrofitKotlinxSerialization = "1.0.0" # https://github.com/Kotlin/kotlinx.serialization/releases kotlinxSerialization = "1.6.0" # https://github.com/square/okhttp/tags -okhttp = "4.11.0" +okhttp = "4.12.0" # https://developer.android.com/jetpack/androidx/releases/paging paging = "3.2.1" # https://developer.android.com/jetpack/androidx/releases/room -room = "2.5.2" +room = "2.6.0" # https://github.com/Kotlin/kotlinx-datetime/releases -kotlinxDatetime = "0.4.1" +kotlinxDatetime = "0.4.0" # https://github.com/bumptech/glide/releases glide = "4.16.0" # https://github.com/androidbroadcast/ViewBindingPropertyDelegate/releases @@ -55,12 +51,10 @@ datastore = "1.0.0" security = "1.0.0" # https://github.com/junit-team/junit4/releases junit = "4.13.2" -# https://developer.android.com/jetpack/androidx/releases/test -junitKtx = "1.1.5" # https://github.com/Kotlin/kotlinx.coroutines/releases coroutines = "1.7.3" # https://github.com/robolectric/robolectric/releases -robolectric = "4.10.3" +robolectric = "4.11" # https://mvnrepository.com/artifact/com.google.truth/truth truth = "1.1.5" # https://mockk.io/ @@ -82,18 +76,21 @@ androidXTestCore = "1.5.0" androidXTestRules = "1.5.0" androidXTestRunner = "1.5.2" androidXTestOrchestrator = "1.4.2" +junitKtx = "1.1.5" # https://mvnrepository.com/artifact/androidx.compose/compose-bom -composeBom = "2023.09.02" +composeBom = "2023.10.01" # https://developer.android.com/jetpack/androidx/releases/compose-kotlin composeKotlinCompilerExtension = "1.5.3" # https://google.github.io/accompanist/ accompanistVersion = "0.32.0" # https://developer.android.com/jetpack/androidx/releases/compose-material -materialCompose = "1.5.3" +materialCompose = "1.5.4" # https://github.com/raamcosta/compose-destinations composeDestinations = "1.9.54" # https://mvnrepository.com/artifact/androidx.hilt/hilt-navigation-compose hiltNavigationCompose = "1.0.0" +# https://github.com/ktorio/ktor/releases +ktor = "2.3.5" [libraries] android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } @@ -171,10 +168,6 @@ androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = " androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "androidXTestRunner" } androidx-test-orchestrator = { group = "androidx.test", name = "orchestrator", version.ref = "androidXTestOrchestrator" } -jakewharton-retrofitSerialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerialization" } - -squareup-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } - squareup-leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary" } okhttp3-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" } @@ -202,6 +195,13 @@ kaspersky-kaspresso = { group = "com.kaspersky.android-components", name = "kasp composeDestinations-core = { group = "io.github.raamcosta.compose-destinations", name = "core", version.ref = "composeDestinations" } composeDestinations-ksp = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "composeDestinations" } +ktor-core = { group = "io.ktor", name = "ktor-client-core", version.ref = "ktor" } +ktor-okhttp = { group = "io.ktor", name = "ktor-client-okhttp", version.ref = "ktor" } +ktor-auth = { group = "io.ktor", name = "ktor-client-auth", version.ref = "ktor" } +ktor-encoding = { group = "io.ktor", name = "ktor-client-encoding", version.ref = "ktor" } +ktor-negotiation = { group = "io.ktor", name = "ktor-client-content-negotiation", version.ref = "ktor" } +ktor-json = { group = "io.ktor", name = "ktor-serialization-kotlinx-json", version.ref = "ktor" } + [plugins] sonarqube = { id = "org.sonarqube", version.ref = "sonarqube" } appsweep = { id = "com.guardsquare.appsweep", version.ref = "appsweep" } diff --git a/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapper.kt b/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapper.kt index 203b508..d85c1e6 100644 --- a/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapper.kt +++ b/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapper.kt @@ -5,138 +5,48 @@ 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.AddRecipeInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient import gq.kirmanak.mealient.datasource.models.AddRecipeIngredientInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction import gq.kirmanak.mealient.datasource.models.AddRecipeInstructionInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeSettings import gq.kirmanak.mealient.datasource.models.AddRecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo -import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo -import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo -import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemRecipeReferenceInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListsInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo -import gq.kirmanak.mealient.datasource.models.VersionInfo -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.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 gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeIngredientV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeInstructionV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeSettingsV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSettingsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemRecipeReferenceFullResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +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.models.UpdateRecipeRequest import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft interface ModelMapper { - fun toRecipeEntity(fullRecipeInfo: FullRecipeInfo): RecipeEntity + fun toRecipeEntity(getRecipeResponse: GetRecipeResponse): RecipeEntity fun toRecipeIngredientEntity( - recipeIngredientInfo: RecipeIngredientInfo, remoteId: String + ingredientResponse: GetRecipeIngredientResponse, remoteId: String ): RecipeIngredientEntity fun toRecipeInstructionEntity( - recipeInstructionInfo: RecipeInstructionInfo, remoteId: String + instructionResponse: GetRecipeInstructionResponse, remoteId: String ): RecipeInstructionEntity fun toRecipeSummaryEntity( - recipeSummaryInfo: RecipeSummaryInfo, isFavorite: Boolean + recipeSummaryInfo: GetRecipeSummaryResponse, isFavorite: Boolean ): RecipeSummaryEntity fun toAddRecipeInfo(addRecipeDraft: AddRecipeDraft): AddRecipeInfo fun toDraft(addRecipeInfo: AddRecipeInfo): AddRecipeDraft - fun toVersionInfo(versionResponseV1: VersionResponseV1): VersionInfo + fun toCreateRequest(addRecipeInfo: AddRecipeInfo): CreateRecipeRequest - fun toFullRecipeInfo(getRecipeResponseV1: GetRecipeResponseV1): FullRecipeInfo + fun toUpdateRequest(addRecipeInfo: AddRecipeInfo): UpdateRecipeRequest - fun toRecipeSettingsInfo(getRecipeSettingsResponseV1: GetRecipeSettingsResponseV1?): RecipeSettingsInfo + fun toSettings(addRecipeSettingsInfo: AddRecipeSettingsInfo): AddRecipeSettings - fun toRecipeIngredientInfo(getRecipeIngredientResponseV1: GetRecipeIngredientResponseV1): RecipeIngredientInfo + fun toIngredient(addRecipeIngredientInfo: AddRecipeIngredientInfo): AddRecipeIngredient - fun toRecipeInstructionInfo(getRecipeInstructionResponseV1: GetRecipeInstructionResponseV1): RecipeInstructionInfo + fun toInstruction(addRecipeInstructionInfo: AddRecipeInstructionInfo): AddRecipeInstruction - fun toV1CreateRequest(addRecipeInfo: AddRecipeInfo): CreateRecipeRequestV1 - - fun toV1UpdateRequest(addRecipeInfo: AddRecipeInfo): UpdateRecipeRequestV1 - - fun toV1Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo): AddRecipeSettingsV1 - - fun toV1Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo): AddRecipeIngredientV1 - - fun toV1Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo): AddRecipeInstructionV1 - - fun toV1Request(parseRecipeURLInfo: ParseRecipeURLInfo): ParseRecipeURLRequestV1 - - fun toFullShoppingListInfo(getShoppingListResponseV1: GetShoppingListResponseV1): FullShoppingListInfo - - fun toShoppingListItemInfo( - getShoppingListItemResponseV1: GetShoppingListItemResponseV1, - recipes: Map> - ): ShoppingListItemInfo - - fun toShoppingListItemRecipeReferenceInfo( - getShoppingListItemRecipeReferenceFullResponseV1: GetShoppingListItemRecipeReferenceFullResponseV1 - ): ShoppingListItemRecipeReferenceInfo - - fun toShoppingListsInfo(getShoppingListsResponseV1: GetShoppingListsResponseV1): ShoppingListsInfo - - fun toShoppingListInfo(getShoppingListsSummaryResponseV1: GetShoppingListsSummaryResponseV1): ShoppingListInfo - - fun toRecipeSummaryInfo(getRecipeSummaryResponseV1: GetRecipeSummaryResponseV1): RecipeSummaryInfo - - fun toRecipeSummaryInfo(getRecipeSummaryResponseV0: GetRecipeSummaryResponseV0): RecipeSummaryInfo - - fun toVersionInfo(versionResponseV0: VersionResponseV0): VersionInfo - - fun toFullRecipeInfo(getRecipeResponseV0: GetRecipeResponseV0): FullRecipeInfo - - fun toRecipeIngredientInfo(getRecipeIngredientResponseV0: GetRecipeIngredientResponseV0): RecipeIngredientInfo - - fun toRecipeInstructionInfo(getRecipeInstructionResponseV0: GetRecipeInstructionResponseV0): RecipeInstructionInfo - - fun toV0Request(addRecipeInfo: AddRecipeInfo): AddRecipeRequestV0 - - fun toV0Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo): AddRecipeSettingsV0 - - fun toV0Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo): AddRecipeIngredientV0 - - fun toV0Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo): AddRecipeInstructionV0 - - fun toV0Request(parseRecipeURLInfo: ParseRecipeURLInfo): ParseRecipeURLRequestV0 - - fun toFoodInfo(getFoodsResponseV1: GetFoodsResponseV1): List - - fun toUnitInfo(getUnitsResponseV1: GetUnitsResponseV1): List - - fun toV1CreateRequest(addRecipeInfo: NewShoppingListItemInfo): CreateShoppingListItemRequestV1 } \ No newline at end of file diff --git a/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapperImpl.kt b/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapperImpl.kt index c55c358..79c84ce 100644 --- a/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapperImpl.kt +++ b/model_mapper/src/main/kotlin/gq/kirmanak/mealient/model_mapper/ModelMapperImpl.kt @@ -5,58 +5,18 @@ 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.AddRecipeInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient import gq.kirmanak.mealient.datasource.models.AddRecipeIngredientInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction import gq.kirmanak.mealient.datasource.models.AddRecipeInstructionInfo +import gq.kirmanak.mealient.datasource.models.AddRecipeSettings import gq.kirmanak.mealient.datasource.models.AddRecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.FoodInfo -import gq.kirmanak.mealient.datasource.models.FullRecipeInfo -import gq.kirmanak.mealient.datasource.models.FullShoppingListInfo -import gq.kirmanak.mealient.datasource.models.NewShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ParseRecipeURLInfo -import gq.kirmanak.mealient.datasource.models.RecipeIngredientInfo -import gq.kirmanak.mealient.datasource.models.RecipeInstructionInfo -import gq.kirmanak.mealient.datasource.models.RecipeSettingsInfo -import gq.kirmanak.mealient.datasource.models.RecipeSummaryInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListItemRecipeReferenceInfo -import gq.kirmanak.mealient.datasource.models.ShoppingListsInfo -import gq.kirmanak.mealient.datasource.models.UnitInfo -import gq.kirmanak.mealient.datasource.models.VersionInfo -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.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 gq.kirmanak.mealient.datasource.v0.models.ParseRecipeURLRequestV0 -import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeIngredientV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeInstructionV1 -import gq.kirmanak.mealient.datasource.v1.models.AddRecipeSettingsV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.CreateShoppingListItemRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.GetFoodResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetFoodsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientFoodResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeIngredientUnitResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeInstructionResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSettingsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetRecipeSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemRecipeReferenceFullResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListItemResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetShoppingListsSummaryResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetUnitResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.GetUnitsResponseV1 -import gq.kirmanak.mealient.datasource.v1.models.ParseRecipeURLRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.UpdateRecipeRequestV1 -import gq.kirmanak.mealient.datasource.v1.models.VersionResponseV1 +import gq.kirmanak.mealient.datasource.models.CreateRecipeRequest +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.models.UpdateRecipeRequest import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft import java.util.UUID import javax.inject.Inject @@ -64,30 +24,36 @@ import javax.inject.Inject class ModelMapperImpl @Inject constructor() : ModelMapper { - override fun toRecipeEntity(fullRecipeInfo: FullRecipeInfo) = RecipeEntity( - remoteId = fullRecipeInfo.remoteId, - recipeYield = fullRecipeInfo.recipeYield, - disableAmounts = fullRecipeInfo.settings.disableAmounts, + override fun toRecipeEntity(getRecipeResponse: GetRecipeResponse) = RecipeEntity( + remoteId = getRecipeResponse.remoteId, + recipeYield = getRecipeResponse.recipeYield, + disableAmounts = getRecipeResponse.settings?.disableAmount ?: true, ) override fun toRecipeIngredientEntity( - recipeIngredientInfo: RecipeIngredientInfo, remoteId: String + ingredientResponse: GetRecipeIngredientResponse, + remoteId: String ) = RecipeIngredientEntity( recipeId = remoteId, - note = recipeIngredientInfo.note, - unit = recipeIngredientInfo.unit, - food = recipeIngredientInfo.food, - quantity = recipeIngredientInfo.quantity, - title = recipeIngredientInfo.title, + note = ingredientResponse.note, + unit = ingredientResponse.unit?.name, + food = ingredientResponse.food?.name, + quantity = ingredientResponse.quantity, + title = ingredientResponse.title, ) override fun toRecipeInstructionEntity( - recipeInstructionInfo: RecipeInstructionInfo, remoteId: String + instructionResponse: GetRecipeInstructionResponse, + remoteId: String ) = RecipeInstructionEntity( - recipeId = remoteId, text = recipeInstructionInfo.text + recipeId = remoteId, + text = instructionResponse.text, ) - override fun toRecipeSummaryEntity(recipeSummaryInfo: RecipeSummaryInfo, isFavorite: Boolean) = + override fun toRecipeSummaryEntity( + recipeSummaryInfo: GetRecipeSummaryResponse, + isFavorite: Boolean + ) = RecipeSummaryEntity( remoteId = recipeSummaryInfo.remoteId, name = recipeSummaryInfo.name, @@ -95,7 +61,7 @@ class ModelMapperImpl @Inject constructor() : ModelMapper { description = recipeSummaryInfo.description, dateAdded = recipeSummaryInfo.dateAdded, dateUpdated = recipeSummaryInfo.dateUpdated, - imageId = recipeSummaryInfo.imageId, + imageId = recipeSummaryInfo.remoteId, isFavorite = isFavorite, ) @@ -122,246 +88,35 @@ class ModelMapperImpl @Inject constructor() : ModelMapper { ) - override fun toVersionInfo(versionResponseV1: VersionResponseV1) = - VersionInfo(versionResponseV1.version) - - override fun toFullRecipeInfo(getRecipeResponseV1: GetRecipeResponseV1) = FullRecipeInfo( - remoteId = getRecipeResponseV1.remoteId, - name = getRecipeResponseV1.name, - recipeYield = getRecipeResponseV1.recipeYield, - recipeIngredients = getRecipeResponseV1.recipeIngredients.map { toRecipeIngredientInfo(it) }, - recipeInstructions = getRecipeResponseV1.recipeInstructions.map { toRecipeInstructionInfo(it) }, - settings = toRecipeSettingsInfo(getRecipeResponseV1.settings), - ) - - override fun toRecipeSettingsInfo(getRecipeSettingsResponseV1: GetRecipeSettingsResponseV1?) = - RecipeSettingsInfo( - disableAmounts = getRecipeSettingsResponseV1?.disableAmount ?: true, - ) - - override fun toRecipeIngredientInfo(getRecipeIngredientResponseV1: GetRecipeIngredientResponseV1) = - RecipeIngredientInfo( - note = getRecipeIngredientResponseV1.note, - unit = getRecipeIngredientResponseV1.unit?.name, - food = getRecipeIngredientResponseV1.food?.name, - quantity = getRecipeIngredientResponseV1.quantity, - title = getRecipeIngredientResponseV1.title, - ) - - override fun toRecipeInstructionInfo(getRecipeInstructionResponseV1: GetRecipeInstructionResponseV1) = - RecipeInstructionInfo( - text = getRecipeInstructionResponseV1.text - ) - - override fun toV1CreateRequest(addRecipeInfo: AddRecipeInfo) = CreateRecipeRequestV1( + override fun toCreateRequest(addRecipeInfo: AddRecipeInfo) = CreateRecipeRequest( name = addRecipeInfo.name, ) - override fun toV1UpdateRequest(addRecipeInfo: AddRecipeInfo) = UpdateRecipeRequestV1( + override fun toUpdateRequest(addRecipeInfo: AddRecipeInfo) = UpdateRecipeRequest( description = addRecipeInfo.description, recipeYield = addRecipeInfo.recipeYield, - recipeIngredient = addRecipeInfo.recipeIngredient.map { toV1Ingredient(it) }, - recipeInstructions = addRecipeInfo.recipeInstructions.map { toV1Instruction(it) }, - settings = toV1Settings(addRecipeInfo.settings), + recipeIngredient = addRecipeInfo.recipeIngredient.map { toIngredient(it) }, + recipeInstructions = addRecipeInfo.recipeInstructions.map { toInstruction(it) }, + settings = toSettings(addRecipeInfo.settings), ) - override fun toV1Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo) = AddRecipeSettingsV1( + override fun toSettings(addRecipeSettingsInfo: AddRecipeSettingsInfo) = AddRecipeSettings( disableComments = addRecipeSettingsInfo.disableComments, public = addRecipeSettingsInfo.public, ) - override fun toV1Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo) = - AddRecipeIngredientV1( + override fun toIngredient(addRecipeIngredientInfo: AddRecipeIngredientInfo) = + AddRecipeIngredient( id = UUID.randomUUID().toString(), note = addRecipeIngredientInfo.note, ) - override fun toV1Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo) = - AddRecipeInstructionV1( + override fun toInstruction(addRecipeInstructionInfo: AddRecipeInstructionInfo) = + AddRecipeInstruction( id = UUID.randomUUID().toString(), text = addRecipeInstructionInfo.text, ingredientReferences = emptyList(), ) - override fun toV1Request(parseRecipeURLInfo: ParseRecipeURLInfo) = ParseRecipeURLRequestV1( - url = parseRecipeURLInfo.url, - includeTags = parseRecipeURLInfo.includeTags, - ) - override fun toFullShoppingListInfo(getShoppingListResponseV1: GetShoppingListResponseV1): FullShoppingListInfo { - val recipes = getShoppingListResponseV1.recipeReferences.groupBy { it.recipeId } - return FullShoppingListInfo( - id = getShoppingListResponseV1.id, - name = getShoppingListResponseV1.name, - items = getShoppingListResponseV1.listItems.map { toShoppingListItemInfo(it, recipes) }, - ) - } - - override fun toShoppingListItemInfo( - getShoppingListItemResponseV1: GetShoppingListItemResponseV1, - recipes: Map> - ): ShoppingListItemInfo = ShoppingListItemInfo( - shoppingListId = getShoppingListItemResponseV1.shoppingListId, - id = getShoppingListItemResponseV1.id, - checked = getShoppingListItemResponseV1.checked, - position = getShoppingListItemResponseV1.position, - isFood = getShoppingListItemResponseV1.isFood, - note = getShoppingListItemResponseV1.note, - quantity = getShoppingListItemResponseV1.quantity, - unit = getShoppingListItemResponseV1.unit?.let { toUnitInfo(it) }, - food = getShoppingListItemResponseV1.food?.let { toFoodInfo(it) }, - recipeReferences = getShoppingListItemResponseV1.recipeReferences.map { it.recipeId } - .mapNotNull { recipes[it] }.flatten().map { toShoppingListItemRecipeReferenceInfo(it) }, - ) - - private fun toUnitInfo(getRecipeIngredientUnitResponseV1: GetRecipeIngredientUnitResponseV1): UnitInfo { - return UnitInfo( - name = getRecipeIngredientUnitResponseV1.name, - id = getRecipeIngredientUnitResponseV1.id, - ) - } - - private fun toFoodInfo(getRecipeIngredientFoodResponseV1: GetRecipeIngredientFoodResponseV1): FoodInfo { - return FoodInfo( - name = getRecipeIngredientFoodResponseV1.name, - id = getRecipeIngredientFoodResponseV1.id, - ) - } - - override fun toShoppingListItemRecipeReferenceInfo( - getShoppingListItemRecipeReferenceFullResponseV1: GetShoppingListItemRecipeReferenceFullResponseV1 - ) = ShoppingListItemRecipeReferenceInfo( - recipeId = getShoppingListItemRecipeReferenceFullResponseV1.recipeId, - recipeQuantity = getShoppingListItemRecipeReferenceFullResponseV1.recipeQuantity, - id = getShoppingListItemRecipeReferenceFullResponseV1.id, - shoppingListId = getShoppingListItemRecipeReferenceFullResponseV1.shoppingListId, - recipe = toFullRecipeInfo(getShoppingListItemRecipeReferenceFullResponseV1.recipe), - ) - - override fun toShoppingListsInfo(getShoppingListsResponseV1: GetShoppingListsResponseV1) = - ShoppingListsInfo( - page = getShoppingListsResponseV1.page, - perPage = getShoppingListsResponseV1.perPage, - totalPages = getShoppingListsResponseV1.totalPages, - totalItems = getShoppingListsResponseV1.total, - items = getShoppingListsResponseV1.items.map { toShoppingListInfo(it) }, - ) - - override fun toShoppingListInfo(getShoppingListsSummaryResponseV1: GetShoppingListsSummaryResponseV1) = - ShoppingListInfo( - name = getShoppingListsSummaryResponseV1.name.orEmpty(), - id = getShoppingListsSummaryResponseV1.id, - ) - - override fun toRecipeSummaryInfo(getRecipeSummaryResponseV1: GetRecipeSummaryResponseV1) = - RecipeSummaryInfo( - remoteId = getRecipeSummaryResponseV1.remoteId, - name = getRecipeSummaryResponseV1.name, - slug = getRecipeSummaryResponseV1.slug, - description = getRecipeSummaryResponseV1.description, - dateAdded = getRecipeSummaryResponseV1.dateAdded, - dateUpdated = getRecipeSummaryResponseV1.dateUpdated, - imageId = getRecipeSummaryResponseV1.remoteId, - ) - - - override fun toRecipeSummaryInfo(getRecipeSummaryResponseV0: GetRecipeSummaryResponseV0) = - RecipeSummaryInfo( - remoteId = getRecipeSummaryResponseV0.remoteId.toString(), - name = getRecipeSummaryResponseV0.name, - slug = getRecipeSummaryResponseV0.slug, - description = getRecipeSummaryResponseV0.description, - dateAdded = getRecipeSummaryResponseV0.dateAdded, - dateUpdated = getRecipeSummaryResponseV0.dateUpdated, - imageId = getRecipeSummaryResponseV0.slug, - ) - - override fun toVersionInfo(versionResponseV0: VersionResponseV0) = - VersionInfo(versionResponseV0.version) - - override fun toFullRecipeInfo(getRecipeResponseV0: GetRecipeResponseV0) = FullRecipeInfo( - remoteId = getRecipeResponseV0.remoteId.toString(), - name = getRecipeResponseV0.name, - recipeYield = getRecipeResponseV0.recipeYield, - recipeIngredients = getRecipeResponseV0.recipeIngredients.map { toRecipeIngredientInfo(it) }, - recipeInstructions = getRecipeResponseV0.recipeInstructions.map { toRecipeInstructionInfo(it) }, - settings = RecipeSettingsInfo(disableAmounts = true) - ) - - override fun toRecipeIngredientInfo(getRecipeIngredientResponseV0: GetRecipeIngredientResponseV0) = - RecipeIngredientInfo( - note = getRecipeIngredientResponseV0.note, - unit = null, - food = null, - quantity = 1.0, - title = null, - ) - - override fun toRecipeInstructionInfo(getRecipeInstructionResponseV0: GetRecipeInstructionResponseV0) = - RecipeInstructionInfo( - text = getRecipeInstructionResponseV0.text - ) - - override fun toV0Request(addRecipeInfo: AddRecipeInfo) = AddRecipeRequestV0( - name = addRecipeInfo.name, - description = addRecipeInfo.description, - recipeYield = addRecipeInfo.recipeYield, - recipeIngredient = addRecipeInfo.recipeIngredient.map { toV0Ingredient(it) }, - recipeInstructions = addRecipeInfo.recipeInstructions.map { toV0Instruction(it) }, - settings = toV0Settings(addRecipeInfo.settings), - ) - - override fun toV0Settings(addRecipeSettingsInfo: AddRecipeSettingsInfo) = AddRecipeSettingsV0( - disableComments = addRecipeSettingsInfo.disableComments, - public = addRecipeSettingsInfo.public, - ) - - override fun toV0Ingredient(addRecipeIngredientInfo: AddRecipeIngredientInfo) = - AddRecipeIngredientV0( - note = addRecipeIngredientInfo.note, - ) - - override fun toV0Instruction(addRecipeInstructionInfo: AddRecipeInstructionInfo) = - AddRecipeInstructionV0( - text = addRecipeInstructionInfo.text, - ) - - override fun toV0Request(parseRecipeURLInfo: ParseRecipeURLInfo) = ParseRecipeURLRequestV0( - url = parseRecipeURLInfo.url, - ) - - override fun toFoodInfo(getFoodsResponseV1: GetFoodsResponseV1): List { - return getFoodsResponseV1.items.map { toFoodInfo(it) } - } - - private fun toFoodInfo(getFoodResponseV1: GetFoodResponseV1): FoodInfo { - return FoodInfo( - name = getFoodResponseV1.name, - id = getFoodResponseV1.id, - ) - } - - override fun toUnitInfo(getUnitsResponseV1: GetUnitsResponseV1): List { - return getUnitsResponseV1.items.map { toUnitInfo(it) } - } - - private fun toUnitInfo(getUnitResponseV1: GetUnitResponseV1): UnitInfo { - return UnitInfo( - name = getUnitResponseV1.name, - id = getUnitResponseV1.id, - ) - } - - override fun toV1CreateRequest(addRecipeInfo: NewShoppingListItemInfo): CreateShoppingListItemRequestV1 { - return CreateShoppingListItemRequestV1( - shoppingListId = addRecipeInfo.shoppingListId, - checked = false, - position = addRecipeInfo.position, - isFood = addRecipeInfo.isFood, - note = addRecipeInfo.note, - quantity = addRecipeInfo.quantity, - foodId = addRecipeInfo.food?.id, - unitId = addRecipeInfo.unit?.id, - ) - } } \ No newline at end of file diff --git a/model_mapper/src/test/kotlin/gq/kirmanak/mealient/model_mapper/ModelMappingsTest.kt b/model_mapper/src/test/kotlin/gq/kirmanak/mealient/model_mapper/ModelMappingsTest.kt index a6713f0..25ff6e3 100644 --- a/model_mapper/src/test/kotlin/gq/kirmanak/mealient/model_mapper/ModelMappingsTest.kt +++ b/model_mapper/src/test/kotlin/gq/kirmanak/mealient/model_mapper/ModelMappingsTest.kt @@ -4,31 +4,12 @@ import com.google.common.truth.Truth.assertThat import gq.kirmanak.mealient.database.CAKE_RECIPE_ENTITY import gq.kirmanak.mealient.database.CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY import gq.kirmanak.mealient.database.MIX_CAKE_RECIPE_INSTRUCTION_ENTITY -import gq.kirmanak.mealient.database.PORRIDGE_RECIPE_SUMMARY_ENTITY -import gq.kirmanak.mealient.datasource_test.CAKE_FULL_RECIPE_INFO -import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_INFO -import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.MILK_RECIPE_INGREDIENT_RESPONSE_V1 -import gq.kirmanak.mealient.datasource_test.MIX_INSTRUCTION -import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_INFO -import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_RESPONSE_V1 +import gq.kirmanak.mealient.datasource_test.CAKE_RECIPE_RESPONSE +import gq.kirmanak.mealient.datasource_test.MIX_RECIPE_INSTRUCTION_RESPONSE import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_INFO -import gq.kirmanak.mealient.datasource_test.PORRIDGE_ADD_RECIPE_REQUEST_V0 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_CREATE_RECIPE_REQUEST_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_FULL_RECIPE_INFO -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_RESPONSE_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1 -import gq.kirmanak.mealient.datasource_test.PORRIDGE_UPDATE_RECIPE_REQUEST_V1 -import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V0 -import gq.kirmanak.mealient.datasource_test.RECIPE_SUMMARY_PORRIDGE_V1 -import gq.kirmanak.mealient.datasource_test.SUGAR_INGREDIENT -import gq.kirmanak.mealient.datasource_test.VERSION_INFO_V0 -import gq.kirmanak.mealient.datasource_test.VERSION_INFO_V1 -import gq.kirmanak.mealient.datasource_test.VERSION_RESPONSE_V0 -import gq.kirmanak.mealient.datasource_test.VERSION_RESPONSE_V1 +import gq.kirmanak.mealient.datasource_test.PORRIDGE_CREATE_RECIPE_REQUEST +import gq.kirmanak.mealient.datasource_test.PORRIDGE_UPDATE_RECIPE_REQUEST +import gq.kirmanak.mealient.datasource_test.SUGAR_RECIPE_INGREDIENT_RESPONSE import gq.kirmanak.mealient.datastore_test.PORRIDGE_RECIPE_DRAFT import gq.kirmanak.mealient.test.BaseUnitTest import org.junit.Before @@ -58,100 +39,30 @@ class ModelMappingsTest : BaseUnitTest() { @Test fun `when full recipe info to entity expect correct entity`() { - assertThat(subject.toRecipeEntity(CAKE_FULL_RECIPE_INFO)).isEqualTo(CAKE_RECIPE_ENTITY) + assertThat(subject.toRecipeEntity(CAKE_RECIPE_RESPONSE)).isEqualTo(CAKE_RECIPE_ENTITY) } @Test fun `when ingredient info to entity expect correct entity`() { - val actual = subject.toRecipeIngredientEntity(SUGAR_INGREDIENT, "1") + val actual = subject.toRecipeIngredientEntity(SUGAR_RECIPE_INGREDIENT_RESPONSE, "1") assertThat(actual).isEqualTo(CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY) } @Test fun `when instruction info to entity expect correct entity`() { - val actual = subject.toRecipeInstructionEntity(MIX_INSTRUCTION, "1") + val actual = subject.toRecipeInstructionEntity(MIX_RECIPE_INSTRUCTION_RESPONSE, "1") assertThat(actual).isEqualTo(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY) } @Test - fun `when summary v0 to info expect correct info`() { - val actual = subject.toRecipeSummaryInfo(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0) - assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V0) + fun `when add recipe info to create request expect correct request`() { + val actual = subject.toCreateRequest(PORRIDGE_ADD_RECIPE_INFO) + assertThat(actual).isEqualTo(PORRIDGE_CREATE_RECIPE_REQUEST) } @Test - fun `when summary v1 to info expect correct info`() { - val actual = subject.toRecipeSummaryInfo(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1) - assertThat(actual).isEqualTo(RECIPE_SUMMARY_PORRIDGE_V1) - } - - @Test - fun `when summary info to entity expect correct entity`() { - val actual = subject.toRecipeSummaryEntity(RECIPE_SUMMARY_PORRIDGE_V0, isFavorite = false) - assertThat(actual).isEqualTo(PORRIDGE_RECIPE_SUMMARY_ENTITY) - } - - @Test - fun `when version response v0 to info expect correct info`() { - assertThat(subject.toVersionInfo(VERSION_RESPONSE_V0)).isEqualTo(VERSION_INFO_V0) - } - - @Test - fun `when version response v1 to info expect correct info`() { - assertThat(subject.toVersionInfo(VERSION_RESPONSE_V1)).isEqualTo(VERSION_INFO_V1) - } - - @Test - fun `when recipe ingredient response v0 to info expect correct info`() { - val actual = subject.toRecipeIngredientInfo(MILK_RECIPE_INGREDIENT_RESPONSE_V0) - assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO) - } - - @Test - fun `when recipe ingredient response v1 to info expect correct info`() { - val actual = subject.toRecipeIngredientInfo(MILK_RECIPE_INGREDIENT_RESPONSE_V1) - assertThat(actual).isEqualTo(MILK_RECIPE_INGREDIENT_INFO) - } - - @Test - fun `when recipe instruction response v0 to info expect correct info`() { - val actual = subject.toRecipeInstructionInfo(MIX_RECIPE_INSTRUCTION_RESPONSE_V0) - assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO) - } - - @Test - fun `when recipe instruction response v1 to info expect correct info`() { - val actual = subject.toRecipeInstructionInfo(MIX_RECIPE_INSTRUCTION_RESPONSE_V1) - assertThat(actual).isEqualTo(MIX_RECIPE_INSTRUCTION_INFO) - } - - @Test - fun `when recipe response v0 to info expect correct info`() { - val actual = subject.toFullRecipeInfo(PORRIDGE_RECIPE_RESPONSE_V0) - assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) - } - - @Test - fun `when recipe response v1 to info expect correct info`() { - val actual = subject.toFullRecipeInfo(PORRIDGE_RECIPE_RESPONSE_V1) - assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) - } - - @Test - fun `when add recipe info to request v0 expect correct request`() { - val actual = subject.toV0Request(PORRIDGE_ADD_RECIPE_INFO) - assertThat(actual).isEqualTo(PORRIDGE_ADD_RECIPE_REQUEST_V0) - } - - @Test - fun `when add recipe info to create request v1 expect correct request`() { - val actual = subject.toV1CreateRequest(PORRIDGE_ADD_RECIPE_INFO) - assertThat(actual).isEqualTo(PORRIDGE_CREATE_RECIPE_REQUEST_V1) - } - - @Test - fun `when add recipe info to update request v1 expect correct request`() { - val actual = subject.toV1UpdateRequest(PORRIDGE_ADD_RECIPE_INFO) - assertThat(actual).isEqualTo(PORRIDGE_UPDATE_RECIPE_REQUEST_V1) + fun `when add recipe info to update request expect correct request`() { + val actual = subject.toUpdateRequest(PORRIDGE_ADD_RECIPE_INFO) + assertThat(actual).isEqualTo(PORRIDGE_UPDATE_RECIPE_REQUEST) } } \ No newline at end of file diff --git a/ui/src/main/kotlin/gq/kirmanak/mealient/ui/ActivityUiState.kt b/ui/src/main/kotlin/gq/kirmanak/mealient/ui/ActivityUiState.kt index ea10c97..52c22a1 100644 --- a/ui/src/main/kotlin/gq/kirmanak/mealient/ui/ActivityUiState.kt +++ b/ui/src/main/kotlin/gq/kirmanak/mealient/ui/ActivityUiState.kt @@ -5,7 +5,6 @@ data class ActivityUiState( val navigationVisible: Boolean = false, val searchVisible: Boolean = false, val checkedMenuItem: CheckableMenuItem? = null, - val v1MenuItemsVisible: Boolean = false, ) { val canShowLogin: Boolean get() = !isAuthorized