@@ -1,7 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.add
|
package gq.kirmanak.mealient.data.add
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
|
||||||
|
|
||||||
interface AddRecipeDataSource {
|
interface AddRecipeDataSource {
|
||||||
suspend fun addRecipe(recipe: AddRecipeRequest): String
|
|
||||||
|
suspend fun addRecipe(recipe: AddRecipeInfo): String
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package gq.kirmanak.mealient.data.add
|
||||||
|
|
||||||
|
data class AddRecipeInfo(
|
||||||
|
val name: String,
|
||||||
|
val description: String,
|
||||||
|
val recipeYield: String,
|
||||||
|
val recipeIngredient: List<AddRecipeIngredientInfo>,
|
||||||
|
val recipeInstructions: List<AddRecipeInstructionInfo>,
|
||||||
|
val settings: AddRecipeSettingsInfo,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AddRecipeSettingsInfo(
|
||||||
|
val disableComments: Boolean,
|
||||||
|
val public: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AddRecipeIngredientInfo(
|
||||||
|
val note: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AddRecipeInstructionInfo(
|
||||||
|
val text: String,
|
||||||
|
)
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
package gq.kirmanak.mealient.data.add
|
package gq.kirmanak.mealient.data.add
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface AddRecipeRepo {
|
interface AddRecipeRepo {
|
||||||
|
|
||||||
val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
val addRecipeRequestFlow: Flow<AddRecipeInfo>
|
||||||
|
|
||||||
suspend fun preserve(recipe: AddRecipeRequest)
|
suspend fun preserve(recipe: AddRecipeInfo)
|
||||||
|
|
||||||
suspend fun clear()
|
suspend fun clear()
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.data.add.impl
|
package gq.kirmanak.mealient.data.add.impl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
|
||||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage
|
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage
|
||||||
import gq.kirmanak.mealient.extensions.toAddRecipeRequest
|
import gq.kirmanak.mealient.extensions.toAddRecipeInfo
|
||||||
import gq.kirmanak.mealient.extensions.toDraft
|
import gq.kirmanak.mealient.extensions.toDraft
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -20,10 +20,10 @@ class AddRecipeRepoImpl @Inject constructor(
|
|||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : AddRecipeRepo {
|
) : AddRecipeRepo {
|
||||||
|
|
||||||
override val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
override val addRecipeRequestFlow: Flow<AddRecipeInfo>
|
||||||
get() = addRecipeStorage.updates.map { it.toAddRecipeRequest() }
|
get() = addRecipeStorage.updates.map { it.toAddRecipeInfo() }
|
||||||
|
|
||||||
override suspend fun preserve(recipe: AddRecipeRequest) {
|
override suspend fun preserve(recipe: AddRecipeInfo) {
|
||||||
logger.v { "preserveRecipe() called with: recipe = $recipe" }
|
logger.v { "preserveRecipe() called with: recipe = $recipe" }
|
||||||
addRecipeStorage.save(recipe.toDraft())
|
addRecipeStorage.save(recipe.toDraft())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ interface AuthDataSource {
|
|||||||
/**
|
/**
|
||||||
* Tries to acquire authentication token using the provided credentials
|
* Tries to acquire authentication token using the provided credentials
|
||||||
*/
|
*/
|
||||||
suspend fun authenticate(username: String, password: String, baseUrl: String): String
|
suspend fun authenticate(username: String, password: String): String
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,28 @@
|
|||||||
package gq.kirmanak.mealient.data.auth.impl
|
package gq.kirmanak.mealient.data.auth.impl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
|
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class AuthDataSourceImpl @Inject constructor(
|
class AuthDataSourceImpl @Inject constructor(
|
||||||
private val mealieDataSource: MealieDataSource,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
|
private val v0Source: MealieDataSourceV0,
|
||||||
|
private val v1Source: MealieDataSourceV1,
|
||||||
) : AuthDataSource {
|
) : AuthDataSource {
|
||||||
|
|
||||||
override suspend fun authenticate(username: String, password: String, baseUrl: String): String =
|
override suspend fun authenticate(
|
||||||
mealieDataSource.authenticate(baseUrl, username, password)
|
username: String,
|
||||||
|
password: String,
|
||||||
|
): String {
|
||||||
|
val baseUrl = serverInfoRepo.requireUrl()
|
||||||
|
return when (serverInfoRepo.getVersion()) {
|
||||||
|
ServerVersion.V0 -> v0Source.authenticate(baseUrl, username, password)
|
||||||
|
ServerVersion.V1 -> v1Source.authenticate(baseUrl, username, password)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl
|
|||||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -15,7 +14,6 @@ import javax.inject.Singleton
|
|||||||
class AuthRepoImpl @Inject constructor(
|
class AuthRepoImpl @Inject constructor(
|
||||||
private val authStorage: AuthStorage,
|
private val authStorage: AuthStorage,
|
||||||
private val authDataSource: AuthDataSource,
|
private val authDataSource: AuthDataSource,
|
||||||
private val baseURLStorage: BaseURLStorage,
|
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : AuthRepo {
|
) : AuthRepo {
|
||||||
|
|
||||||
@@ -24,9 +22,9 @@ class AuthRepoImpl @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun authenticate(email: String, password: String) {
|
override suspend fun authenticate(email: String, password: String) {
|
||||||
logger.v { "authenticate() called with: email = $email, password = $password" }
|
logger.v { "authenticate() called with: email = $email, password = $password" }
|
||||||
authDataSource.authenticate(email, password, baseURLStorage.requireBaseURL())
|
val token = authDataSource.authenticate(email, password)
|
||||||
.let { AUTH_HEADER_FORMAT.format(it) }
|
val header = AUTH_HEADER_FORMAT.format(token)
|
||||||
.let { authStorage.setAuthHeader(it) }
|
authStorage.setAuthHeader(header)
|
||||||
authStorage.setEmail(email)
|
authStorage.setEmail(email)
|
||||||
authStorage.setPassword(password)
|
authStorage.setPassword(password)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.data.baseurl
|
|
||||||
|
|
||||||
interface BaseURLStorage {
|
|
||||||
|
|
||||||
suspend fun getBaseURL(): String?
|
|
||||||
|
|
||||||
suspend fun requireBaseURL(): String
|
|
||||||
|
|
||||||
suspend fun storeBaseURL(baseURL: String)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
interface ServerInfoRepo {
|
||||||
|
|
||||||
|
suspend fun getUrl(): String?
|
||||||
|
|
||||||
|
suspend fun requireUrl(): String
|
||||||
|
|
||||||
|
suspend fun getVersion(): ServerVersion
|
||||||
|
|
||||||
|
suspend fun storeBaseURL(baseURL: String, version: String)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ServerInfoRepoImpl @Inject constructor(
|
||||||
|
private val serverInfoStorage: ServerInfoStorage,
|
||||||
|
private val versionDataSource: VersionDataSource,
|
||||||
|
private val logger: Logger,
|
||||||
|
) : ServerInfoRepo {
|
||||||
|
|
||||||
|
override suspend fun getUrl(): String? {
|
||||||
|
val result = serverInfoStorage.getBaseURL()
|
||||||
|
logger.v { "getUrl() returned: $result" }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun requireUrl(): String {
|
||||||
|
val result = checkNotNull(getUrl()) { "Server URL was null when it was required" }
|
||||||
|
logger.v { "requireUrl() returned: $result" }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getVersion(): ServerVersion {
|
||||||
|
var version = serverInfoStorage.getServerVersion()
|
||||||
|
val serverVersion = if (version == null) {
|
||||||
|
logger.d { "getVersion: version is null, requesting" }
|
||||||
|
version = versionDataSource.getVersionInfo(requireUrl()).version
|
||||||
|
val result = determineServerVersion(version)
|
||||||
|
serverInfoStorage.storeServerVersion(version)
|
||||||
|
result
|
||||||
|
} else {
|
||||||
|
determineServerVersion(version)
|
||||||
|
}
|
||||||
|
logger.v { "getVersion() returned: $serverVersion from $version" }
|
||||||
|
return serverVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineServerVersion(version: String): ServerVersion = when {
|
||||||
|
version.startsWith("v0") -> ServerVersion.V0
|
||||||
|
version.startsWith("v1") -> ServerVersion.V1
|
||||||
|
else -> throw NetworkError.NotMealie(IllegalStateException("Server version is unknown: $version"))
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeBaseURL(baseURL: String, version: String) {
|
||||||
|
logger.v { "storeBaseURL() called with: baseURL = $baseURL, version = $version" }
|
||||||
|
serverInfoStorage.storeBaseURL(baseURL, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
interface ServerInfoStorage {
|
||||||
|
|
||||||
|
suspend fun getBaseURL(): String?
|
||||||
|
|
||||||
|
suspend fun storeBaseURL(baseURL: String, version: String)
|
||||||
|
|
||||||
|
suspend fun storeServerVersion(version: String)
|
||||||
|
|
||||||
|
suspend fun getServerVersion(): String?
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
enum class ServerVersion { V0, V1 }
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
|
import gq.kirmanak.mealient.extensions.toVersionInfo
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class VersionDataSourceImpl @Inject constructor(
|
||||||
|
private val v0Source: MealieDataSourceV0,
|
||||||
|
private val v1Source: MealieDataSourceV1,
|
||||||
|
) : VersionDataSource {
|
||||||
|
|
||||||
|
override suspend fun getVersionInfo(baseUrl: String): VersionInfo {
|
||||||
|
val responses = coroutineScope {
|
||||||
|
val v0Deferred = async {
|
||||||
|
runCatchingExceptCancel { v0Source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||||
|
}
|
||||||
|
val v1Deferred = async {
|
||||||
|
runCatchingExceptCancel { v1Source.getVersionInfo(baseUrl).toVersionInfo() }
|
||||||
|
}
|
||||||
|
listOf(v0Deferred, v1Deferred).awaitAll()
|
||||||
|
}
|
||||||
|
val firstSuccess = responses.firstNotNullOfOrNull { it.getOrNull() }
|
||||||
|
if (firstSuccess == null) {
|
||||||
|
throw responses.firstNotNullOf { it.exceptionOrNull() }
|
||||||
|
} else {
|
||||||
|
return firstSuccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package gq.kirmanak.mealient.data.baseurl
|
package gq.kirmanak.mealient.data.baseurl
|
||||||
|
|
||||||
data class VersionInfo(
|
data class VersionInfo(
|
||||||
val production: Boolean,
|
|
||||||
val version: String,
|
val version: String,
|
||||||
val demoStatus: Boolean,
|
|
||||||
)
|
)
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.data.baseurl.impl
|
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.Preferences
|
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
|
||||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class BaseURLStorageImpl @Inject constructor(
|
|
||||||
private val preferencesStorage: PreferencesStorage,
|
|
||||||
) : BaseURLStorage {
|
|
||||||
|
|
||||||
private val baseUrlKey: Preferences.Key<String>
|
|
||||||
get() = preferencesStorage.baseUrlKey
|
|
||||||
|
|
||||||
override suspend fun getBaseURL(): String? = preferencesStorage.getValue(baseUrlKey)
|
|
||||||
|
|
||||||
override suspend fun requireBaseURL(): String = checkNotNull(getBaseURL()) {
|
|
||||||
"Base URL was null when it was required"
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun storeBaseURL(baseURL: String) {
|
|
||||||
preferencesStorage.storeValues(Pair(baseUrlKey, baseURL))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class ServerInfoStorageImpl @Inject constructor(
|
||||||
|
private val preferencesStorage: PreferencesStorage,
|
||||||
|
) : ServerInfoStorage {
|
||||||
|
|
||||||
|
private val baseUrlKey: Preferences.Key<String>
|
||||||
|
get() = preferencesStorage.baseUrlKey
|
||||||
|
|
||||||
|
private val serverVersionKey: Preferences.Key<String>
|
||||||
|
get() = preferencesStorage.serverVersionKey
|
||||||
|
|
||||||
|
override suspend fun getBaseURL(): String? = getValue(baseUrlKey)
|
||||||
|
|
||||||
|
override suspend fun storeBaseURL(baseURL: String, version: String) {
|
||||||
|
preferencesStorage.storeValues(
|
||||||
|
Pair(baseUrlKey, baseURL),
|
||||||
|
Pair(serverVersionKey, version),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getServerVersion(): String? = getValue(serverVersionKey)
|
||||||
|
|
||||||
|
override suspend fun storeServerVersion(version: String) {
|
||||||
|
preferencesStorage.storeValues(Pair(serverVersionKey, version))
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> getValue(key: Preferences.Key<T>): T? = preferencesStorage.getValue(key)
|
||||||
|
}
|
||||||
@@ -1,49 +1,80 @@
|
|||||||
package gq.kirmanak.mealient.data.network
|
package gq.kirmanak.mealient.data.network
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
import gq.kirmanak.mealient.extensions.toVersionInfo
|
import gq.kirmanak.mealient.extensions.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class MealieDataSourceWrapper @Inject constructor(
|
class MealieDataSourceWrapper @Inject constructor(
|
||||||
private val baseURLStorage: BaseURLStorage,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
private val authRepo: AuthRepo,
|
private val authRepo: AuthRepo,
|
||||||
private val mealieDataSource: MealieDataSource,
|
private val v0Source: MealieDataSourceV0,
|
||||||
) : AddRecipeDataSource, RecipeDataSource, VersionDataSource {
|
private val v1Source: MealieDataSourceV1,
|
||||||
|
) : AddRecipeDataSource, RecipeDataSource {
|
||||||
|
|
||||||
override suspend fun addRecipe(recipe: AddRecipeRequest): String =
|
override suspend fun addRecipe(
|
||||||
withAuthHeader { token -> addRecipe(getUrl(), token, recipe) }
|
recipe: AddRecipeInfo,
|
||||||
|
): String = makeCall { token, url, version ->
|
||||||
|
when (version) {
|
||||||
|
ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request())
|
||||||
|
ServerVersion.V1 -> {
|
||||||
|
val slug = v1Source.createRecipe(url, token, recipe.toV1CreateRequest())
|
||||||
|
v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest())
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getVersionInfo(baseUrl: String): VersionInfo =
|
override suspend fun requestRecipes(
|
||||||
mealieDataSource.getVersionInfo(baseUrl).toVersionInfo()
|
start: Int,
|
||||||
|
limit: Int,
|
||||||
|
): List<RecipeSummaryInfo> = makeCall { token, url, version ->
|
||||||
|
when (version) {
|
||||||
|
ServerVersion.V0 -> {
|
||||||
|
v0Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() }
|
||||||
|
}
|
||||||
|
ServerVersion.V1 -> {
|
||||||
|
// Imagine start is 30 and limit is 15. It means that we already have page 1 and 2, now we need page 3
|
||||||
|
val page = start / limit + 1
|
||||||
|
v1Source.requestRecipes(url, token, page, limit).map { it.toRecipeSummaryInfo() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun requestRecipes(start: Int, limit: Int): List<GetRecipeSummaryResponse> =
|
override suspend fun requestRecipeInfo(
|
||||||
withAuthHeader { token -> requestRecipes(getUrl(), token, start, limit) }
|
slug: String,
|
||||||
|
): FullRecipeInfo = makeCall { token, url, version ->
|
||||||
|
when (version) {
|
||||||
|
ServerVersion.V0 -> v0Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||||
|
ServerVersion.V1 -> v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse =
|
private suspend inline fun <T> makeCall(block: (String?, String, ServerVersion) -> T): T {
|
||||||
withAuthHeader { token -> requestRecipeInfo(getUrl(), token, slug) }
|
val authHeader = authRepo.getAuthHeader()
|
||||||
|
val url = serverInfoRepo.requireUrl()
|
||||||
private suspend fun getUrl() = baseURLStorage.requireBaseURL()
|
val version = serverInfoRepo.getVersion()
|
||||||
|
return runCatchingExceptCancel { block(authHeader, url, version) }.getOrElse {
|
||||||
private suspend inline fun <T> withAuthHeader(block: MealieDataSource.(String?) -> T): T =
|
|
||||||
mealieDataSource.runCatching { block(authRepo.getAuthHeader()) }.getOrElse {
|
|
||||||
if (it is NetworkError.Unauthorized) {
|
if (it is NetworkError.Unauthorized) {
|
||||||
authRepo.invalidateAuthHeader()
|
authRepo.invalidateAuthHeader()
|
||||||
// Trying again with new authentication header
|
// Trying again with new authentication header
|
||||||
mealieDataSource.block(authRepo.getAuthHeader())
|
val newHeader = authRepo.getAuthHeader()
|
||||||
|
if (newHeader == authHeader) throw it else block(newHeader, url, version)
|
||||||
} else {
|
} else {
|
||||||
throw it
|
throw it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes
|
package gq.kirmanak.mealient.data.recipes
|
||||||
|
|
||||||
import androidx.paging.Pager
|
import androidx.paging.Pager
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
|
||||||
interface RecipeRepo {
|
interface RecipeRepo {
|
||||||
@@ -9,5 +9,5 @@ interface RecipeRepo {
|
|||||||
|
|
||||||
suspend fun clearLocalData()
|
suspend fun clearLocalData()
|
||||||
|
|
||||||
suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo
|
suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.db
|
package gq.kirmanak.mealient.data.recipes.db
|
||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
|
||||||
|
|
||||||
interface RecipeStorage {
|
interface RecipeStorage {
|
||||||
suspend fun saveRecipes(recipes: List<GetRecipeSummaryResponse>)
|
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
||||||
|
|
||||||
fun queryRecipes(): PagingSource<Int, RecipeSummaryEntity>
|
fun queryRecipes(): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
suspend fun refreshAll(recipes: List<GetRecipeSummaryResponse>)
|
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
||||||
|
|
||||||
suspend fun clearAllLocalData()
|
suspend fun clearAllLocalData()
|
||||||
|
|
||||||
suspend fun saveRecipeInfo(recipe: GetRecipeResponse)
|
suspend fun saveRecipeInfo(recipe: FullRecipeInfo)
|
||||||
|
|
||||||
suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo
|
suspend fun queryRecipeInfo(recipeId: String): FullRecipeEntity
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,12 @@ package gq.kirmanak.mealient.data.recipes.db
|
|||||||
|
|
||||||
import androidx.paging.PagingSource
|
import androidx.paging.PagingSource
|
||||||
import androidx.room.withTransaction
|
import androidx.room.withTransaction
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
import gq.kirmanak.mealient.database.AppDb
|
import gq.kirmanak.mealient.database.AppDb
|
||||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
|
||||||
import gq.kirmanak.mealient.extensions.recipeEntity
|
import gq.kirmanak.mealient.extensions.recipeEntity
|
||||||
import gq.kirmanak.mealient.extensions.toRecipeEntity
|
import gq.kirmanak.mealient.extensions.toRecipeEntity
|
||||||
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
||||||
@@ -23,71 +24,14 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
||||||
|
|
||||||
override suspend fun saveRecipes(
|
override suspend fun saveRecipes(
|
||||||
recipes: List<GetRecipeSummaryResponse>
|
recipes: List<RecipeSummaryInfo>
|
||||||
) = db.withTransaction {
|
) = db.withTransaction {
|
||||||
logger.v { "saveRecipes() called with $recipes" }
|
logger.v { "saveRecipes() called with $recipes" }
|
||||||
|
|
||||||
val tagEntities = mutableSetOf<TagEntity>()
|
|
||||||
tagEntities.addAll(recipeDao.queryAllTags())
|
|
||||||
|
|
||||||
val categoryEntities = mutableSetOf<CategoryEntity>()
|
|
||||||
categoryEntities.addAll(recipeDao.queryAllCategories())
|
|
||||||
|
|
||||||
val tagRecipeEntities = mutableSetOf<TagRecipeEntity>()
|
|
||||||
val categoryRecipeEntities = mutableSetOf<CategoryRecipeEntity>()
|
|
||||||
|
|
||||||
for (recipe in recipes) {
|
for (recipe in recipes) {
|
||||||
val recipeSummaryEntity = recipe.recipeEntity()
|
val recipeSummaryEntity = recipe.recipeEntity()
|
||||||
recipeDao.insertRecipe(recipeSummaryEntity)
|
recipeDao.insertRecipe(recipeSummaryEntity)
|
||||||
|
|
||||||
for (tag in recipe.tags) {
|
|
||||||
val tagId = getIdOrInsert(tagEntities, tag)
|
|
||||||
tagRecipeEntities += TagRecipeEntity(tagId, recipeSummaryEntity.remoteId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (category in recipe.recipeCategories) {
|
|
||||||
val categoryId = getOrInsert(categoryEntities, category)
|
|
||||||
categoryRecipeEntities += CategoryRecipeEntity(
|
|
||||||
categoryId,
|
|
||||||
recipeSummaryEntity.remoteId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeDao.insertTagRecipeEntities(tagRecipeEntities)
|
|
||||||
recipeDao.insertCategoryRecipeEntities(categoryRecipeEntities)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getOrInsert(
|
|
||||||
categoryEntities: MutableSet<CategoryEntity>,
|
|
||||||
category: String
|
|
||||||
): Long {
|
|
||||||
val existingCategory = categoryEntities.find { it.name == category }
|
|
||||||
val categoryId = if (existingCategory == null) {
|
|
||||||
val categoryEntity = CategoryEntity(name = category)
|
|
||||||
val newId = recipeDao.insertCategory(categoryEntity)
|
|
||||||
categoryEntities.add(categoryEntity.copy(localId = newId))
|
|
||||||
newId
|
|
||||||
} else {
|
|
||||||
existingCategory.localId
|
|
||||||
}
|
|
||||||
return categoryId
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getIdOrInsert(
|
|
||||||
tagEntities: MutableSet<TagEntity>,
|
|
||||||
tag: String
|
|
||||||
): Long {
|
|
||||||
val existingTag = tagEntities.find { it.name == tag }
|
|
||||||
val tagId = if (existingTag == null) {
|
|
||||||
val tagEntity = TagEntity(name = tag)
|
|
||||||
val newId = recipeDao.insertTag(tagEntity)
|
|
||||||
tagEntities.add(tagEntity.copy(localId = newId))
|
|
||||||
newId
|
|
||||||
} else {
|
|
||||||
existingTag.localId
|
|
||||||
}
|
|
||||||
return tagId
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -96,7 +40,7 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
return recipeDao.queryRecipesByPages()
|
return recipeDao.queryRecipesByPages()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun refreshAll(recipes: List<GetRecipeSummaryResponse>) {
|
override suspend fun refreshAll(recipes: List<RecipeSummaryInfo>) {
|
||||||
logger.v { "refreshAll() called with: recipes = $recipes" }
|
logger.v { "refreshAll() called with: recipes = $recipes" }
|
||||||
db.withTransaction {
|
db.withTransaction {
|
||||||
recipeDao.removeAllRecipes()
|
recipeDao.removeAllRecipes()
|
||||||
@@ -108,12 +52,10 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
logger.v { "clearAllLocalData() called" }
|
logger.v { "clearAllLocalData() called" }
|
||||||
db.withTransaction {
|
db.withTransaction {
|
||||||
recipeDao.removeAllRecipes()
|
recipeDao.removeAllRecipes()
|
||||||
recipeDao.removeAllCategories()
|
|
||||||
recipeDao.removeAllTags()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveRecipeInfo(recipe: GetRecipeResponse) {
|
override suspend fun saveRecipeInfo(recipe: FullRecipeInfo) {
|
||||||
logger.v { "saveRecipeInfo() called with: recipe = $recipe" }
|
logger.v { "saveRecipeInfo() called with: recipe = $recipe" }
|
||||||
db.withTransaction {
|
db.withTransaction {
|
||||||
recipeDao.insertRecipe(recipe.toRecipeEntity())
|
recipeDao.insertRecipe(recipe.toRecipeEntity())
|
||||||
@@ -132,7 +74,7 @@ class RecipeStorageImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun queryRecipeInfo(recipeId: Long): FullRecipeInfo {
|
override suspend fun queryRecipeInfo(recipeId: String): FullRecipeEntity {
|
||||||
logger.v { "queryRecipeInfo() called with: recipeId = $recipeId" }
|
logger.v { "queryRecipeInfo() called with: recipeId = $recipeId" }
|
||||||
val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) {
|
val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) {
|
||||||
"Can't find recipe by id $recipeId in DB"
|
"Can't find recipe by id $recipeId in DB"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.impl
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -8,7 +8,7 @@ import javax.inject.Singleton
|
|||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class RecipeImageUrlProviderImpl @Inject constructor(
|
class RecipeImageUrlProviderImpl @Inject constructor(
|
||||||
private val baseURLStorage: BaseURLStorage,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : RecipeImageUrlProvider {
|
) : RecipeImageUrlProvider {
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class RecipeImageUrlProviderImpl @Inject constructor(
|
|||||||
logger.v { "generateImageUrl() called with: slug = $slug" }
|
logger.v { "generateImageUrl() called with: slug = $slug" }
|
||||||
slug?.takeUnless { it.isBlank() } ?: return null
|
slug?.takeUnless { it.isBlank() } ?: return null
|
||||||
val imagePath = IMAGE_PATH_FORMAT.format(slug)
|
val imagePath = IMAGE_PATH_FORMAT.format(slug)
|
||||||
val baseUrl = baseURLStorage.getBaseURL()?.takeUnless { it.isEmpty() }
|
val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() }
|
||||||
val result = baseUrl?.toHttpUrlOrNull()
|
val result = baseUrl?.toHttpUrlOrNull()
|
||||||
?.newBuilder()
|
?.newBuilder()
|
||||||
?.addPathSegments(imagePath)
|
?.addPathSegments(imagePath)
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import androidx.paging.PagingConfig
|
|||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -38,7 +38,7 @@ class RecipeRepoImpl @Inject constructor(
|
|||||||
storage.clearAllLocalData()
|
storage.clearAllLocalData()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo {
|
override suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity {
|
||||||
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
||||||
|
|
||||||
runCatchingExceptCancel {
|
runCatchingExceptCancel {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import androidx.paging.LoadType.REFRESH
|
|||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package gq.kirmanak.mealient.data.recipes.network
|
||||||
|
|
||||||
|
data class FullRecipeInfo(
|
||||||
|
val remoteId: String,
|
||||||
|
val name: String,
|
||||||
|
val recipeYield: String,
|
||||||
|
val recipeIngredients: List<RecipeIngredientInfo>,
|
||||||
|
val recipeInstructions: List<RecipeInstructionInfo>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RecipeIngredientInfo(
|
||||||
|
val note: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class RecipeInstructionInfo(
|
||||||
|
val text: String,
|
||||||
|
)
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.network
|
package gq.kirmanak.mealient.data.recipes.network
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
|
||||||
|
|
||||||
interface RecipeDataSource {
|
interface RecipeDataSource {
|
||||||
suspend fun requestRecipes(start: Int, limit: Int): List<GetRecipeSummaryResponse>
|
suspend fun requestRecipes(start: Int, limit: Int): List<RecipeSummaryInfo>
|
||||||
|
|
||||||
suspend fun requestRecipeInfo(slug: String): GetRecipeResponse
|
suspend fun requestRecipeInfo(slug: String): FullRecipeInfo
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package gq.kirmanak.mealient.data.recipes.network
|
||||||
|
|
||||||
|
import kotlinx.datetime.LocalDate
|
||||||
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
|
||||||
|
data class RecipeSummaryInfo(
|
||||||
|
val remoteId: String,
|
||||||
|
val name: String,
|
||||||
|
val slug: String,
|
||||||
|
val description: String = "",
|
||||||
|
val imageId: String,
|
||||||
|
val dateAdded: LocalDate,
|
||||||
|
val dateUpdated: LocalDateTime
|
||||||
|
)
|
||||||
@@ -7,6 +7,8 @@ interface PreferencesStorage {
|
|||||||
|
|
||||||
val baseUrlKey: Preferences.Key<String>
|
val baseUrlKey: Preferences.Key<String>
|
||||||
|
|
||||||
|
val serverVersionKey: Preferences.Key<String>
|
||||||
|
|
||||||
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||||
|
|
||||||
suspend fun <T> getValue(key: Preferences.Key<T>): T?
|
suspend fun <T> getValue(key: Preferences.Key<T>): T?
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class PreferencesStorageImpl @Inject constructor(
|
|||||||
|
|
||||||
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
||||||
|
|
||||||
|
override val serverVersionKey = stringPreferencesKey("serverVersion")
|
||||||
|
|
||||||
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
||||||
|
|
||||||
override suspend fun <T> getValue(key: Preferences.Key<T>): T? {
|
override suspend fun <T> getValue(key: Preferences.Key<T>): T? {
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import dagger.Binds
|
|||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.*
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||||
import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl
|
|
||||||
import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper
|
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -16,9 +14,13 @@ interface BaseURLModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
fun bindVersionDataSource(mealieDataSourceWrapper: MealieDataSourceWrapper): VersionDataSource
|
fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
fun bindBaseUrlStorage(baseURLStorageImpl: BaseURLStorageImpl): BaseURLStorage
|
fun bindBaseUrlStorage(baseURLStorageImpl: ServerInfoStorageImpl): ServerInfoStorage
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo
|
||||||
}
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Like [runCatching] but rethrows [CancellationException] to support
|
|
||||||
* cancellation of coroutines.
|
|
||||||
*/
|
|
||||||
inline fun <T> runCatchingExceptCancel(block: () -> T): Result<T> = try {
|
|
||||||
Result.success(block())
|
|
||||||
} catch (e: CancellationException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||||
|
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
||||||
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.*
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.*
|
||||||
|
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
fun FullRecipeInfo.toRecipeEntity() = RecipeEntity(
|
||||||
|
remoteId = remoteId,
|
||||||
|
recipeYield = recipeYield
|
||||||
|
)
|
||||||
|
|
||||||
|
fun RecipeIngredientInfo.toRecipeIngredientEntity(remoteId: String) = RecipeIngredientEntity(
|
||||||
|
recipeId = remoteId,
|
||||||
|
note = note,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun RecipeInstructionInfo.toRecipeInstructionEntity(remoteId: String) = RecipeInstructionEntity(
|
||||||
|
recipeId = remoteId,
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeSummaryResponseV0.toRecipeSummaryInfo() = RecipeSummaryInfo(
|
||||||
|
remoteId = remoteId.toString(),
|
||||||
|
name = name,
|
||||||
|
slug = slug,
|
||||||
|
description = description,
|
||||||
|
dateAdded = dateAdded,
|
||||||
|
dateUpdated = dateUpdated,
|
||||||
|
imageId = slug,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeSummaryResponseV1.toRecipeSummaryInfo() = RecipeSummaryInfo(
|
||||||
|
remoteId = remoteId,
|
||||||
|
name = name,
|
||||||
|
slug = slug,
|
||||||
|
description = description,
|
||||||
|
dateAdded = dateAdded,
|
||||||
|
dateUpdated = dateUpdated,
|
||||||
|
imageId = remoteId,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun RecipeSummaryInfo.recipeEntity() = RecipeSummaryEntity(
|
||||||
|
remoteId = remoteId,
|
||||||
|
name = name,
|
||||||
|
slug = slug,
|
||||||
|
description = description,
|
||||||
|
dateAdded = dateAdded,
|
||||||
|
dateUpdated = dateUpdated,
|
||||||
|
imageId = imageId,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun VersionResponseV0.toVersionInfo() = VersionInfo(version)
|
||||||
|
|
||||||
|
fun VersionResponseV1.toVersionInfo() = VersionInfo(version)
|
||||||
|
|
||||||
|
fun AddRecipeDraft.toAddRecipeInfo() = AddRecipeInfo(
|
||||||
|
name = recipeName,
|
||||||
|
description = recipeDescription,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeIngredient = recipeIngredients.map { AddRecipeIngredientInfo(note = it) },
|
||||||
|
recipeInstructions = recipeInstructions.map { AddRecipeInstructionInfo(text = it) },
|
||||||
|
settings = AddRecipeSettingsInfo(
|
||||||
|
public = isRecipePublic,
|
||||||
|
disableComments = areCommentsDisabled,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AddRecipeInfo.toDraft(): AddRecipeDraft = AddRecipeDraft(
|
||||||
|
recipeName = name,
|
||||||
|
recipeDescription = description,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeInstructions = recipeInstructions.map { it.text },
|
||||||
|
recipeIngredients = recipeIngredient.map { it.note },
|
||||||
|
isRecipePublic = settings.public,
|
||||||
|
areCommentsDisabled = settings.disableComments,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeResponseV0.toFullRecipeInfo() = FullRecipeInfo(
|
||||||
|
remoteId = remoteId.toString(),
|
||||||
|
name = name,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() },
|
||||||
|
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeIngredientResponseV0.toRecipeIngredientInfo() = RecipeIngredientInfo(
|
||||||
|
note = note,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeInstructionResponseV0.toRecipeInstructionInfo() = RecipeInstructionInfo(
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeResponseV1.toFullRecipeInfo() = FullRecipeInfo(
|
||||||
|
remoteId = remoteId,
|
||||||
|
name = name,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeIngredients = recipeIngredients.map { it.toRecipeIngredientInfo() },
|
||||||
|
recipeInstructions = recipeInstructions.map { it.toRecipeInstructionInfo() }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeIngredientResponseV1.toRecipeIngredientInfo() = RecipeIngredientInfo(
|
||||||
|
note = note,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun GetRecipeInstructionResponseV1.toRecipeInstructionInfo() = RecipeInstructionInfo(
|
||||||
|
text = text
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AddRecipeInfo.toV0Request() = AddRecipeRequestV0(
|
||||||
|
name = name,
|
||||||
|
description = description,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeIngredient = recipeIngredient.map { it.toV0Ingredient() },
|
||||||
|
recipeInstructions = recipeInstructions.map { it.toV0Instruction() },
|
||||||
|
settings = settings.toV0Settings(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeSettingsInfo.toV0Settings() = AddRecipeSettingsV0(
|
||||||
|
disableComments = disableComments,
|
||||||
|
public = public,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeIngredientInfo.toV0Ingredient() = AddRecipeIngredientV0(
|
||||||
|
note = note,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeInstructionInfo.toV0Instruction() = AddRecipeInstructionV0(
|
||||||
|
text = text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
fun AddRecipeInfo.toV1CreateRequest() = CreateRecipeRequestV1(
|
||||||
|
name = name,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun AddRecipeInfo.toV1UpdateRequest() = UpdateRecipeRequestV1(
|
||||||
|
description = description,
|
||||||
|
recipeYield = recipeYield,
|
||||||
|
recipeIngredient = recipeIngredient.map { it.toV1Ingredient() },
|
||||||
|
recipeInstructions = recipeInstructions.map { it.toV1Instruction() },
|
||||||
|
settings = settings.toV1Settings(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeSettingsInfo.toV1Settings() = AddRecipeSettingsV1(
|
||||||
|
disableComments = disableComments,
|
||||||
|
public = public,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeIngredientInfo.toV1Ingredient() = AddRecipeIngredientV1(
|
||||||
|
id = UUID.randomUUID().toString(),
|
||||||
|
note = note,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun AddRecipeInstructionInfo.toV1Instruction() = AddRecipeInstructionV1(
|
||||||
|
id = UUID.randomUUID().toString(),
|
||||||
|
text = text,
|
||||||
|
ingredientReferences = emptyList(),
|
||||||
|
)
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeIngredientEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeInstructionEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
|
||||||
import gq.kirmanak.mealient.datasource.models.*
|
|
||||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
|
||||||
|
|
||||||
fun GetRecipeResponse.toRecipeEntity() = RecipeEntity(
|
|
||||||
remoteId = remoteId,
|
|
||||||
recipeYield = recipeYield
|
|
||||||
)
|
|
||||||
|
|
||||||
fun GetRecipeIngredientResponse.toRecipeIngredientEntity(remoteId: Long) =
|
|
||||||
RecipeIngredientEntity(
|
|
||||||
recipeId = remoteId,
|
|
||||||
title = title,
|
|
||||||
note = note,
|
|
||||||
unit = unit,
|
|
||||||
food = food,
|
|
||||||
disableAmount = disableAmount,
|
|
||||||
quantity = quantity
|
|
||||||
)
|
|
||||||
|
|
||||||
fun GetRecipeInstructionResponse.toRecipeInstructionEntity(remoteId: Long) =
|
|
||||||
RecipeInstructionEntity(
|
|
||||||
recipeId = remoteId,
|
|
||||||
title = title,
|
|
||||||
text = text
|
|
||||||
)
|
|
||||||
|
|
||||||
fun GetRecipeSummaryResponse.recipeEntity() = RecipeSummaryEntity(
|
|
||||||
remoteId = remoteId,
|
|
||||||
name = name,
|
|
||||||
slug = slug,
|
|
||||||
image = image,
|
|
||||||
description = description,
|
|
||||||
rating = rating,
|
|
||||||
dateAdded = dateAdded,
|
|
||||||
dateUpdated = dateUpdated,
|
|
||||||
)
|
|
||||||
|
|
||||||
fun VersionResponse.toVersionInfo() = VersionInfo(production, version, demoStatus)
|
|
||||||
|
|
||||||
fun AddRecipeDraft.toAddRecipeRequest() = AddRecipeRequest(
|
|
||||||
name = recipeName,
|
|
||||||
description = recipeDescription,
|
|
||||||
recipeYield = recipeYield,
|
|
||||||
recipeIngredient = recipeIngredients.map { AddRecipeIngredient(note = it) },
|
|
||||||
recipeInstructions = recipeInstructions.map { AddRecipeInstruction(text = it) },
|
|
||||||
settings = AddRecipeSettings(
|
|
||||||
public = isRecipePublic,
|
|
||||||
disableComments = areCommentsDisabled,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun AddRecipeRequest.toDraft(): AddRecipeDraft = AddRecipeDraft(
|
|
||||||
recipeName = name,
|
|
||||||
recipeDescription = description,
|
|
||||||
recipeYield = recipeYield,
|
|
||||||
recipeInstructions = recipeInstructions.map { it.text },
|
|
||||||
recipeIngredients = recipeIngredient.map { it.note },
|
|
||||||
isRecipePublic = settings.public,
|
|
||||||
areCommentsDisabled = settings.disableComments,
|
|
||||||
)
|
|
||||||
@@ -12,12 +12,12 @@ import androidx.fragment.app.viewModels
|
|||||||
import by.kirich1409.viewbindingdelegate.viewBinding
|
import by.kirich1409.viewbindingdelegate.viewBinding
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||||
import gq.kirmanak.mealient.databinding.FragmentAddRecipeBinding
|
import gq.kirmanak.mealient.databinding.FragmentAddRecipeBinding
|
||||||
import gq.kirmanak.mealient.databinding.ViewSingleInputBinding
|
import gq.kirmanak.mealient.databinding.ViewSingleInputBinding
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeSettings
|
|
||||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||||
import gq.kirmanak.mealient.extensions.collectWhenViewResumed
|
import gq.kirmanak.mealient.extensions.collectWhenViewResumed
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
@@ -122,14 +122,15 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
|
|
||||||
private fun saveValues() = with(binding) {
|
private fun saveValues() = with(binding) {
|
||||||
logger.v { "saveValues() called" }
|
logger.v { "saveValues() called" }
|
||||||
val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstruction(text = it) }
|
val instructions =
|
||||||
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredient(note = it) }
|
parseInputRows(instructionsFlow).map { AddRecipeInstructionInfo(text = it) }
|
||||||
val settings = AddRecipeSettings(
|
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientInfo(note = it) }
|
||||||
|
val settings = AddRecipeSettingsInfo(
|
||||||
public = publicRecipe.isChecked,
|
public = publicRecipe.isChecked,
|
||||||
disableComments = disableComments.isChecked,
|
disableComments = disableComments.isChecked,
|
||||||
)
|
)
|
||||||
viewModel.preserve(
|
viewModel.preserve(
|
||||||
AddRecipeRequest(
|
AddRecipeInfo(
|
||||||
name = recipeNameInput.text.toString(),
|
name = recipeNameInput.text.toString(),
|
||||||
description = recipeDescriptionInput.text.toString(),
|
description = recipeDescriptionInput.text.toString(),
|
||||||
recipeYield = recipeYieldInput.text.toString(),
|
recipeYield = recipeYieldInput.text.toString(),
|
||||||
@@ -148,7 +149,7 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
|||||||
.filterNot { it.isBlank() }
|
.filterNot { it.isBlank() }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
private fun onSavedInputLoaded(request: AddRecipeRequest) = with(binding) {
|
private fun onSavedInputLoaded(request: AddRecipeInfo) = with(binding) {
|
||||||
logger.v { "onSavedInputLoaded() called with: request = $request" }
|
logger.v { "onSavedInputLoaded() called with: request = $request" }
|
||||||
recipeNameInput.setText(request.name)
|
recipeNameInput.setText(request.name)
|
||||||
recipeDescriptionInput.setText(request.description)
|
recipeDescriptionInput.setText(request.description)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package gq.kirmanak.mealient.ui.add
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@@ -23,8 +23,8 @@ class AddRecipeViewModel @Inject constructor(
|
|||||||
private val _addRecipeResultChannel = Channel<Boolean>(Channel.UNLIMITED)
|
private val _addRecipeResultChannel = Channel<Boolean>(Channel.UNLIMITED)
|
||||||
val addRecipeResult: Flow<Boolean> get() = _addRecipeResultChannel.receiveAsFlow()
|
val addRecipeResult: Flow<Boolean> get() = _addRecipeResultChannel.receiveAsFlow()
|
||||||
|
|
||||||
private val _preservedAddRecipeRequestChannel = Channel<AddRecipeRequest>(Channel.UNLIMITED)
|
private val _preservedAddRecipeRequestChannel = Channel<AddRecipeInfo>(Channel.UNLIMITED)
|
||||||
val preservedAddRecipeRequest: Flow<AddRecipeRequest>
|
val preservedAddRecipeRequest: Flow<AddRecipeInfo>
|
||||||
get() = _preservedAddRecipeRequestChannel.receiveAsFlow()
|
get() = _preservedAddRecipeRequestChannel.receiveAsFlow()
|
||||||
|
|
||||||
fun loadPreservedRequest() {
|
fun loadPreservedRequest() {
|
||||||
@@ -47,7 +47,7 @@ class AddRecipeViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun preserve(request: AddRecipeRequest) {
|
fun preserve(request: AddRecipeInfo) {
|
||||||
logger.v { "preserve() called with: request = $request" }
|
logger.v { "preserve() called with: request = $request" }
|
||||||
viewModelScope.launch { addRecipeRepo.preserve(request) }
|
viewModelScope.launch { addRecipeRepo.preserve(request) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
||||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import gq.kirmanak.mealient.R
|
import gq.kirmanak.mealient.R
|
||||||
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
||||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.ui.OperationUiState
|
import gq.kirmanak.mealient.ui.OperationUiState
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class BaseURLViewModel @Inject constructor(
|
class BaseURLViewModel @Inject constructor(
|
||||||
private val baseURLStorage: BaseURLStorage,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
private val versionDataSource: VersionDataSource,
|
private val versionDataSource: VersionDataSource,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -35,8 +35,8 @@ class BaseURLViewModel @Inject constructor(
|
|||||||
logger.v { "checkBaseURL() called with: baseURL = $baseURL" }
|
logger.v { "checkBaseURL() called with: baseURL = $baseURL" }
|
||||||
val result = runCatchingExceptCancel {
|
val result = runCatchingExceptCancel {
|
||||||
// If it returns proper version info then it must be a Mealie
|
// If it returns proper version info then it must be a Mealie
|
||||||
versionDataSource.getVersionInfo(baseURL)
|
val version = versionDataSource.getVersionInfo(baseURL).version
|
||||||
baseURLStorage.storeBaseURL(baseURL)
|
serverInfoRepo.storeBaseURL(baseURL, version)
|
||||||
}
|
}
|
||||||
logger.i { "checkBaseURL: result is $result" }
|
logger.i { "checkBaseURL: result is $result" }
|
||||||
_uiState.value = OperationUiState.fromResult(result)
|
_uiState.value = OperationUiState.fromResult(result)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class RecipeModelLoader private constructor(
|
|||||||
options: Options?
|
options: Options?
|
||||||
): String? {
|
): String? {
|
||||||
logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" }
|
logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" }
|
||||||
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.slug) }
|
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.imageId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getHeaders(
|
override fun getHeaders(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package gq.kirmanak.mealient.ui.recipes.info
|
package gq.kirmanak.mealient.ui.recipes.info
|
||||||
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||||
|
|
||||||
data class RecipeInfoUiState(
|
data class RecipeInfoUiState(
|
||||||
val areIngredientsVisible: Boolean = false,
|
val areIngredientsVisible: Boolean = false,
|
||||||
val areInstructionsVisible: Boolean = false,
|
val areInstructionsVisible: Boolean = false,
|
||||||
val recipeInfo: FullRecipeInfo? = null,
|
val recipeInfo: FullRecipeEntity? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -20,7 +20,7 @@ class RecipeInfoViewModel @Inject constructor(
|
|||||||
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
||||||
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
|
val uiState: LiveData<RecipeInfoUiState> get() = _uiState
|
||||||
|
|
||||||
fun loadRecipeInfo(recipeId: Long, recipeSlug: String) {
|
fun loadRecipeInfo(recipeId: String, recipeSlug: String) {
|
||||||
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
||||||
_uiState.value = RecipeInfoUiState()
|
_uiState.value = RecipeInfoUiState()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class SplashViewModel @Inject constructor(
|
class SplashViewModel @Inject constructor(
|
||||||
private val disclaimerStorage: DisclaimerStorage,
|
private val disclaimerStorage: DisclaimerStorage,
|
||||||
private val baseURLStorage: BaseURLStorage,
|
private val serverInfoRepo: ServerInfoRepo,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _nextDestination = MutableLiveData<NavDirections>()
|
private val _nextDestination = MutableLiveData<NavDirections>()
|
||||||
val nextDestination: LiveData<NavDirections> = _nextDestination
|
val nextDestination: LiveData<NavDirections> = _nextDestination
|
||||||
@@ -25,7 +25,7 @@ class SplashViewModel @Inject constructor(
|
|||||||
delay(1000)
|
delay(1000)
|
||||||
_nextDestination.value = when {
|
_nextDestination.value = when {
|
||||||
!disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
!disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
||||||
baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
serverInfoRepo.getUrl() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||||
else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
app:argType="string" />
|
app:argType="string" />
|
||||||
<argument
|
<argument
|
||||||
android:name="recipe_id"
|
android:name="recipe_id"
|
||||||
app:argType="long" />
|
app:argType="string" />
|
||||||
</dialog>
|
</dialog>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/disclaimerFragment"
|
android:id="@+id/disclaimerFragment"
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
import gq.kirmanak.mealient.data.auth.AuthDataSource
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
|
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
|
||||||
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
|
||||||
import io.mockk.*
|
import io.mockk.*
|
||||||
@@ -27,7 +29,7 @@ class AuthRepoImplTest {
|
|||||||
lateinit var dataSource: AuthDataSource
|
lateinit var dataSource: AuthDataSource
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var baseURLStorage: BaseURLStorage
|
lateinit var serverInfoRepo: ServerInfoRepo
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var storage: AuthStorage
|
lateinit var storage: AuthStorage
|
||||||
@@ -40,7 +42,7 @@ class AuthRepoImplTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = AuthRepoImpl(storage, dataSource, baseURLStorage, logger)
|
subject = AuthRepoImpl(storage, dataSource, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -51,14 +53,9 @@ class AuthRepoImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authenticate successfully then saves to storage`() = runTest {
|
fun `when authenticate successfully then saves to storage`() = runTest {
|
||||||
coEvery {
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION
|
||||||
dataSource.authenticate(
|
coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN
|
||||||
eq(TEST_USERNAME),
|
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||||
eq(TEST_PASSWORD),
|
|
||||||
eq(TEST_BASE_URL)
|
|
||||||
)
|
|
||||||
} returns TEST_TOKEN
|
|
||||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
|
||||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
|
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
|
||||||
coVerifyAll {
|
coVerifyAll {
|
||||||
storage.setAuthHeader(TEST_AUTH_HEADER)
|
storage.setAuthHeader(TEST_AUTH_HEADER)
|
||||||
@@ -70,9 +67,9 @@ class AuthRepoImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authenticate fails then does not change storage`() = runTest {
|
fun `when authenticate fails then does not change storage`() = runTest {
|
||||||
coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException()
|
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
||||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||||
runCatching { subject.authenticate("invalid", "") }
|
runCatchingExceptCancel { subject.authenticate("invalid", "") }
|
||||||
confirmVerified(storage)
|
confirmVerified(storage)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,22 +104,19 @@ class AuthRepoImplTest {
|
|||||||
fun `when invalidate with credentials then calls authenticate`() = runTest {
|
fun `when invalidate with credentials then calls authenticate`() = runTest {
|
||||||
coEvery { storage.getEmail() } returns TEST_USERNAME
|
coEvery { storage.getEmail() } returns TEST_USERNAME
|
||||||
coEvery { storage.getPassword() } returns TEST_PASSWORD
|
coEvery { storage.getPassword() } returns TEST_PASSWORD
|
||||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION
|
||||||
coEvery {
|
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN
|
||||||
} returns TEST_TOKEN
|
|
||||||
subject.invalidateAuthHeader()
|
subject.invalidateAuthHeader()
|
||||||
coVerifyAll {
|
coVerifyAll { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) }
|
||||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when invalidate with credentials and auth fails then clears email`() = runTest {
|
fun `when invalidate with credentials and auth fails then clears email`() = runTest {
|
||||||
coEvery { storage.getEmail() } returns "invalid"
|
coEvery { storage.getEmail() } returns "invalid"
|
||||||
coEvery { storage.getPassword() } returns ""
|
coEvery { storage.getPassword() } returns ""
|
||||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||||
coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException()
|
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
||||||
subject.invalidateAuthHeader()
|
subject.invalidateAuthHeader()
|
||||||
coVerify { storage.setEmail(null) }
|
coVerify { storage.setEmail(null) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.baseurl
|
|||||||
|
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl
|
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
@@ -15,20 +15,22 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class BaseURLStorageImplTest {
|
class ServerInfoStorageImplTest {
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var preferencesStorage: PreferencesStorage
|
lateinit var preferencesStorage: PreferencesStorage
|
||||||
|
|
||||||
lateinit var subject: BaseURLStorage
|
lateinit var subject: ServerInfoStorage
|
||||||
|
|
||||||
private val baseUrlKey = stringPreferencesKey("baseUrlKey")
|
private val baseUrlKey = stringPreferencesKey("baseUrlKey")
|
||||||
|
private val serverVersionKey = stringPreferencesKey("serverVersionKey")
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = BaseURLStorageImpl(preferencesStorage)
|
subject = ServerInfoStorageImpl(preferencesStorage)
|
||||||
every { preferencesStorage.baseUrlKey } returns baseUrlKey
|
every { preferencesStorage.baseUrlKey } returns baseUrlKey
|
||||||
|
every { preferencesStorage.serverVersionKey } returns serverVersionKey
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -37,30 +39,21 @@ class BaseURLStorageImplTest {
|
|||||||
assertThat(subject.getBaseURL()).isNull()
|
assertThat(subject.getBaseURL()).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException::class)
|
|
||||||
fun `when requireBaseURL and preferences storage empty then IllegalStateException`() = runTest {
|
|
||||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null
|
|
||||||
subject.requireBaseURL()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when getBaseUrl and preferences storage has value then value`() = runTest {
|
fun `when getBaseUrl and preferences storage has value then value`() = runTest {
|
||||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
|
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
|
||||||
assertThat(subject.getBaseURL()).isEqualTo("baseUrl")
|
assertThat(subject.getBaseURL()).isEqualTo("baseUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when requireBaseURL and preferences storage has value then value`() = runTest {
|
|
||||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
|
|
||||||
assertThat(subject.requireBaseURL()).isEqualTo("baseUrl")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when storeBaseURL then calls preferences storage`() = runTest {
|
fun `when storeBaseURL then calls preferences storage`() = runTest {
|
||||||
subject.storeBaseURL("baseUrl")
|
subject.storeBaseURL("baseUrl", "v0.5.6")
|
||||||
coVerify {
|
coVerify {
|
||||||
preferencesStorage.baseUrlKey
|
preferencesStorage.baseUrlKey
|
||||||
preferencesStorage.storeValues(eq(Pair(baseUrlKey, "baseUrl")))
|
preferencesStorage.storeValues(
|
||||||
|
eq(Pair(baseUrlKey, "baseUrl")),
|
||||||
|
eq(Pair(serverVersionKey, "v0.5.6")),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
package gq.kirmanak.mealient.data.network
|
package gq.kirmanak.mealient.data.network
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerifyAll
|
import io.mockk.coVerifyAll
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
|
import io.mockk.mockk
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -21,32 +24,37 @@ import java.io.IOException
|
|||||||
class MealieDataSourceWrapperTest {
|
class MealieDataSourceWrapperTest {
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var baseURLStorage: BaseURLStorage
|
lateinit var serverInfoRepo: ServerInfoRepo
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var authRepo: AuthRepo
|
lateinit var authRepo: AuthRepo
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var mealieDataSource: MealieDataSource
|
lateinit var v0Source: MealieDataSourceV0
|
||||||
|
|
||||||
|
@MockK
|
||||||
|
lateinit var v1Source: MealieDataSourceV1
|
||||||
|
|
||||||
lateinit var subject: MealieDataSourceWrapper
|
lateinit var subject: MealieDataSourceWrapper
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = MealieDataSourceWrapper(baseURLStorage, authRepo, mealieDataSource)
|
subject = MealieDataSourceWrapper(serverInfoRepo, authRepo, v0Source, v1Source)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when withAuthHeader fails with Unauthorized then invalidates auth`() = runTest {
|
fun `when withAuthHeader fails with Unauthorized then invalidates auth`() = runTest {
|
||||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION
|
||||||
|
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||||
coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER
|
coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER
|
||||||
coEvery {
|
coEvery {
|
||||||
mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake"))
|
v0Source.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake"))
|
||||||
} throws NetworkError.Unauthorized(IOException())
|
} throws NetworkError.Unauthorized(IOException())
|
||||||
|
val successResponse = mockk<GetRecipeResponseV0>(relaxed = true)
|
||||||
coEvery {
|
coEvery {
|
||||||
mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake"))
|
v0Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake"))
|
||||||
} returns GET_CAKE_RESPONSE
|
} returns successResponse
|
||||||
subject.requestRecipeInfo("cake")
|
subject.requestRecipeInfo("cake")
|
||||||
coVerifyAll {
|
coVerifyAll {
|
||||||
authRepo.getAuthHeader()
|
authRepo.getAuthHeader()
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ package gq.kirmanak.mealient.data.recipes.db
|
|||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import gq.kirmanak.mealient.database.AppDb
|
import gq.kirmanak.mealient.database.AppDb
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.CategoryEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.CategoryRecipeEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.TagEntity
|
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.TagRecipeEntity
|
|
||||||
import gq.kirmanak.mealient.test.HiltRobolectricTest
|
import gq.kirmanak.mealient.test.HiltRobolectricTest
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.BREAD_INGREDIENT
|
import gq.kirmanak.mealient.test.RecipeImplTestData.BREAD_INGREDIENT
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
|
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
|
||||||
@@ -36,28 +32,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
@Inject
|
@Inject
|
||||||
lateinit var appDb: AppDb
|
lateinit var appDb: AppDb
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when saveRecipes then saves tags`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
val actualTags = appDb.recipeDao().queryAllTags()
|
|
||||||
assertThat(actualTags).containsExactly(
|
|
||||||
TagEntity(localId = 1, name = "gluten"),
|
|
||||||
TagEntity(localId = 2, name = "allergic"),
|
|
||||||
TagEntity(localId = 3, name = "milk")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when saveRecipes then saves categories`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
val actual = appDb.recipeDao().queryAllCategories()
|
|
||||||
assertThat(actual).containsExactly(
|
|
||||||
CategoryEntity(localId = 1, name = "dessert"),
|
|
||||||
CategoryEntity(localId = 2, name = "tasty"),
|
|
||||||
CategoryEntity(localId = 3, name = "porridge")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when saveRecipes then saves recipes`() = runTest {
|
fun `when saveRecipes then saves recipes`() = runTest {
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
@@ -68,30 +42,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when saveRecipes then saves category recipes`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
val actual = appDb.recipeDao().queryAllCategoryRecipes()
|
|
||||||
assertThat(actual).containsExactly(
|
|
||||||
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
|
|
||||||
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
|
|
||||||
CategoryRecipeEntity(categoryId = 3, recipeId = 2),
|
|
||||||
CategoryRecipeEntity(categoryId = 2, recipeId = 2)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when saveRecipes then saves tag recipes`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
val actual = appDb.recipeDao().queryAllTagRecipes()
|
|
||||||
assertThat(actual).containsExactly(
|
|
||||||
TagRecipeEntity(tagId = 1, recipeId = 1),
|
|
||||||
TagRecipeEntity(tagId = 2, recipeId = 1),
|
|
||||||
TagRecipeEntity(tagId = 3, recipeId = 2),
|
|
||||||
TagRecipeEntity(tagId = 1, recipeId = 2),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when refreshAll then old recipes aren't preserved`() = runTest {
|
fun `when refreshAll then old recipes aren't preserved`() = runTest {
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
@@ -100,28 +50,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
assertThat(actual).containsExactly(CAKE_RECIPE_SUMMARY_ENTITY)
|
assertThat(actual).containsExactly(CAKE_RECIPE_SUMMARY_ENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when refreshAll then old category recipes aren't preserved`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
|
||||||
val actual = appDb.recipeDao().queryAllCategoryRecipes()
|
|
||||||
assertThat(actual).containsExactly(
|
|
||||||
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
|
|
||||||
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when refreshAll then old tag recipes aren't preserved`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
|
||||||
val actual = appDb.recipeDao().queryAllTagRecipes()
|
|
||||||
assertThat(actual).containsExactly(
|
|
||||||
TagRecipeEntity(tagId = 1, recipeId = 1),
|
|
||||||
TagRecipeEntity(tagId = 2, recipeId = 1),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when clearAllLocalData then recipes aren't preserved`() = runTest {
|
fun `when clearAllLocalData then recipes aren't preserved`() = runTest {
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||||
@@ -130,27 +58,11 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
assertThat(actual).isEmpty()
|
assertThat(actual).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when clearAllLocalData then categories aren't preserved`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
subject.clearAllLocalData()
|
|
||||||
val actual = appDb.recipeDao().queryAllCategories()
|
|
||||||
assertThat(actual).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when clearAllLocalData then tags aren't preserved`() = runTest {
|
|
||||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
|
||||||
subject.clearAllLocalData()
|
|
||||||
val actual = appDb.recipeDao().queryAllTags()
|
|
||||||
assertThat(actual).isEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when saveRecipeInfo then saves recipe info`() = runTest {
|
fun `when saveRecipeInfo then saves recipe info`() = runTest {
|
||||||
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
|
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
|
||||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)
|
val actual = appDb.recipeDao().queryFullRecipeInfo("1")
|
||||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +71,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE))
|
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE))
|
||||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||||
subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE)
|
subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE)
|
||||||
val actual = appDb.recipeDao().queryFullRecipeInfo(2)
|
val actual = appDb.recipeDao().queryFullRecipeInfo("2")
|
||||||
assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY)
|
assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +81,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||||
val newRecipe = GET_CAKE_RESPONSE.copy(recipeIngredients = listOf(BREAD_INGREDIENT))
|
val newRecipe = GET_CAKE_RESPONSE.copy(recipeIngredients = listOf(BREAD_INGREDIENT))
|
||||||
subject.saveRecipeInfo(newRecipe)
|
subject.saveRecipeInfo(newRecipe)
|
||||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeIngredients
|
val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeIngredients
|
||||||
val expected = listOf(CAKE_BREAD_RECIPE_INGREDIENT_ENTITY.copy(localId = 3))
|
val expected = listOf(CAKE_BREAD_RECIPE_INGREDIENT_ENTITY.copy(localId = 3))
|
||||||
assertThat(actual).isEqualTo(expected)
|
assertThat(actual).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
@@ -180,7 +92,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
|||||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||||
val newRecipe = GET_CAKE_RESPONSE.copy(recipeInstructions = listOf(MIX_INSTRUCTION))
|
val newRecipe = GET_CAKE_RESPONSE.copy(recipeInstructions = listOf(MIX_INSTRUCTION))
|
||||||
subject.saveRecipeInfo(newRecipe)
|
subject.saveRecipeInfo(newRecipe)
|
||||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeInstructions
|
val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeInstructions
|
||||||
val expected = listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY.copy(localId = 3))
|
val expected = listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY.copy(localId = 3))
|
||||||
assertThat(actual).isEqualTo(expected)
|
assertThat(actual).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package gq.kirmanak.mealient.data.recipes.impl
|
package gq.kirmanak.mealient.data.recipes.impl
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
@@ -17,7 +17,7 @@ class RecipeImageUrlProviderImplTest {
|
|||||||
lateinit var subject: RecipeImageUrlProvider
|
lateinit var subject: RecipeImageUrlProvider
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var baseURLStorage: BaseURLStorage
|
lateinit var serverInfoRepo: ServerInfoRepo
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var logger: Logger
|
lateinit var logger: Logger
|
||||||
@@ -25,7 +25,7 @@ class RecipeImageUrlProviderImplTest {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = RecipeImageUrlProviderImpl(baseURLStorage, logger)
|
subject = RecipeImageUrlProviderImpl(serverInfoRepo, logger)
|
||||||
prepareBaseURL("https://google.com/")
|
prepareBaseURL("https://google.com/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +81,6 @@ class RecipeImageUrlProviderImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareBaseURL(baseURL: String?) {
|
private fun prepareBaseURL(baseURL: String?) {
|
||||||
coEvery { baseURLStorage.getBaseURL() } returns baseURL
|
coEvery { serverInfoRepo.getUrl() } returns baseURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,24 +47,24 @@ class RecipeRepoImplTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `when loadRecipeInfo then loads recipe`() = runTest {
|
fun `when loadRecipeInfo then loads recipe`() = runTest {
|
||||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
||||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||||
val actual = subject.loadRecipeInfo(1, "cake")
|
val actual = subject.loadRecipeInfo("1", "cake")
|
||||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when loadRecipeInfo then saves to DB`() = runTest {
|
fun `when loadRecipeInfo then saves to DB`() = runTest {
|
||||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
||||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||||
subject.loadRecipeInfo(1, "cake")
|
subject.loadRecipeInfo("1", "cake")
|
||||||
coVerify { storage.saveRecipeInfo(eq(GET_CAKE_RESPONSE)) }
|
coVerify { storage.saveRecipeInfo(eq(GET_CAKE_RESPONSE)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when loadRecipeInfo with error then loads from DB`() = runTest {
|
fun `when loadRecipeInfo with error then loads from DB`() = runTest {
|
||||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } throws RuntimeException()
|
coEvery { dataSource.requestRecipeInfo(eq("cake")) } throws RuntimeException()
|
||||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||||
val actual = subject.loadRecipeInfo(1, "cake")
|
val actual = subject.loadRecipeInfo("1", "cake")
|
||||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||||
import gq.kirmanak.mealient.datasource.models.NetworkError.Unauthorized
|
import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES
|
import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.extensions
|
package gq.kirmanak.mealient.extensions
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction
|
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeSettings
|
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@@ -22,42 +22,42 @@ class RemoteToLocalMappingsTest {
|
|||||||
areCommentsDisabled = true,
|
areCommentsDisabled = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
val expected = AddRecipeRequest(
|
val expected = AddRecipeInfo(
|
||||||
name = "Recipe name",
|
name = "Recipe name",
|
||||||
description = "Recipe description",
|
description = "Recipe description",
|
||||||
recipeYield = "Recipe yield",
|
recipeYield = "Recipe yield",
|
||||||
recipeIngredient = listOf(
|
recipeIngredient = listOf(
|
||||||
AddRecipeIngredient(note = "Recipe ingredient 1"),
|
AddRecipeIngredientInfo(note = "Recipe ingredient 1"),
|
||||||
AddRecipeIngredient(note = "Recipe ingredient 2")
|
AddRecipeIngredientInfo(note = "Recipe ingredient 2")
|
||||||
),
|
),
|
||||||
recipeInstructions = listOf(
|
recipeInstructions = listOf(
|
||||||
AddRecipeInstruction(text = "Recipe instruction 1"),
|
AddRecipeInstructionInfo(text = "Recipe instruction 1"),
|
||||||
AddRecipeInstruction(text = "Recipe instruction 2")
|
AddRecipeInstructionInfo(text = "Recipe instruction 2")
|
||||||
),
|
),
|
||||||
settings = AddRecipeSettings(
|
settings = AddRecipeSettingsInfo(
|
||||||
public = false,
|
public = false,
|
||||||
disableComments = true,
|
disableComments = true,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
assertThat(input.toAddRecipeRequest()).isEqualTo(expected)
|
assertThat(input.toAddRecipeInfo()).isEqualTo(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when toDraft then fills fields correctly`() {
|
fun `when toDraft then fills fields correctly`() {
|
||||||
val request = AddRecipeRequest(
|
val request = AddRecipeInfo(
|
||||||
name = "Recipe name",
|
name = "Recipe name",
|
||||||
description = "Recipe description",
|
description = "Recipe description",
|
||||||
recipeYield = "Recipe yield",
|
recipeYield = "Recipe yield",
|
||||||
recipeIngredient = listOf(
|
recipeIngredient = listOf(
|
||||||
AddRecipeIngredient(note = "Recipe ingredient 1"),
|
AddRecipeIngredientInfo(note = "Recipe ingredient 1"),
|
||||||
AddRecipeIngredient(note = "Recipe ingredient 2")
|
AddRecipeIngredientInfo(note = "Recipe ingredient 2")
|
||||||
),
|
),
|
||||||
recipeInstructions = listOf(
|
recipeInstructions = listOf(
|
||||||
AddRecipeInstruction(text = "Recipe instruction 1"),
|
AddRecipeInstructionInfo(text = "Recipe instruction 1"),
|
||||||
AddRecipeInstruction(text = "Recipe instruction 2")
|
AddRecipeInstructionInfo(text = "Recipe instruction 2")
|
||||||
),
|
),
|
||||||
settings = AddRecipeSettings(
|
settings = AddRecipeSettingsInfo(
|
||||||
public = false,
|
public = false,
|
||||||
disableComments = true,
|
disableComments = true,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package gq.kirmanak.mealient.test
|
package gq.kirmanak.mealient.test
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||||
|
|
||||||
object AuthImplTestData {
|
object AuthImplTestData {
|
||||||
const val TEST_USERNAME = "TEST_USERNAME"
|
const val TEST_USERNAME = "TEST_USERNAME"
|
||||||
const val TEST_PASSWORD = "TEST_PASSWORD"
|
const val TEST_PASSWORD = "TEST_PASSWORD"
|
||||||
const val TEST_BASE_URL = "https://example.com/"
|
const val TEST_BASE_URL = "https://example.com/"
|
||||||
const val TEST_TOKEN = "TEST_TOKEN"
|
const val TEST_TOKEN = "TEST_TOKEN"
|
||||||
const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN"
|
const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN"
|
||||||
const val TEST_URL = "TEST_URL"
|
const val TEST_VERSION = "v0.5.6"
|
||||||
|
val TEST_SERVER_VERSION = ServerVersion.V0
|
||||||
}
|
}
|
||||||
@@ -1,133 +1,95 @@
|
|||||||
package gq.kirmanak.mealient.test
|
package gq.kirmanak.mealient.test
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||||
|
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeIngredientInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeInstructionInfo
|
||||||
|
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
import gq.kirmanak.mealient.database.recipe.entity.*
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeIngredientResponse
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeInstructionResponse
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.LocalDateTime
|
import kotlinx.datetime.LocalDateTime
|
||||||
|
|
||||||
object RecipeImplTestData {
|
object RecipeImplTestData {
|
||||||
val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponse(
|
val RECIPE_SUMMARY_CAKE = RecipeSummaryInfo(
|
||||||
remoteId = 1,
|
remoteId = "1",
|
||||||
name = "Cake",
|
name = "Cake",
|
||||||
slug = "cake",
|
slug = "cake",
|
||||||
image = "86",
|
|
||||||
description = "A tasty cake",
|
description = "A tasty cake",
|
||||||
recipeCategories = listOf("dessert", "tasty"),
|
|
||||||
tags = listOf("gluten", "allergic"),
|
|
||||||
rating = 4,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-13"),
|
dateAdded = LocalDate.parse("2021-11-13"),
|
||||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
||||||
|
imageId = "cake",
|
||||||
)
|
)
|
||||||
|
|
||||||
val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponse(
|
val RECIPE_SUMMARY_PORRIDGE = RecipeSummaryInfo(
|
||||||
remoteId = 2,
|
remoteId = "2",
|
||||||
name = "Porridge",
|
name = "Porridge",
|
||||||
slug = "porridge",
|
slug = "porridge",
|
||||||
image = "89",
|
|
||||||
description = "A tasty porridge",
|
description = "A tasty porridge",
|
||||||
recipeCategories = listOf("porridge", "tasty"),
|
|
||||||
tags = listOf("gluten", "milk"),
|
|
||||||
rating = 5,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-12"),
|
dateAdded = LocalDate.parse("2021-11-12"),
|
||||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
||||||
|
imageId = "porridge",
|
||||||
)
|
)
|
||||||
|
|
||||||
val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)
|
val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)
|
||||||
|
|
||||||
val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
||||||
remoteId = 1,
|
remoteId = "1",
|
||||||
name = "Cake",
|
name = "Cake",
|
||||||
slug = "cake",
|
slug = "cake",
|
||||||
image = "86",
|
|
||||||
description = "A tasty cake",
|
description = "A tasty cake",
|
||||||
rating = 4,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-13"),
|
dateAdded = LocalDate.parse("2021-11-13"),
|
||||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13")
|
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
||||||
|
imageId = "cake",
|
||||||
)
|
)
|
||||||
|
|
||||||
val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
||||||
remoteId = 2,
|
remoteId = "2",
|
||||||
name = "Porridge",
|
name = "Porridge",
|
||||||
slug = "porridge",
|
slug = "porridge",
|
||||||
image = "89",
|
|
||||||
description = "A tasty porridge",
|
description = "A tasty porridge",
|
||||||
rating = 5,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-12"),
|
dateAdded = LocalDate.parse("2021-11-12"),
|
||||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
||||||
|
imageId = "porridge",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val SUGAR_INGREDIENT = GetRecipeIngredientResponse(
|
private val SUGAR_INGREDIENT = RecipeIngredientInfo(
|
||||||
title = "Sugar",
|
|
||||||
note = "2 oz of white sugar",
|
note = "2 oz of white sugar",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = true,
|
|
||||||
quantity = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val BREAD_INGREDIENT = GetRecipeIngredientResponse(
|
val BREAD_INGREDIENT = RecipeIngredientInfo(
|
||||||
title = "Bread",
|
|
||||||
note = "2 oz of white bread",
|
note = "2 oz of white bread",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = false,
|
|
||||||
quantity = 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val MILK_INGREDIENT = GetRecipeIngredientResponse(
|
private val MILK_INGREDIENT = RecipeIngredientInfo(
|
||||||
title = "Milk",
|
|
||||||
note = "2 oz of white milk",
|
note = "2 oz of white milk",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = true,
|
|
||||||
quantity = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val MIX_INSTRUCTION = GetRecipeInstructionResponse(
|
val MIX_INSTRUCTION = RecipeInstructionInfo(
|
||||||
title = "Mix",
|
|
||||||
text = "Mix the ingredients"
|
text = "Mix the ingredients"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val BAKE_INSTRUCTION = GetRecipeInstructionResponse(
|
private val BAKE_INSTRUCTION = RecipeInstructionInfo(
|
||||||
title = "Bake",
|
|
||||||
text = "Bake the ingredients"
|
text = "Bake the ingredients"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val BOIL_INSTRUCTION = GetRecipeInstructionResponse(
|
private val BOIL_INSTRUCTION = RecipeInstructionInfo(
|
||||||
title = "Boil",
|
|
||||||
text = "Boil the ingredients"
|
text = "Boil the ingredients"
|
||||||
)
|
)
|
||||||
|
|
||||||
val GET_CAKE_RESPONSE = GetRecipeResponse(
|
val GET_CAKE_RESPONSE = FullRecipeInfo(
|
||||||
remoteId = 1,
|
remoteId = "1",
|
||||||
name = "Cake",
|
name = "Cake",
|
||||||
slug = "cake",
|
|
||||||
image = "86",
|
|
||||||
description = "A tasty cake",
|
|
||||||
recipeCategories = listOf("dessert", "tasty"),
|
|
||||||
tags = listOf("gluten", "allergic"),
|
|
||||||
rating = 4,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-13"),
|
|
||||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
|
||||||
recipeYield = "4 servings",
|
recipeYield = "4 servings",
|
||||||
recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT),
|
recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT),
|
||||||
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION)
|
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION)
|
||||||
)
|
)
|
||||||
|
|
||||||
val GET_PORRIDGE_RESPONSE = GetRecipeResponse(
|
val GET_PORRIDGE_RESPONSE = FullRecipeInfo(
|
||||||
remoteId = 2,
|
remoteId = "2",
|
||||||
name = "Porridge",
|
name = "Porridge",
|
||||||
slug = "porridge",
|
|
||||||
image = "89",
|
|
||||||
description = "A tasty porridge",
|
|
||||||
recipeCategories = listOf("porridge", "tasty"),
|
|
||||||
tags = listOf("gluten", "milk"),
|
|
||||||
rating = 5,
|
|
||||||
dateAdded = LocalDate.parse("2021-11-12"),
|
|
||||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
|
||||||
recipeYield = "3 servings",
|
recipeYield = "3 servings",
|
||||||
recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT),
|
recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT),
|
||||||
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION)
|
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION)
|
||||||
@@ -135,46 +97,34 @@ object RecipeImplTestData {
|
|||||||
|
|
||||||
val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||||
localId = 1,
|
localId = 1,
|
||||||
recipeId = 1,
|
recipeId = "1",
|
||||||
title = "Mix",
|
|
||||||
text = "Mix the ingredients",
|
text = "Mix the ingredients",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||||
localId = 2,
|
localId = 2,
|
||||||
recipeId = 1,
|
recipeId = "1",
|
||||||
title = "Bake",
|
|
||||||
text = "Bake the ingredients",
|
text = "Bake the ingredients",
|
||||||
)
|
)
|
||||||
|
|
||||||
private val CAKE_RECIPE_ENTITY = RecipeEntity(
|
private val CAKE_RECIPE_ENTITY = RecipeEntity(
|
||||||
remoteId = 1,
|
remoteId = "1",
|
||||||
recipeYield = "4 servings"
|
recipeYield = "4 servings"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||||
localId = 1,
|
localId = 1,
|
||||||
recipeId = 1,
|
recipeId = "1",
|
||||||
title = "Sugar",
|
|
||||||
note = "2 oz of white sugar",
|
note = "2 oz of white sugar",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = true,
|
|
||||||
quantity = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||||
localId = 2,
|
localId = 2,
|
||||||
recipeId = 1,
|
recipeId = "1",
|
||||||
title = "Bread",
|
|
||||||
note = "2 oz of white bread",
|
note = "2 oz of white bread",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = false,
|
|
||||||
quantity = 2
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val FULL_CAKE_INFO_ENTITY = FullRecipeInfo(
|
val FULL_CAKE_INFO_ENTITY = FullRecipeEntity(
|
||||||
recipeEntity = CAKE_RECIPE_ENTITY,
|
recipeEntity = CAKE_RECIPE_ENTITY,
|
||||||
recipeSummaryEntity = CAKE_RECIPE_SUMMARY_ENTITY,
|
recipeSummaryEntity = CAKE_RECIPE_SUMMARY_ENTITY,
|
||||||
recipeIngredients = listOf(
|
recipeIngredients = listOf(
|
||||||
@@ -188,47 +138,35 @@ object RecipeImplTestData {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity(
|
private val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity(
|
||||||
remoteId = 2,
|
remoteId = "2",
|
||||||
recipeYield = "3 servings"
|
recipeYield = "3 servings"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||||
localId = 4,
|
localId = 4,
|
||||||
recipeId = 2,
|
recipeId = "2",
|
||||||
title = "Milk",
|
|
||||||
note = "2 oz of white milk",
|
note = "2 oz of white milk",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = true,
|
|
||||||
quantity = 3
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||||
localId = 3,
|
localId = 3,
|
||||||
recipeId = 2,
|
recipeId = "2",
|
||||||
title = "Sugar",
|
|
||||||
note = "2 oz of white sugar",
|
note = "2 oz of white sugar",
|
||||||
unit = "",
|
|
||||||
food = "",
|
|
||||||
disableAmount = true,
|
|
||||||
quantity = 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||||
localId = 3,
|
localId = 3,
|
||||||
recipeId = 2,
|
recipeId = "2",
|
||||||
title = "Mix",
|
|
||||||
text = "Mix the ingredients"
|
text = "Mix the ingredients"
|
||||||
)
|
)
|
||||||
|
|
||||||
private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||||
localId = 4,
|
localId = 4,
|
||||||
recipeId = 2,
|
recipeId = "2",
|
||||||
title = "Boil",
|
|
||||||
text = "Boil the ingredients"
|
text = "Boil the ingredients"
|
||||||
)
|
)
|
||||||
|
|
||||||
val FULL_PORRIDGE_INFO_ENTITY = FullRecipeInfo(
|
val FULL_PORRIDGE_INFO_ENTITY = FullRecipeEntity(
|
||||||
recipeEntity = PORRIDGE_RECIPE_ENTITY_FULL,
|
recipeEntity = PORRIDGE_RECIPE_ENTITY_FULL,
|
||||||
recipeSummaryEntity = PORRIDGE_RECIPE_SUMMARY_ENTITY,
|
recipeSummaryEntity = PORRIDGE_RECIPE_SUMMARY_ENTITY,
|
||||||
recipeIngredients = listOf(
|
recipeIngredients = listOf(
|
||||||
@@ -240,4 +178,21 @@ object RecipeImplTestData {
|
|||||||
PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY,
|
PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val PORRIDGE_ADD_RECIPE_INFO = AddRecipeInfo(
|
||||||
|
name = "Porridge",
|
||||||
|
description = "Tasty breakfast",
|
||||||
|
recipeYield = "5 servings",
|
||||||
|
recipeIngredient = listOf(
|
||||||
|
AddRecipeIngredientInfo("Milk"),
|
||||||
|
AddRecipeIngredientInfo("Sugar"),
|
||||||
|
AddRecipeIngredientInfo("Salt"),
|
||||||
|
AddRecipeIngredientInfo("Porridge"),
|
||||||
|
),
|
||||||
|
recipeInstructions = listOf(
|
||||||
|
AddRecipeInstructionInfo("Mix"),
|
||||||
|
AddRecipeInstructionInfo("Cook"),
|
||||||
|
),
|
||||||
|
settings = AddRecipeSettingsInfo(disableComments = false, public = true),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,8 @@ package gq.kirmanak.mealient.ui.add
|
|||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
@@ -61,21 +61,21 @@ class AddRecipeViewModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when preserve then doesn't update UI`() {
|
fun `when preserve then doesn't update UI`() {
|
||||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest())
|
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
subject.preserve(AddRecipeRequest())
|
subject.preserve(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow }
|
coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest {
|
fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest {
|
||||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest())
|
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO)
|
||||||
val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() }
|
val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() }
|
||||||
assertThat(actual).isNull()
|
assertThat(actual).isNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest {
|
fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest {
|
||||||
val expected = AddRecipeRequest()
|
val expected = PORRIDGE_ADD_RECIPE_INFO
|
||||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
||||||
subject.loadPreservedRequest()
|
subject.loadPreservedRequest()
|
||||||
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
||||||
@@ -83,7 +83,7 @@ class AddRecipeViewModelTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when clear then updates preservedAddRecipeRequest`() = runTest {
|
fun `when clear then updates preservedAddRecipeRequest`() = runTest {
|
||||||
val expected = AddRecipeRequest()
|
val expected = PORRIDGE_ADD_RECIPE_INFO
|
||||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
||||||
subject.clear()
|
subject.clear()
|
||||||
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package gq.kirmanak.mealient.ui.baseurl
|
package gq.kirmanak.mealient.ui.baseurl
|
||||||
|
|
||||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||||
|
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION
|
||||||
import gq.kirmanak.mealient.test.RobolectricTest
|
import gq.kirmanak.mealient.test.RobolectricTest
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
@@ -20,7 +21,7 @@ import org.junit.Test
|
|||||||
class BaseURLViewModelTest : RobolectricTest() {
|
class BaseURLViewModelTest : RobolectricTest() {
|
||||||
|
|
||||||
@MockK(relaxUnitFun = true)
|
@MockK(relaxUnitFun = true)
|
||||||
lateinit var baseURLStorage: BaseURLStorage
|
lateinit var serverInfoRepo: ServerInfoRepo
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var versionDataSource: VersionDataSource
|
lateinit var versionDataSource: VersionDataSource
|
||||||
@@ -33,16 +34,16 @@ class BaseURLViewModelTest : RobolectricTest() {
|
|||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
subject = BaseURLViewModel(baseURLStorage, versionDataSource, logger)
|
subject = BaseURLViewModel(serverInfoRepo, versionDataSource, logger)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when saveBaseUrl and getVersionInfo returns result then saves to storage`() = runTest {
|
fun `when saveBaseUrl and getVersionInfo returns result then saves to storage`() = runTest {
|
||||||
coEvery {
|
coEvery {
|
||||||
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
|
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
|
||||||
} returns VersionInfo(true, "0.5.6", true)
|
} returns VersionInfo(TEST_VERSION)
|
||||||
subject.saveBaseUrl(TEST_BASE_URL)
|
subject.saveBaseUrl(TEST_BASE_URL)
|
||||||
advanceUntilIdle()
|
advanceUntilIdle()
|
||||||
coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL)) }
|
coVerify { serverInfoRepo.storeBaseURL(eq(TEST_BASE_URL), eq(TEST_VERSION)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
404
database/schemas/gq.kirmanak.mealient.database.AppDb/3.json
Normal file
404
database/schemas/gq.kirmanak.mealient.database.AppDb/3.json
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 3,
|
||||||
|
"identityHash": "28c896eb34e95c0cff33148178252f72",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "categories",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_categories_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_categories_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "category_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `recipe_id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "categoryId",
|
||||||
|
"columnName": "category_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_category_id_recipe_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_category_recipe_category_id_recipe_id` ON `${TABLE_NAME}` (`category_id`, `recipe_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_category_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "categories",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tags",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tags_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tags_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tag_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`tag_id`, `recipe_id`), FOREIGN KEY(`tag_id`) REFERENCES `tags`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "tagId",
|
||||||
|
"columnName": "tag_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"tag_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tag_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tag_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "tags",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"tag_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_summaries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `image` TEXT, `description` TEXT NOT NULL, `rating` INTEGER, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slug",
|
||||||
|
"columnName": "slug",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "image",
|
||||||
|
"columnName": "image",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateUpdated",
|
||||||
|
"columnName": "date_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeYield",
|
||||||
|
"columnName": "recipe_yield",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_ingredient",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `note` TEXT NOT NULL, `unit` TEXT NOT NULL, `food` TEXT NOT NULL, `disable_amount` INTEGER NOT NULL, `quantity` REAL NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "note",
|
||||||
|
"columnName": "note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unit",
|
||||||
|
"columnName": "unit",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "food",
|
||||||
|
"columnName": "food",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "disableAmount",
|
||||||
|
"columnName": "disable_amount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quantity",
|
||||||
|
"columnName": "quantity",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_instruction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '28c896eb34e95c0cff33148178252f72')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
410
database/schemas/gq.kirmanak.mealient.database.AppDb/4.json
Normal file
410
database/schemas/gq.kirmanak.mealient.database.AppDb/4.json
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 4,
|
||||||
|
"identityHash": "13be83018f147e1f6e864790656da4a7",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "categories",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_categories_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_categories_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "category_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `recipe_id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "categoryId",
|
||||||
|
"columnName": "category_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_category_id_recipe_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_category_recipe_category_id_recipe_id` ON `${TABLE_NAME}` (`category_id`, `recipe_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_category_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "categories",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tags",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tags_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tags_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tag_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`tag_id`, `recipe_id`), FOREIGN KEY(`tag_id`) REFERENCES `tags`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "tagId",
|
||||||
|
"columnName": "tag_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"tag_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tag_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tag_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "tags",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"tag_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_summaries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `image` TEXT, `description` TEXT NOT NULL, `rating` INTEGER, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slug",
|
||||||
|
"columnName": "slug",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "image",
|
||||||
|
"columnName": "image",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateUpdated",
|
||||||
|
"columnName": "date_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageId",
|
||||||
|
"columnName": "image_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeYield",
|
||||||
|
"columnName": "recipe_yield",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_ingredient",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `note` TEXT NOT NULL, `unit` TEXT NOT NULL, `food` TEXT NOT NULL, `disable_amount` INTEGER NOT NULL, `quantity` REAL NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "note",
|
||||||
|
"columnName": "note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "unit",
|
||||||
|
"columnName": "unit",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "food",
|
||||||
|
"columnName": "food",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "disableAmount",
|
||||||
|
"columnName": "disable_amount",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "quantity",
|
||||||
|
"columnName": "quantity",
|
||||||
|
"affinity": "REAL",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_instruction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `title` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "title",
|
||||||
|
"columnName": "title",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '13be83018f147e1f6e864790656da4a7')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
374
database/schemas/gq.kirmanak.mealient.database.AppDb/5.json
Normal file
374
database/schemas/gq.kirmanak.mealient.database.AppDb/5.json
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 5,
|
||||||
|
"identityHash": "e75a1e16503fdf60c62b7f9d17ec0bc6",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "categories",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_categories_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_categories_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "category_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`category_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`category_id`, `recipe_id`), FOREIGN KEY(`category_id`) REFERENCES `categories`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "categoryId",
|
||||||
|
"columnName": "category_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_category_id_recipe_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"category_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_category_recipe_category_id_recipe_id` ON `${TABLE_NAME}` (`category_id`, `recipe_id`)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "index_category_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_category_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "categories",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"category_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tags",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tags_name",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"name"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_tags_name` ON `${TABLE_NAME}` (`name`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "tag_recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag_id` INTEGER NOT NULL, `recipe_id` TEXT NOT NULL, PRIMARY KEY(`tag_id`, `recipe_id`), FOREIGN KEY(`tag_id`) REFERENCES `tags`(`local_id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`recipe_id`) REFERENCES `recipe_summaries`(`remote_id`) ON UPDATE CASCADE ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "tagId",
|
||||||
|
"columnName": "tag_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"tag_id",
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_tag_recipe_recipe_id",
|
||||||
|
"unique": false,
|
||||||
|
"columnNames": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE INDEX IF NOT EXISTS `index_tag_recipe_recipe_id` ON `${TABLE_NAME}` (`recipe_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "tags",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"tag_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"local_id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"table": "recipe_summaries",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "CASCADE",
|
||||||
|
"columns": [
|
||||||
|
"recipe_id"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"remote_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_summaries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `image` TEXT, `description` TEXT NOT NULL, `rating` INTEGER, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slug",
|
||||||
|
"columnName": "slug",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "image",
|
||||||
|
"columnName": "image",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "rating",
|
||||||
|
"columnName": "rating",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateUpdated",
|
||||||
|
"columnName": "date_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageId",
|
||||||
|
"columnName": "image_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeYield",
|
||||||
|
"columnName": "recipe_yield",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_ingredient",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `note` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "note",
|
||||||
|
"columnName": "note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_instruction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e75a1e16503fdf60c62b7f9d17ec0bc6')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
160
database/schemas/gq.kirmanak.mealient.database.AppDb/6.json
Normal file
160
database/schemas/gq.kirmanak.mealient.database.AppDb/6.json
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "f6e28dd617e4d4a6843a7865c9da736d",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "recipe_summaries",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `name` TEXT NOT NULL, `slug` TEXT NOT NULL, `description` TEXT NOT NULL, `date_added` INTEGER NOT NULL, `date_updated` INTEGER NOT NULL, `image_id` TEXT, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "slug",
|
||||||
|
"columnName": "slug",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "description",
|
||||||
|
"columnName": "description",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateUpdated",
|
||||||
|
"columnName": "date_updated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "imageId",
|
||||||
|
"columnName": "image_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`remote_id` TEXT NOT NULL, `recipe_yield` TEXT NOT NULL, PRIMARY KEY(`remote_id`))",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "remoteId",
|
||||||
|
"columnName": "remote_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeYield",
|
||||||
|
"columnName": "recipe_yield",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"remote_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": false
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_ingredient",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `note` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "note",
|
||||||
|
"columnName": "note",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recipe_instruction",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`local_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `recipe_id` TEXT NOT NULL, `text` TEXT NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "localId",
|
||||||
|
"columnName": "local_id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "recipeId",
|
||||||
|
"columnName": "recipe_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "text",
|
||||||
|
"columnName": "text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"local_id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f6e28dd617e4d4a6843a7865c9da736d')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,13 @@
|
|||||||
package gq.kirmanak.mealient.database
|
package gq.kirmanak.mealient.database
|
||||||
|
|
||||||
import androidx.room.AutoMigration
|
import androidx.room.*
|
||||||
import androidx.room.Database
|
import androidx.room.migration.AutoMigrationSpec
|
||||||
import androidx.room.RoomDatabase
|
|
||||||
import androidx.room.TypeConverters
|
|
||||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
import gq.kirmanak.mealient.database.recipe.entity.*
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
version = 2,
|
version = 6,
|
||||||
entities = [
|
entities = [
|
||||||
CategoryEntity::class,
|
|
||||||
CategoryRecipeEntity::class,
|
|
||||||
TagEntity::class,
|
|
||||||
TagRecipeEntity::class,
|
|
||||||
RecipeSummaryEntity::class,
|
RecipeSummaryEntity::class,
|
||||||
RecipeEntity::class,
|
RecipeEntity::class,
|
||||||
RecipeIngredientEntity::class,
|
RecipeIngredientEntity::class,
|
||||||
@@ -21,10 +15,29 @@ import gq.kirmanak.mealient.database.recipe.entity.*
|
|||||||
],
|
],
|
||||||
exportSchema = true,
|
exportSchema = true,
|
||||||
autoMigrations = [
|
autoMigrations = [
|
||||||
AutoMigration(from = 1, to = 2)
|
AutoMigration(from = 1, to = 2),
|
||||||
|
AutoMigration(from = 3, to = 4),
|
||||||
|
AutoMigration(from = 4, to = 5, spec = AppDb.From4To5Migration::class),
|
||||||
|
AutoMigration(from = 5, to = 6, spec = AppDb.From5To6Migration::class),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@TypeConverters(RoomTypeConverters::class)
|
@TypeConverters(RoomTypeConverters::class)
|
||||||
abstract class AppDb : RoomDatabase() {
|
abstract class AppDb : RoomDatabase() {
|
||||||
abstract fun recipeDao(): RecipeDao
|
abstract fun recipeDao(): RecipeDao
|
||||||
|
|
||||||
|
@DeleteColumn(tableName = "recipe_instruction", columnName = "title")
|
||||||
|
@DeleteColumn(tableName = "recipe_ingredient", columnName = "title")
|
||||||
|
@DeleteColumn(tableName = "recipe_ingredient", columnName = "unit")
|
||||||
|
@DeleteColumn(tableName = "recipe_ingredient", columnName = "food")
|
||||||
|
@DeleteColumn(tableName = "recipe_ingredient", columnName = "disable_amount")
|
||||||
|
@DeleteColumn(tableName = "recipe_ingredient", columnName = "quantity")
|
||||||
|
class From4To5Migration : AutoMigrationSpec
|
||||||
|
|
||||||
|
@DeleteColumn(tableName = "recipe_summaries", columnName = "image")
|
||||||
|
@DeleteColumn(tableName = "recipe_summaries", columnName = "rating")
|
||||||
|
@DeleteTable(tableName = "tag_recipe")
|
||||||
|
@DeleteTable(tableName = "tags")
|
||||||
|
@DeleteTable(tableName = "categories")
|
||||||
|
@DeleteTable(tableName = "category_recipe")
|
||||||
|
class From5To6Migration : AutoMigrationSpec
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,8 @@ interface DatabaseModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun createDb(@ApplicationContext context: Context): AppDb =
|
fun createDb(@ApplicationContext context: Context): AppDb =
|
||||||
Room.databaseBuilder(context, AppDb::class.java, "app.db").build()
|
Room.databaseBuilder(context, AppDb::class.java, "app.db")
|
||||||
|
.fallbackToDestructiveMigrationFrom(2)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,54 +6,18 @@ import gq.kirmanak.mealient.database.recipe.entity.*
|
|||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface RecipeDao {
|
interface RecipeDao {
|
||||||
@Query("SELECT * FROM tags")
|
|
||||||
suspend fun queryAllTags(): List<TagEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM categories")
|
|
||||||
suspend fun queryAllCategories(): List<CategoryEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM recipe_summaries ORDER BY date_added DESC")
|
@Query("SELECT * FROM recipe_summaries ORDER BY date_added DESC")
|
||||||
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
|
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity)
|
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertTag(tagEntity: TagEntity): Long
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertTagRecipeEntity(tagRecipeEntity: TagRecipeEntity)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertCategory(categoryEntity: CategoryEntity): Long
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertCategoryRecipeEntity(categoryRecipeEntity: CategoryRecipeEntity)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertTagRecipeEntities(tagRecipeEntities: Set<TagRecipeEntity>)
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
|
||||||
suspend fun insertCategoryRecipeEntities(categoryRecipeEntities: Set<CategoryRecipeEntity>)
|
|
||||||
|
|
||||||
@Query("DELETE FROM recipe_summaries")
|
@Query("DELETE FROM recipe_summaries")
|
||||||
suspend fun removeAllRecipes()
|
suspend fun removeAllRecipes()
|
||||||
|
|
||||||
@Query("DELETE FROM tags")
|
|
||||||
suspend fun removeAllTags()
|
|
||||||
|
|
||||||
@Query("DELETE FROM categories")
|
|
||||||
suspend fun removeAllCategories()
|
|
||||||
|
|
||||||
@Query("SELECT * FROM recipe_summaries ORDER BY date_updated DESC")
|
@Query("SELECT * FROM recipe_summaries ORDER BY date_updated DESC")
|
||||||
suspend fun queryAllRecipes(): List<RecipeSummaryEntity>
|
suspend fun queryAllRecipes(): List<RecipeSummaryEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM category_recipe")
|
|
||||||
suspend fun queryAllCategoryRecipes(): List<CategoryRecipeEntity>
|
|
||||||
|
|
||||||
@Query("SELECT * FROM tag_recipe")
|
|
||||||
suspend fun queryAllTagRecipes(): List<TagRecipeEntity>
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
suspend fun insertRecipe(recipe: RecipeEntity)
|
suspend fun insertRecipe(recipe: RecipeEntity)
|
||||||
|
|
||||||
@@ -66,11 +30,11 @@ interface RecipeDao {
|
|||||||
@Transaction
|
@Transaction
|
||||||
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) // The lint is wrong, the columns are actually used
|
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) // The lint is wrong, the columns are actually used
|
||||||
@Query("SELECT * FROM recipe JOIN recipe_summaries ON recipe.remote_id = recipe_summaries.remote_id JOIN recipe_ingredient ON recipe_ingredient.recipe_id = recipe.remote_id JOIN recipe_instruction ON recipe_instruction.recipe_id = recipe.remote_id WHERE recipe.remote_id = :recipeId")
|
@Query("SELECT * FROM recipe JOIN recipe_summaries ON recipe.remote_id = recipe_summaries.remote_id JOIN recipe_ingredient ON recipe_ingredient.recipe_id = recipe.remote_id JOIN recipe_instruction ON recipe_instruction.recipe_id = recipe.remote_id WHERE recipe.remote_id = :recipeId")
|
||||||
suspend fun queryFullRecipeInfo(recipeId: Long): FullRecipeInfo?
|
suspend fun queryFullRecipeInfo(recipeId: String): FullRecipeEntity?
|
||||||
|
|
||||||
@Query("DELETE FROM recipe_ingredient WHERE recipe_id = :recipeId")
|
@Query("DELETE FROM recipe_ingredient WHERE recipe_id = :recipeId")
|
||||||
suspend fun deleteRecipeIngredients(recipeId: Long)
|
suspend fun deleteRecipeIngredients(recipeId: String)
|
||||||
|
|
||||||
@Query("DELETE FROM recipe_instruction WHERE recipe_id = :recipeId")
|
@Query("DELETE FROM recipe_instruction WHERE recipe_id = :recipeId")
|
||||||
suspend fun deleteRecipeInstructions(recipeId: Long)
|
suspend fun deleteRecipeInstructions(recipeId: String)
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.database.recipe.entity
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "categories", indices = [Index(value = ["name"], unique = true)])
|
|
||||||
data class CategoryEntity(
|
|
||||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
|
||||||
@ColumnInfo(name = "name") val name: String,
|
|
||||||
)
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.database.recipe.entity
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
import androidx.room.Index
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "category_recipe",
|
|
||||||
primaryKeys = ["category_id", "recipe_id"],
|
|
||||||
indices = [Index(value = ["category_id", "recipe_id"], unique = true)],
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
entity = CategoryEntity::class,
|
|
||||||
parentColumns = ["local_id"],
|
|
||||||
childColumns = ["category_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
onUpdate = ForeignKey.CASCADE
|
|
||||||
), ForeignKey(
|
|
||||||
entity = RecipeSummaryEntity::class,
|
|
||||||
parentColumns = ["remote_id"],
|
|
||||||
childColumns = ["recipe_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
onUpdate = ForeignKey.CASCADE
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
data class CategoryRecipeEntity(
|
|
||||||
@ColumnInfo(name = "category_id") val categoryId: Long,
|
|
||||||
@ColumnInfo(name = "recipe_id", index = true) val recipeId: Long
|
|
||||||
)
|
|
||||||
@@ -3,7 +3,7 @@ package gq.kirmanak.mealient.database.recipe.entity
|
|||||||
import androidx.room.Embedded
|
import androidx.room.Embedded
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
|
|
||||||
data class FullRecipeInfo(
|
data class FullRecipeEntity(
|
||||||
@Embedded val recipeEntity: RecipeEntity,
|
@Embedded val recipeEntity: RecipeEntity,
|
||||||
@Relation(
|
@Relation(
|
||||||
parentColumn = "remote_id",
|
parentColumn = "remote_id",
|
||||||
@@ -6,6 +6,6 @@ import androidx.room.PrimaryKey
|
|||||||
|
|
||||||
@Entity(tableName = "recipe")
|
@Entity(tableName = "recipe")
|
||||||
data class RecipeEntity(
|
data class RecipeEntity(
|
||||||
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: Long,
|
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: String,
|
||||||
@ColumnInfo(name = "recipe_yield") val recipeYield: String,
|
@ColumnInfo(name = "recipe_yield") val recipeYield: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,11 +7,6 @@ import androidx.room.PrimaryKey
|
|||||||
@Entity(tableName = "recipe_ingredient")
|
@Entity(tableName = "recipe_ingredient")
|
||||||
data class RecipeIngredientEntity(
|
data class RecipeIngredientEntity(
|
||||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||||
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
@ColumnInfo(name = "recipe_id") val recipeId: String,
|
||||||
@ColumnInfo(name = "title") val title: String,
|
|
||||||
@ColumnInfo(name = "note") val note: String,
|
@ColumnInfo(name = "note") val note: String,
|
||||||
@ColumnInfo(name = "unit") val unit: String,
|
|
||||||
@ColumnInfo(name = "food") val food: String,
|
|
||||||
@ColumnInfo(name = "disable_amount") val disableAmount: Boolean,
|
|
||||||
@ColumnInfo(name = "quantity") val quantity: Int,
|
|
||||||
)
|
)
|
||||||
@@ -7,7 +7,6 @@ import androidx.room.PrimaryKey
|
|||||||
@Entity(tableName = "recipe_instruction")
|
@Entity(tableName = "recipe_instruction")
|
||||||
data class RecipeInstructionEntity(
|
data class RecipeInstructionEntity(
|
||||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||||
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
@ColumnInfo(name = "recipe_id") val recipeId: String,
|
||||||
@ColumnInfo(name = "title") val title: String,
|
|
||||||
@ColumnInfo(name = "text") val text: String,
|
@ColumnInfo(name = "text") val text: String,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,16 +8,11 @@ import kotlinx.datetime.LocalDateTime
|
|||||||
|
|
||||||
@Entity(tableName = "recipe_summaries")
|
@Entity(tableName = "recipe_summaries")
|
||||||
data class RecipeSummaryEntity(
|
data class RecipeSummaryEntity(
|
||||||
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: Long,
|
@PrimaryKey @ColumnInfo(name = "remote_id") val remoteId: String,
|
||||||
@ColumnInfo(name = "name") val name: String,
|
@ColumnInfo(name = "name") val name: String,
|
||||||
@ColumnInfo(name = "slug") val slug: String,
|
@ColumnInfo(name = "slug") val slug: String,
|
||||||
@ColumnInfo(name = "image") val image: String?,
|
|
||||||
@ColumnInfo(name = "description") val description: String,
|
@ColumnInfo(name = "description") val description: String,
|
||||||
@ColumnInfo(name = "rating") val rating: Int?,
|
|
||||||
@ColumnInfo(name = "date_added") val dateAdded: LocalDate,
|
@ColumnInfo(name = "date_added") val dateAdded: LocalDate,
|
||||||
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime
|
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime,
|
||||||
) {
|
@ColumnInfo(name = "image_id") val imageId: String?,
|
||||||
override fun toString(): String {
|
)
|
||||||
return "RecipeSummaryEntity(remoteId=$remoteId, name='$name')"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.database.recipe.entity
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.Index
|
|
||||||
import androidx.room.PrimaryKey
|
|
||||||
|
|
||||||
@Entity(tableName = "tags", indices = [Index(value = ["name"], unique = true)])
|
|
||||||
data class TagEntity(
|
|
||||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
|
||||||
@ColumnInfo(name = "name") val name: String
|
|
||||||
)
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.database.recipe.entity
|
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
|
||||||
import androidx.room.Entity
|
|
||||||
import androidx.room.ForeignKey
|
|
||||||
|
|
||||||
@Entity(
|
|
||||||
tableName = "tag_recipe",
|
|
||||||
primaryKeys = ["tag_id", "recipe_id"],
|
|
||||||
foreignKeys = [ForeignKey(
|
|
||||||
entity = TagEntity::class,
|
|
||||||
parentColumns = ["local_id"],
|
|
||||||
childColumns = ["tag_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
onUpdate = ForeignKey.CASCADE
|
|
||||||
), ForeignKey(
|
|
||||||
entity = RecipeSummaryEntity::class,
|
|
||||||
parentColumns = ["remote_id"],
|
|
||||||
childColumns = ["recipe_id"],
|
|
||||||
onDelete = ForeignKey.CASCADE,
|
|
||||||
onUpdate = ForeignKey.CASCADE
|
|
||||||
)]
|
|
||||||
)
|
|
||||||
data class TagRecipeEntity(
|
|
||||||
@ColumnInfo(name = "tag_id") val tagId: Long,
|
|
||||||
@ColumnInfo(name = "recipe_id", index = true) val recipeId: Long
|
|
||||||
)
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
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
|
||||||
|
* cancellation of coroutines.
|
||||||
|
*/
|
||||||
|
inline fun <T> runCatchingExceptCancel(block: () -> T): Result<T> = try {
|
||||||
|
Result.success(block())
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
throw e
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Result.failure(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
inline fun <reified R> ResponseBody.decode(json: Json): R = json.decodeFromStream(byteStream())
|
||||||
@@ -6,6 +6,12 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0Impl
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.MealieServiceV0
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
@@ -49,9 +55,13 @@ interface DataSourceModule {
|
|||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideMealieService(retrofit: Retrofit): MealieService =
|
fun provideMealieService(retrofit: Retrofit): MealieServiceV0 =
|
||||||
retrofit.create()
|
retrofit.create()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideMealieServiceV1(retrofit: Retrofit): MealieServiceV1 =
|
||||||
|
retrofit.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@@ -64,5 +74,13 @@ interface DataSourceModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@Singleton
|
@Singleton
|
||||||
fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceImpl): MealieDataSource
|
fun bindMealieDataSource(mealientDataSourceImpl: MealieDataSourceV0Impl): MealieDataSourceV0
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindMealieDataSourceV1(mealientDataSourceImpl: MealieDataSourceV1Impl): MealieDataSourceV1
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
fun bindNetworkRequestWrapper(networkRequestWrapperImpl: NetworkRequestWrapperImpl): NetworkRequestWrapper
|
||||||
}
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource
|
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.models.*
|
|
||||||
import gq.kirmanak.mealient.logging.Logger
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import okhttp3.ResponseBody
|
|
||||||
import retrofit2.HttpException
|
|
||||||
import java.net.ConnectException
|
|
||||||
import java.net.SocketTimeoutException
|
|
||||||
import javax.inject.Inject
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class MealieDataSourceImpl @Inject constructor(
|
|
||||||
private val logger: Logger,
|
|
||||||
private val mealieService: MealieService,
|
|
||||||
private val json: Json,
|
|
||||||
) : MealieDataSource {
|
|
||||||
|
|
||||||
override suspend fun addRecipe(
|
|
||||||
baseUrl: String, token: String?, recipe: AddRecipeRequest
|
|
||||||
): String = makeCall(
|
|
||||||
block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) },
|
|
||||||
logMethod = { "addRecipe" },
|
|
||||||
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
|
||||||
).getOrThrowUnauthorized()
|
|
||||||
|
|
||||||
override suspend fun authenticate(
|
|
||||||
baseUrl: String, username: String, password: String
|
|
||||||
): String = makeCall(
|
|
||||||
block = { getToken("$baseUrl/api/auth/token", username, password) },
|
|
||||||
logMethod = { "authenticate" },
|
|
||||||
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
|
||||||
).map { it.accessToken }.getOrElse {
|
|
||||||
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
|
||||||
val errorDetail = errorBody.decode<ErrorDetail>()
|
|
||||||
throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall(
|
|
||||||
block = { getVersion("$baseUrl/api/debug/version") },
|
|
||||||
logMethod = { "getVersionInfo" },
|
|
||||||
logParameters = { "baseUrl = $baseUrl" },
|
|
||||||
).getOrElse {
|
|
||||||
throw when (it) {
|
|
||||||
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
|
||||||
is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it)
|
|
||||||
else -> NetworkError.MalformedUrl(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun requestRecipes(
|
|
||||||
baseUrl: String, token: String?, start: Int, limit: Int
|
|
||||||
): List<GetRecipeSummaryResponse> = makeCall(
|
|
||||||
block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) },
|
|
||||||
logMethod = { "requestRecipes" },
|
|
||||||
logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
|
|
||||||
).getOrThrowUnauthorized()
|
|
||||||
|
|
||||||
override suspend fun requestRecipeInfo(
|
|
||||||
baseUrl: String, token: String?, slug: String
|
|
||||||
): GetRecipeResponse = makeCall(
|
|
||||||
block = { getRecipe("$baseUrl/api/recipes/$slug", token) },
|
|
||||||
logMethod = { "requestRecipeInfo" },
|
|
||||||
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
|
||||||
).getOrThrowUnauthorized()
|
|
||||||
|
|
||||||
private suspend inline fun <T> makeCall(
|
|
||||||
crossinline block: suspend MealieService.() -> T,
|
|
||||||
crossinline logMethod: () -> String,
|
|
||||||
crossinline logParameters: () -> String,
|
|
||||||
): Result<T> {
|
|
||||||
logger.v { "${logMethod()} called with: ${logParameters()}" }
|
|
||||||
return mealieService.runCatching { block() }
|
|
||||||
.onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } }
|
|
||||||
.onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}" } }
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
private inline fun <reified R> ResponseBody.decode(): R = json.decodeFromStream(byteStream())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
|
|
||||||
throw if (it is HttpException && it.code() in listOf(401, 403)) {
|
|
||||||
NetworkError.Unauthorized(it)
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
package gq.kirmanak.mealient.datasource
|
||||||
|
|
||||||
sealed class NetworkError(cause: Throwable) : RuntimeException(cause) {
|
sealed class NetworkError(cause: Throwable) : RuntimeException(cause) {
|
||||||
class Unauthorized(cause: Throwable) : NetworkError(cause)
|
class Unauthorized(cause: Throwable) : NetworkError(cause)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource
|
||||||
|
|
||||||
|
interface NetworkRequestWrapper {
|
||||||
|
|
||||||
|
suspend fun <T> makeCall(
|
||||||
|
block: suspend () -> T,
|
||||||
|
logMethod: () -> String,
|
||||||
|
logParameters: () -> String,
|
||||||
|
): Result<T>
|
||||||
|
|
||||||
|
suspend fun <T> makeCallAndHandleUnauthorized(
|
||||||
|
block: suspend () -> T,
|
||||||
|
logMethod: () -> String,
|
||||||
|
logParameters: () -> String,
|
||||||
|
): T
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.logging.Logger
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class NetworkRequestWrapperImpl @Inject constructor(
|
||||||
|
private val logger: Logger,
|
||||||
|
) : NetworkRequestWrapper {
|
||||||
|
|
||||||
|
override suspend fun <T> makeCall(
|
||||||
|
block: suspend () -> T,
|
||||||
|
logMethod: () -> String,
|
||||||
|
logParameters: () -> String,
|
||||||
|
): Result<T> {
|
||||||
|
logger.v { "${logMethod()} called with: ${logParameters()}" }
|
||||||
|
return runCatchingExceptCancel { block() }
|
||||||
|
.onFailure { logger.e(it) { "${logMethod()} request failed with: ${logParameters()}" } }
|
||||||
|
.onSuccess { logger.d { "${logMethod()} request succeeded with ${logParameters()}, result = $it" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> makeCallAndHandleUnauthorized(
|
||||||
|
block: suspend () -> T,
|
||||||
|
logMethod: () -> String,
|
||||||
|
logParameters: () -> String
|
||||||
|
): T = makeCall(block, logMethod, logParameters).getOrElse {
|
||||||
|
throw if (it is HttpException && it.code() in listOf(401, 403)) {
|
||||||
|
NetworkError.Unauthorized(it)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AddRecipeRequest(
|
|
||||||
@SerialName("name") val name: String = "",
|
|
||||||
@SerialName("description") val description: String = "",
|
|
||||||
@SerialName("image") val image: String = "",
|
|
||||||
@SerialName("recipeYield") val recipeYield: String = "",
|
|
||||||
@SerialName("recipeIngredient") val recipeIngredient: List<AddRecipeIngredient> = emptyList(),
|
|
||||||
@SerialName("recipeInstructions") val recipeInstructions: List<AddRecipeInstruction> = emptyList(),
|
|
||||||
@SerialName("slug") val slug: String = "",
|
|
||||||
@SerialName("filePath") val filePath: String = "",
|
|
||||||
@SerialName("tags") val tags: List<String> = emptyList(),
|
|
||||||
@SerialName("categories") val categories: List<String> = emptyList(),
|
|
||||||
@SerialName("notes") val notes: List<AddRecipeNote> = emptyList(),
|
|
||||||
@SerialName("extras") val extras: Map<String, String> = emptyMap(),
|
|
||||||
@SerialName("assets") val assets: List<String> = emptyList(),
|
|
||||||
@SerialName("settings") val settings: AddRecipeSettings = AddRecipeSettings(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AddRecipeSettings(
|
|
||||||
@SerialName("disableAmount") val disableAmount: Boolean = true,
|
|
||||||
@SerialName("disableComments") val disableComments: Boolean = false,
|
|
||||||
@SerialName("landscapeView") val landscapeView: Boolean = true,
|
|
||||||
@SerialName("public") val public: Boolean = true,
|
|
||||||
@SerialName("showAssets") val showAssets: Boolean = true,
|
|
||||||
@SerialName("showNutrition") val showNutrition: Boolean = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AddRecipeNote(
|
|
||||||
@SerialName("title") val title: String = "",
|
|
||||||
@SerialName("text") val text: String = "",
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AddRecipeInstruction(
|
|
||||||
@SerialName("title") val title: String = "",
|
|
||||||
@SerialName("text") val text: String = "",
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class AddRecipeIngredient(
|
|
||||||
@SerialName("disableAmount") val disableAmount: Boolean = true,
|
|
||||||
@SerialName("food") val food: String? = null,
|
|
||||||
@SerialName("note") val note: String = "",
|
|
||||||
@SerialName("quantity") val quantity: Int = 1,
|
|
||||||
@SerialName("title") val title: String? = null,
|
|
||||||
@SerialName("unit") val unit: String? = null,
|
|
||||||
)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ErrorDetail(@SerialName("detail") val detail: String? = null)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GetRecipeIngredientResponse(
|
|
||||||
@SerialName("title") val title: String = "",
|
|
||||||
@SerialName("note") val note: String = "",
|
|
||||||
@SerialName("unit") val unit: String = "",
|
|
||||||
@SerialName("food") val food: String = "",
|
|
||||||
@SerialName("disableAmount") val disableAmount: Boolean,
|
|
||||||
@SerialName("quantity") val quantity: Int,
|
|
||||||
)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GetRecipeInstructionResponse(
|
|
||||||
@SerialName("title") val title: String = "",
|
|
||||||
@SerialName("text") val text: String,
|
|
||||||
)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GetRecipeResponse(
|
|
||||||
@SerialName("id") val remoteId: Long,
|
|
||||||
@SerialName("name") val name: String,
|
|
||||||
@SerialName("slug") val slug: String,
|
|
||||||
@SerialName("image") val image: String,
|
|
||||||
@SerialName("description") val description: String = "",
|
|
||||||
@SerialName("recipeCategory") val recipeCategories: List<String>,
|
|
||||||
@SerialName("tags") val tags: List<String>,
|
|
||||||
@SerialName("rating") val rating: Int?,
|
|
||||||
@SerialName("dateAdded") val dateAdded: LocalDate,
|
|
||||||
@SerialName("dateUpdated") val dateUpdated: LocalDateTime,
|
|
||||||
@SerialName("recipeYield") val recipeYield: String = "",
|
|
||||||
@SerialName("recipeIngredient") val recipeIngredients: List<GetRecipeIngredientResponse>,
|
|
||||||
@SerialName("recipeInstructions") val recipeInstructions: List<GetRecipeInstructionResponse>,
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.datetime.LocalDate
|
|
||||||
import kotlinx.datetime.LocalDateTime
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GetRecipeSummaryResponse(
|
|
||||||
@SerialName("id") val remoteId: Long,
|
|
||||||
@SerialName("name") val name: String,
|
|
||||||
@SerialName("slug") val slug: String,
|
|
||||||
@SerialName("image") val image: String?,
|
|
||||||
@SerialName("description") val description: String = "",
|
|
||||||
@SerialName("recipeCategory") val recipeCategories: List<String>,
|
|
||||||
@SerialName("tags") val tags: List<String>,
|
|
||||||
@SerialName("rating") val rating: Int?,
|
|
||||||
@SerialName("dateAdded") val dateAdded: LocalDate,
|
|
||||||
@SerialName("dateUpdated") val dateUpdated: LocalDateTime
|
|
||||||
) {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "GetRecipeSummaryResponse(remoteId=$remoteId, name='$name')"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class GetTokenResponse(@SerialName("access_token") val accessToken: String)
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package gq.kirmanak.mealient.datasource.models
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class VersionResponse(
|
|
||||||
@SerialName("production")
|
|
||||||
val production: Boolean,
|
|
||||||
@SerialName("version")
|
|
||||||
val version: String,
|
|
||||||
@SerialName("demoStatus")
|
|
||||||
val demoStatus: Boolean,
|
|
||||||
)
|
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
package gq.kirmanak.mealient.datasource
|
package gq.kirmanak.mealient.datasource.v0
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||||
import gq.kirmanak.mealient.datasource.models.VersionResponse
|
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||||
|
|
||||||
interface MealieDataSource {
|
interface MealieDataSourceV0 {
|
||||||
|
|
||||||
suspend fun addRecipe(
|
suspend fun addRecipe(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
token: String?,
|
token: String?,
|
||||||
recipe: AddRecipeRequest,
|
recipe: AddRecipeRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,18 +24,18 @@ interface MealieDataSource {
|
|||||||
|
|
||||||
suspend fun getVersionInfo(
|
suspend fun getVersionInfo(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
): VersionResponse
|
): VersionResponseV0
|
||||||
|
|
||||||
suspend fun requestRecipes(
|
suspend fun requestRecipes(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
token: String?,
|
token: String?,
|
||||||
start: Int,
|
start: Int,
|
||||||
limit: Int,
|
limit: Int,
|
||||||
): List<GetRecipeSummaryResponse>
|
): List<GetRecipeSummaryResponseV0>
|
||||||
|
|
||||||
suspend fun requestRecipeInfo(
|
suspend fun requestRecipeInfo(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
token: String?,
|
token: String?,
|
||||||
slug: String,
|
slug: String,
|
||||||
): GetRecipeResponse
|
): GetRecipeResponseV0
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v0
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
|
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
||||||
|
import gq.kirmanak.mealient.datasource.decode
|
||||||
|
import gq.kirmanak.mealient.datasource.v0.models.*
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MealieDataSourceV0Impl @Inject constructor(
|
||||||
|
private val networkRequestWrapper: NetworkRequestWrapper,
|
||||||
|
private val service: MealieServiceV0,
|
||||||
|
private val json: Json,
|
||||||
|
) : MealieDataSourceV0 {
|
||||||
|
|
||||||
|
override suspend fun addRecipe(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
recipe: AddRecipeRequestV0,
|
||||||
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.addRecipe("$baseUrl/api/recipes/create", token, recipe) },
|
||||||
|
logMethod = { "addRecipe" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun authenticate(
|
||||||
|
baseUrl: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
): String = networkRequestWrapper.makeCall(
|
||||||
|
block = { service.getToken("$baseUrl/api/auth/token", username, password) },
|
||||||
|
logMethod = { "authenticate" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
||||||
|
).map { it.accessToken }.getOrElse {
|
||||||
|
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
||||||
|
val errorDetailV0 = errorBody.decode<ErrorDetailV0>(json)
|
||||||
|
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getVersionInfo(
|
||||||
|
baseUrl: String
|
||||||
|
): VersionResponseV0 = networkRequestWrapper.makeCall(
|
||||||
|
block = { service.getVersion("$baseUrl/api/debug/version") },
|
||||||
|
logMethod = { "getVersionInfo" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl" },
|
||||||
|
).getOrElse {
|
||||||
|
throw when (it) {
|
||||||
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
|
is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it)
|
||||||
|
else -> NetworkError.MalformedUrl(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun requestRecipes(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
start: Int,
|
||||||
|
limit: Int,
|
||||||
|
): List<GetRecipeSummaryResponseV0> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) },
|
||||||
|
logMethod = { "requestRecipes" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun requestRecipeInfo(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
slug: String,
|
||||||
|
): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) },
|
||||||
|
logMethod = { "requestRecipeInfo" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package gq.kirmanak.mealient.datasource
|
package gq.kirmanak.mealient.datasource.v0
|
||||||
|
|
||||||
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
|
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
|
||||||
import gq.kirmanak.mealient.datasource.models.*
|
import gq.kirmanak.mealient.datasource.v0.models.*
|
||||||
import retrofit2.http.*
|
import retrofit2.http.*
|
||||||
|
|
||||||
interface MealieService {
|
interface MealieServiceV0 {
|
||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST
|
@POST
|
||||||
@@ -12,19 +12,19 @@ interface MealieService {
|
|||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Field("username") username: String,
|
@Field("username") username: String,
|
||||||
@Field("password") password: String,
|
@Field("password") password: String,
|
||||||
): GetTokenResponse
|
): GetTokenResponseV0
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
suspend fun addRecipe(
|
suspend fun addRecipe(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
@Body addRecipeRequest: AddRecipeRequest,
|
@Body addRecipeRequestV0: AddRecipeRequestV0,
|
||||||
): String
|
): String
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
suspend fun getVersion(
|
suspend fun getVersion(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
): VersionResponse
|
): VersionResponseV0
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
suspend fun getRecipeSummary(
|
suspend fun getRecipeSummary(
|
||||||
@@ -32,11 +32,11 @@ interface MealieService {
|
|||||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
@Query("start") start: Int,
|
@Query("start") start: Int,
|
||||||
@Query("limit") limit: Int,
|
@Query("limit") limit: Int,
|
||||||
): List<GetRecipeSummaryResponse>
|
): List<GetRecipeSummaryResponseV0>
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
suspend fun getRecipe(
|
suspend fun getRecipe(
|
||||||
@Url url: String,
|
@Url url: String,
|
||||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
): GetRecipeResponse
|
): GetRecipeResponseV0
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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<AddRecipeIngredientV0>,
|
||||||
|
@SerialName("recipeInstructions") val recipeInstructions: List<AddRecipeInstructionV0>,
|
||||||
|
@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,
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v0.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ErrorDetailV0(@SerialName("detail") val detail: String? = null)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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<GetRecipeIngredientResponseV0>,
|
||||||
|
@SerialName("recipeInstructions") val recipeInstructions: List<GetRecipeInstructionResponseV0>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetRecipeIngredientResponseV0(
|
||||||
|
@SerialName("note") val note: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetRecipeInstructionResponseV0(
|
||||||
|
@SerialName("text") val text: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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,
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v0.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VersionResponseV0(
|
||||||
|
@SerialName("version") val version: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.*
|
||||||
|
|
||||||
|
interface MealieDataSourceV1 {
|
||||||
|
|
||||||
|
suspend fun createRecipe(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
recipe: CreateRecipeRequestV1,
|
||||||
|
): String
|
||||||
|
|
||||||
|
suspend fun updateRecipe(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
slug: String,
|
||||||
|
recipe: UpdateRecipeRequestV1,
|
||||||
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to acquire authentication token using the provided credentials
|
||||||
|
*/
|
||||||
|
suspend fun authenticate(
|
||||||
|
baseUrl: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
): String
|
||||||
|
|
||||||
|
suspend fun getVersionInfo(
|
||||||
|
baseUrl: String,
|
||||||
|
): VersionResponseV1
|
||||||
|
|
||||||
|
suspend fun requestRecipes(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
page: Int,
|
||||||
|
perPage: Int,
|
||||||
|
): List<GetRecipeSummaryResponseV1>
|
||||||
|
|
||||||
|
suspend fun requestRecipeInfo(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
slug: String,
|
||||||
|
): GetRecipeResponseV1
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.NetworkError
|
||||||
|
import gq.kirmanak.mealient.datasource.NetworkRequestWrapper
|
||||||
|
import gq.kirmanak.mealient.datasource.decode
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.*
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class MealieDataSourceV1Impl @Inject constructor(
|
||||||
|
private val networkRequestWrapper: NetworkRequestWrapper,
|
||||||
|
private val service: MealieServiceV1,
|
||||||
|
private val json: Json,
|
||||||
|
) : MealieDataSourceV1 {
|
||||||
|
|
||||||
|
override suspend fun createRecipe(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
recipe: CreateRecipeRequestV1
|
||||||
|
): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.createRecipe("$baseUrl/api/recipes", token, recipe) },
|
||||||
|
logMethod = { "createRecipe" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun updateRecipe(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
slug: String,
|
||||||
|
recipe: UpdateRecipeRequestV1
|
||||||
|
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.updateRecipe("$baseUrl/api/recipes/$slug", token, recipe) },
|
||||||
|
logMethod = { "updateRecipe" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug, recipe = $recipe" }
|
||||||
|
)
|
||||||
|
|
||||||
|
override suspend fun authenticate(
|
||||||
|
baseUrl: String,
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
): String = networkRequestWrapper.makeCall(
|
||||||
|
block = { service.getToken("$baseUrl/api/auth/token", username, password) },
|
||||||
|
logMethod = { "authenticate" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
||||||
|
).map { it.accessToken }.getOrElse {
|
||||||
|
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
||||||
|
val errorDetailV0 = errorBody.decode<ErrorDetailV1>(json)
|
||||||
|
throw if (errorDetailV0.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getVersionInfo(
|
||||||
|
baseUrl: String,
|
||||||
|
): VersionResponseV1 = networkRequestWrapper.makeCall(
|
||||||
|
block = { service.getVersion("$baseUrl/api/app/about") },
|
||||||
|
logMethod = { "getVersionInfo" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl" },
|
||||||
|
).getOrElse {
|
||||||
|
throw when (it) {
|
||||||
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
||||||
|
is SocketTimeoutException, is ConnectException -> NetworkError.NoServerConnection(it)
|
||||||
|
else -> NetworkError.MalformedUrl(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun requestRecipes(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
page: Int,
|
||||||
|
perPage: Int
|
||||||
|
): List<GetRecipeSummaryResponseV1> = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) },
|
||||||
|
logMethod = { "requestRecipes" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, page = $page, perPage = $perPage" }
|
||||||
|
).items
|
||||||
|
|
||||||
|
override suspend fun requestRecipeInfo(
|
||||||
|
baseUrl: String,
|
||||||
|
token: String?,
|
||||||
|
slug: String
|
||||||
|
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
|
||||||
|
block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) },
|
||||||
|
logMethod = { "requestRecipeInfo" },
|
||||||
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1
|
||||||
|
|
||||||
|
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
|
||||||
|
import gq.kirmanak.mealient.datasource.v1.models.*
|
||||||
|
import retrofit2.http.*
|
||||||
|
|
||||||
|
interface MealieServiceV1 {
|
||||||
|
|
||||||
|
@FormUrlEncoded
|
||||||
|
@POST
|
||||||
|
suspend fun getToken(
|
||||||
|
@Url url: String,
|
||||||
|
@Field("username") username: String,
|
||||||
|
@Field("password") password: String,
|
||||||
|
): GetTokenResponseV1
|
||||||
|
|
||||||
|
@POST
|
||||||
|
suspend fun createRecipe(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
@Body addRecipeRequest: CreateRecipeRequestV1,
|
||||||
|
): String
|
||||||
|
|
||||||
|
@PATCH
|
||||||
|
suspend fun updateRecipe(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
@Body addRecipeRequest: UpdateRecipeRequestV1,
|
||||||
|
): GetRecipeResponseV1
|
||||||
|
|
||||||
|
@GET
|
||||||
|
suspend fun getVersion(
|
||||||
|
@Url url: String,
|
||||||
|
): VersionResponseV1
|
||||||
|
|
||||||
|
@GET
|
||||||
|
suspend fun getRecipeSummary(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("perPage") perPage: Int,
|
||||||
|
): GetRecipesResponseV1
|
||||||
|
|
||||||
|
@GET
|
||||||
|
suspend fun getRecipe(
|
||||||
|
@Url url: String,
|
||||||
|
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||||
|
): GetRecipeResponseV1
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CreateRecipeRequestV1(
|
||||||
|
@SerialName("name") val name: String,
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ErrorDetailV1(
|
||||||
|
@SerialName("detail") val detail: String? = null,
|
||||||
|
)
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package gq.kirmanak.mealient.datasource.v1.models
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetRecipeResponseV1(
|
||||||
|
@SerialName("id") val remoteId: String,
|
||||||
|
@SerialName("name") val name: String,
|
||||||
|
@SerialName("recipeYield") val recipeYield: String = "",
|
||||||
|
@SerialName("recipeIngredient") val recipeIngredients: List<GetRecipeIngredientResponseV1>,
|
||||||
|
@SerialName("recipeInstructions") val recipeInstructions: List<GetRecipeInstructionResponseV1>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetRecipeIngredientResponseV1(
|
||||||
|
@SerialName("note") val note: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class GetRecipeInstructionResponseV1(
|
||||||
|
@SerialName("text") val text: String,
|
||||||
|
)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user