@@ -1,7 +1,6 @@
|
||||
package gq.kirmanak.mealient.data.add
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
|
||||
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
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AddRecipeRepo {
|
||||
|
||||
val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
||||
val addRecipeRequestFlow: Flow<AddRecipeInfo>
|
||||
|
||||
suspend fun preserve(recipe: AddRecipeRequest)
|
||||
suspend fun preserve(recipe: AddRecipeInfo)
|
||||
|
||||
suspend fun clear()
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package gq.kirmanak.mealient.data.add.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeStorage
|
||||
import gq.kirmanak.mealient.extensions.toAddRecipeRequest
|
||||
import gq.kirmanak.mealient.extensions.toAddRecipeInfo
|
||||
import gq.kirmanak.mealient.extensions.toDraft
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -20,10 +20,10 @@ class AddRecipeRepoImpl @Inject constructor(
|
||||
private val logger: Logger,
|
||||
) : AddRecipeRepo {
|
||||
|
||||
override val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
||||
get() = addRecipeStorage.updates.map { it.toAddRecipeRequest() }
|
||||
override val addRecipeRequestFlow: Flow<AddRecipeInfo>
|
||||
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" }
|
||||
addRecipeStorage.save(recipe.toDraft())
|
||||
}
|
||||
|
||||
@@ -4,5 +4,5 @@ interface AuthDataSource {
|
||||
/**
|
||||
* Tries to acquire authentication token using the provided credentials
|
||||
*/
|
||||
suspend fun authenticate(username: String, password: String, baseUrl: String): String
|
||||
suspend fun authenticate(username: String, password: String): String
|
||||
}
|
||||
@@ -1,15 +1,28 @@
|
||||
package gq.kirmanak.mealient.data.auth.impl
|
||||
|
||||
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.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthDataSourceImpl @Inject constructor(
|
||||
private val mealieDataSource: MealieDataSource,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
) : AuthDataSource {
|
||||
|
||||
override suspend fun authenticate(username: String, password: String, baseUrl: String): String =
|
||||
mealieDataSource.authenticate(baseUrl, username, password)
|
||||
override suspend fun authenticate(
|
||||
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.AuthRepo
|
||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -15,7 +14,6 @@ import javax.inject.Singleton
|
||||
class AuthRepoImpl @Inject constructor(
|
||||
private val authStorage: AuthStorage,
|
||||
private val authDataSource: AuthDataSource,
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val logger: Logger,
|
||||
) : AuthRepo {
|
||||
|
||||
@@ -24,9 +22,9 @@ class AuthRepoImpl @Inject constructor(
|
||||
|
||||
override suspend fun authenticate(email: String, password: String) {
|
||||
logger.v { "authenticate() called with: email = $email, password = $password" }
|
||||
authDataSource.authenticate(email, password, baseURLStorage.requireBaseURL())
|
||||
.let { AUTH_HEADER_FORMAT.format(it) }
|
||||
.let { authStorage.setAuthHeader(it) }
|
||||
val token = authDataSource.authenticate(email, password)
|
||||
val header = AUTH_HEADER_FORMAT.format(token)
|
||||
authStorage.setAuthHeader(header)
|
||||
authStorage.setEmail(email)
|
||||
authStorage.setPassword(password)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
data class VersionInfo(
|
||||
val production: Boolean,
|
||||
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
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
||||
import gq.kirmanak.mealient.extensions.toVersionInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.extensions.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class MealieDataSourceWrapper @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val authRepo: AuthRepo,
|
||||
private val mealieDataSource: MealieDataSource,
|
||||
) : AddRecipeDataSource, RecipeDataSource, VersionDataSource {
|
||||
private val v0Source: MealieDataSourceV0,
|
||||
private val v1Source: MealieDataSourceV1,
|
||||
) : AddRecipeDataSource, RecipeDataSource {
|
||||
|
||||
override suspend fun addRecipe(recipe: AddRecipeRequest): String =
|
||||
withAuthHeader { token -> addRecipe(getUrl(), token, recipe) }
|
||||
override suspend fun addRecipe(
|
||||
recipe: AddRecipeInfo,
|
||||
): String = makeCall { token, url, version ->
|
||||
when (version) {
|
||||
ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request())
|
||||
ServerVersion.V1 -> {
|
||||
val slug = v1Source.createRecipe(url, token, recipe.toV1CreateRequest())
|
||||
v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest())
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getVersionInfo(baseUrl: String): VersionInfo =
|
||||
mealieDataSource.getVersionInfo(baseUrl).toVersionInfo()
|
||||
override suspend fun requestRecipes(
|
||||
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> =
|
||||
withAuthHeader { token -> requestRecipes(getUrl(), token, start, limit) }
|
||||
override suspend fun requestRecipeInfo(
|
||||
slug: String,
|
||||
): FullRecipeInfo = makeCall { token, url, version ->
|
||||
when (version) {
|
||||
ServerVersion.V0 -> v0Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
ServerVersion.V1 -> v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun requestRecipeInfo(slug: String): GetRecipeResponse =
|
||||
withAuthHeader { token -> requestRecipeInfo(getUrl(), token, slug) }
|
||||
|
||||
private suspend fun getUrl() = baseURLStorage.requireBaseURL()
|
||||
|
||||
private suspend inline fun <T> withAuthHeader(block: MealieDataSource.(String?) -> T): T =
|
||||
mealieDataSource.runCatching { block(authRepo.getAuthHeader()) }.getOrElse {
|
||||
private suspend inline fun <T> makeCall(block: (String?, String, ServerVersion) -> T): T {
|
||||
val authHeader = authRepo.getAuthHeader()
|
||||
val url = serverInfoRepo.requireUrl()
|
||||
val version = serverInfoRepo.getVersion()
|
||||
return runCatchingExceptCancel { block(authHeader, url, version) }.getOrElse {
|
||||
if (it is NetworkError.Unauthorized) {
|
||||
authRepo.invalidateAuthHeader()
|
||||
// Trying again with new authentication header
|
||||
mealieDataSource.block(authRepo.getAuthHeader())
|
||||
val newHeader = authRepo.getAuthHeader()
|
||||
if (newHeader == authHeader) throw it else block(newHeader, url, version)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package gq.kirmanak.mealient.data.recipes
|
||||
|
||||
import androidx.paging.Pager
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
|
||||
interface RecipeRepo {
|
||||
@@ -9,5 +9,5 @@ interface RecipeRepo {
|
||||
|
||||
suspend fun clearLocalData()
|
||||
|
||||
suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo
|
||||
suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity
|
||||
}
|
||||
@@ -1,21 +1,21 @@
|
||||
package gq.kirmanak.mealient.data.recipes.db
|
||||
|
||||
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.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
|
||||
interface RecipeStorage {
|
||||
suspend fun saveRecipes(recipes: List<GetRecipeSummaryResponse>)
|
||||
suspend fun saveRecipes(recipes: List<RecipeSummaryInfo>)
|
||||
|
||||
fun queryRecipes(): PagingSource<Int, RecipeSummaryEntity>
|
||||
|
||||
suspend fun refreshAll(recipes: List<GetRecipeSummaryResponse>)
|
||||
suspend fun refreshAll(recipes: List<RecipeSummaryInfo>)
|
||||
|
||||
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.room.withTransaction
|
||||
import gq.kirmanak.mealient.data.recipes.network.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeSummaryInfo
|
||||
import gq.kirmanak.mealient.database.AppDb
|
||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.extensions.recipeEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeEntity
|
||||
import gq.kirmanak.mealient.extensions.toRecipeIngredientEntity
|
||||
@@ -23,71 +24,14 @@ class RecipeStorageImpl @Inject constructor(
|
||||
private val recipeDao: RecipeDao by lazy { db.recipeDao() }
|
||||
|
||||
override suspend fun saveRecipes(
|
||||
recipes: List<GetRecipeSummaryResponse>
|
||||
recipes: List<RecipeSummaryInfo>
|
||||
) = db.withTransaction {
|
||||
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) {
|
||||
val recipeSummaryEntity = recipe.recipeEntity()
|
||||
recipeDao.insertRecipe(recipeSummaryEntity)
|
||||
|
||||
for (tag in recipe.tags) {
|
||||
val tagId = getIdOrInsert(tagEntities, tag)
|
||||
tagRecipeEntities += TagRecipeEntity(tagId, recipeSummaryEntity.remoteId)
|
||||
}
|
||||
|
||||
for (category in recipe.recipeCategories) {
|
||||
val categoryId = getOrInsert(categoryEntities, category)
|
||||
categoryRecipeEntities += CategoryRecipeEntity(
|
||||
categoryId,
|
||||
recipeSummaryEntity.remoteId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
recipeDao.insertTagRecipeEntities(tagRecipeEntities)
|
||||
recipeDao.insertCategoryRecipeEntities(categoryRecipeEntities)
|
||||
}
|
||||
|
||||
private suspend fun getOrInsert(
|
||||
categoryEntities: MutableSet<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()
|
||||
}
|
||||
|
||||
override suspend fun refreshAll(recipes: List<GetRecipeSummaryResponse>) {
|
||||
override suspend fun refreshAll(recipes: List<RecipeSummaryInfo>) {
|
||||
logger.v { "refreshAll() called with: recipes = $recipes" }
|
||||
db.withTransaction {
|
||||
recipeDao.removeAllRecipes()
|
||||
@@ -108,12 +52,10 @@ class RecipeStorageImpl @Inject constructor(
|
||||
logger.v { "clearAllLocalData() called" }
|
||||
db.withTransaction {
|
||||
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" }
|
||||
db.withTransaction {
|
||||
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" }
|
||||
val fullRecipeInfo = checkNotNull(recipeDao.queryFullRecipeInfo(recipeId)) {
|
||||
"Can't find recipe by id $recipeId in DB"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gq.kirmanak.mealient.data.recipes.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import javax.inject.Inject
|
||||
@@ -8,7 +8,7 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val logger: Logger,
|
||||
) : RecipeImageUrlProvider {
|
||||
|
||||
@@ -16,7 +16,7 @@ class RecipeImageUrlProviderImpl @Inject constructor(
|
||||
logger.v { "generateImageUrl() called with: slug = $slug" }
|
||||
slug?.takeUnless { it.isBlank() } ?: return null
|
||||
val imagePath = IMAGE_PATH_FORMAT.format(slug)
|
||||
val baseUrl = baseURLStorage.getBaseURL()?.takeUnless { it.isEmpty() }
|
||||
val baseUrl = serverInfoRepo.getUrl()?.takeUnless { it.isEmpty() }
|
||||
val result = baseUrl?.toHttpUrlOrNull()
|
||||
?.newBuilder()
|
||||
?.addPathSegments(imagePath)
|
||||
|
||||
@@ -7,9 +7,9 @@ import androidx.paging.PagingConfig
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@@ -38,7 +38,7 @@ class RecipeRepoImpl @Inject constructor(
|
||||
storage.clearAllLocalData()
|
||||
}
|
||||
|
||||
override suspend fun loadRecipeInfo(recipeId: Long, recipeSlug: String): FullRecipeInfo {
|
||||
override suspend fun loadRecipeInfo(recipeId: String, recipeSlug: String): FullRecipeEntity {
|
||||
logger.v { "loadRecipeInfo() called with: recipeId = $recipeId, recipeSlug = $recipeSlug" }
|
||||
|
||||
runCatchingExceptCancel {
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.paging.LoadType.REFRESH
|
||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -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
|
||||
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
|
||||
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 serverVersionKey: Preferences.Key<String>
|
||||
|
||||
val isDisclaimerAcceptedKey: Preferences.Key<Boolean>
|
||||
|
||||
suspend fun <T> getValue(key: Preferences.Key<T>): T?
|
||||
|
||||
@@ -18,6 +18,8 @@ class PreferencesStorageImpl @Inject constructor(
|
||||
|
||||
override val baseUrlKey = stringPreferencesKey("baseUrl")
|
||||
|
||||
override val serverVersionKey = stringPreferencesKey("serverVersion")
|
||||
|
||||
override val isDisclaimerAcceptedKey = booleanPreferencesKey("isDisclaimedAccepted")
|
||||
|
||||
override suspend fun <T> getValue(key: Preferences.Key<T>): T? {
|
||||
|
||||
@@ -4,10 +4,8 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl
|
||||
import gq.kirmanak.mealient.data.network.MealieDataSourceWrapper
|
||||
import gq.kirmanak.mealient.data.baseurl.*
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@@ -16,9 +14,13 @@ interface BaseURLModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindVersionDataSource(mealieDataSourceWrapper: MealieDataSourceWrapper): VersionDataSource
|
||||
fun bindVersionDataSource(versionDataSourceImpl: VersionDataSourceImpl): VersionDataSource
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindBaseUrlStorage(baseURLStorageImpl: BaseURLStorageImpl): BaseURLStorage
|
||||
fun bindBaseUrlStorage(baseURLStorageImpl: ServerInfoStorageImpl): ServerInfoStorage
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindServerInfoRepo(serverInfoRepoImpl: ServerInfoRepoImpl): ServerInfoRepo
|
||||
}
|
||||
@@ -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 dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||
import gq.kirmanak.mealient.databinding.FragmentAddRecipeBinding
|
||||
import gq.kirmanak.mealient.databinding.ViewSingleInputBinding
|
||||
import gq.kirmanak.mealient.datasource.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.collectWhenViewResumed
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
@@ -122,14 +122,15 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
||||
|
||||
private fun saveValues() = with(binding) {
|
||||
logger.v { "saveValues() called" }
|
||||
val instructions = parseInputRows(instructionsFlow).map { AddRecipeInstruction(text = it) }
|
||||
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredient(note = it) }
|
||||
val settings = AddRecipeSettings(
|
||||
val instructions =
|
||||
parseInputRows(instructionsFlow).map { AddRecipeInstructionInfo(text = it) }
|
||||
val ingredients = parseInputRows(ingredientsFlow).map { AddRecipeIngredientInfo(note = it) }
|
||||
val settings = AddRecipeSettingsInfo(
|
||||
public = publicRecipe.isChecked,
|
||||
disableComments = disableComments.isChecked,
|
||||
)
|
||||
viewModel.preserve(
|
||||
AddRecipeRequest(
|
||||
AddRecipeInfo(
|
||||
name = recipeNameInput.text.toString(),
|
||||
description = recipeDescriptionInput.text.toString(),
|
||||
recipeYield = recipeYieldInput.text.toString(),
|
||||
@@ -148,7 +149,7 @@ class AddRecipeFragment : Fragment(R.layout.fragment_add_recipe) {
|
||||
.filterNot { it.isBlank() }
|
||||
.toList()
|
||||
|
||||
private fun onSavedInputLoaded(request: AddRecipeRequest) = with(binding) {
|
||||
private fun onSavedInputLoaded(request: AddRecipeInfo) = with(binding) {
|
||||
logger.v { "onSavedInputLoaded() called with: request = $request" }
|
||||
recipeNameInput.setText(request.name)
|
||||
recipeDescriptionInput.setText(request.description)
|
||||
|
||||
@@ -3,9 +3,9 @@ package gq.kirmanak.mealient.ui.add
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -23,8 +23,8 @@ class AddRecipeViewModel @Inject constructor(
|
||||
private val _addRecipeResultChannel = Channel<Boolean>(Channel.UNLIMITED)
|
||||
val addRecipeResult: Flow<Boolean> get() = _addRecipeResultChannel.receiveAsFlow()
|
||||
|
||||
private val _preservedAddRecipeRequestChannel = Channel<AddRecipeRequest>(Channel.UNLIMITED)
|
||||
val preservedAddRecipeRequest: Flow<AddRecipeRequest>
|
||||
private val _preservedAddRecipeRequestChannel = Channel<AddRecipeInfo>(Channel.UNLIMITED)
|
||||
val preservedAddRecipeRequest: Flow<AddRecipeInfo>
|
||||
get() = _preservedAddRecipeRequestChannel.receiveAsFlow()
|
||||
|
||||
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" }
|
||||
viewModelScope.launch { addRecipeRepo.preserve(request) }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.databinding.FragmentAuthenticationBinding
|
||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
||||
import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.ui.OperationUiState
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.ui.OperationUiState
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -10,7 +10,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import gq.kirmanak.mealient.R
|
||||
import gq.kirmanak.mealient.databinding.FragmentBaseUrlBinding
|
||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
||||
import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.extensions.checkIfInputIsEmpty
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.ui.OperationUiState
|
||||
|
||||
@@ -5,9 +5,9 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.ui.OperationUiState
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class BaseURLViewModel @Inject constructor(
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
private val versionDataSource: VersionDataSource,
|
||||
private val logger: Logger,
|
||||
) : ViewModel() {
|
||||
@@ -35,8 +35,8 @@ class BaseURLViewModel @Inject constructor(
|
||||
logger.v { "checkBaseURL() called with: baseURL = $baseURL" }
|
||||
val result = runCatchingExceptCancel {
|
||||
// If it returns proper version info then it must be a Mealie
|
||||
versionDataSource.getVersionInfo(baseURL)
|
||||
baseURLStorage.storeBaseURL(baseURL)
|
||||
val version = versionDataSource.getVersionInfo(baseURL).version
|
||||
serverInfoRepo.storeBaseURL(baseURL, version)
|
||||
}
|
||||
logger.i { "checkBaseURL: result is $result" }
|
||||
_uiState.value = OperationUiState.fromResult(result)
|
||||
|
||||
@@ -44,7 +44,7 @@ class RecipeModelLoader private constructor(
|
||||
options: Options?
|
||||
): String? {
|
||||
logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" }
|
||||
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.slug) }
|
||||
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.imageId) }
|
||||
}
|
||||
|
||||
override fun getHeaders(
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package gq.kirmanak.mealient.ui.recipes.info
|
||||
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeInfo
|
||||
import gq.kirmanak.mealient.database.recipe.entity.FullRecipeEntity
|
||||
|
||||
data class RecipeInfoUiState(
|
||||
val areIngredientsVisible: Boolean = false,
|
||||
val areInstructionsVisible: Boolean = false,
|
||||
val recipeInfo: FullRecipeInfo? = null,
|
||||
val recipeInfo: FullRecipeEntity? = null,
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
@@ -20,7 +20,7 @@ class RecipeInfoViewModel @Inject constructor(
|
||||
private val _uiState = MutableLiveData(RecipeInfoUiState())
|
||||
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" }
|
||||
_uiState.value = RecipeInfoUiState()
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavDirections
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.data.disclaimer.DisclaimerStorage
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -15,7 +15,7 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class SplashViewModel @Inject constructor(
|
||||
private val disclaimerStorage: DisclaimerStorage,
|
||||
private val baseURLStorage: BaseURLStorage,
|
||||
private val serverInfoRepo: ServerInfoRepo,
|
||||
) : ViewModel() {
|
||||
private val _nextDestination = MutableLiveData<NavDirections>()
|
||||
val nextDestination: LiveData<NavDirections> = _nextDestination
|
||||
@@ -25,7 +25,7 @@ class SplashViewModel @Inject constructor(
|
||||
delay(1000)
|
||||
_nextDestination.value = when {
|
||||
!disclaimerStorage.isDisclaimerAccepted() -> SplashFragmentDirections.actionSplashFragmentToDisclaimerFragment()
|
||||
baseURLStorage.getBaseURL() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||
serverInfoRepo.getUrl() == null -> SplashFragmentDirections.actionSplashFragmentToBaseURLFragment()
|
||||
else -> SplashFragmentDirections.actionSplashFragmentToRecipesFragment()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="recipe_id"
|
||||
app:argType="long" />
|
||||
app:argType="string" />
|
||||
</dialog>
|
||||
<fragment
|
||||
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.AuthRepo
|
||||
import gq.kirmanak.mealient.data.auth.AuthStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_PASSWORD
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_TOKEN
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_USERNAME
|
||||
import io.mockk.*
|
||||
@@ -27,7 +29,7 @@ class AuthRepoImplTest {
|
||||
lateinit var dataSource: AuthDataSource
|
||||
|
||||
@MockK
|
||||
lateinit var baseURLStorage: BaseURLStorage
|
||||
lateinit var serverInfoRepo: ServerInfoRepo
|
||||
|
||||
@MockK(relaxUnitFun = true)
|
||||
lateinit var storage: AuthStorage
|
||||
@@ -40,7 +42,7 @@ class AuthRepoImplTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
subject = AuthRepoImpl(storage, dataSource, baseURLStorage, logger)
|
||||
subject = AuthRepoImpl(storage, dataSource, logger)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,14 +53,9 @@ class AuthRepoImplTest {
|
||||
|
||||
@Test
|
||||
fun `when authenticate successfully then saves to storage`() = runTest {
|
||||
coEvery {
|
||||
dataSource.authenticate(
|
||||
eq(TEST_USERNAME),
|
||||
eq(TEST_PASSWORD),
|
||||
eq(TEST_BASE_URL)
|
||||
)
|
||||
} returns TEST_TOKEN
|
||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION
|
||||
coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN
|
||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||
subject.authenticate(TEST_USERNAME, TEST_PASSWORD)
|
||||
coVerifyAll {
|
||||
storage.setAuthHeader(TEST_AUTH_HEADER)
|
||||
@@ -70,9 +67,9 @@ class AuthRepoImplTest {
|
||||
|
||||
@Test
|
||||
fun `when authenticate fails then does not change storage`() = runTest {
|
||||
coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException()
|
||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
||||
runCatching { subject.authenticate("invalid", "") }
|
||||
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||
runCatchingExceptCancel { subject.authenticate("invalid", "") }
|
||||
confirmVerified(storage)
|
||||
}
|
||||
|
||||
@@ -107,22 +104,19 @@ class AuthRepoImplTest {
|
||||
fun `when invalidate with credentials then calls authenticate`() = runTest {
|
||||
coEvery { storage.getEmail() } returns TEST_USERNAME
|
||||
coEvery { storage.getPassword() } returns TEST_PASSWORD
|
||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
||||
coEvery {
|
||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
||||
} returns TEST_TOKEN
|
||||
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION
|
||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||
coEvery { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) } returns TEST_TOKEN
|
||||
subject.invalidateAuthHeader()
|
||||
coVerifyAll {
|
||||
dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD), eq(TEST_BASE_URL))
|
||||
}
|
||||
coVerifyAll { dataSource.authenticate(eq(TEST_USERNAME), eq(TEST_PASSWORD)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when invalidate with credentials and auth fails then clears email`() = runTest {
|
||||
coEvery { storage.getEmail() } returns "invalid"
|
||||
coEvery { storage.getPassword() } returns ""
|
||||
coEvery { baseURLStorage.requireBaseURL() } returns TEST_BASE_URL
|
||||
coEvery { dataSource.authenticate(any(), any(), any()) } throws RuntimeException()
|
||||
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
|
||||
coEvery { dataSource.authenticate(any(), any()) } throws RuntimeException()
|
||||
subject.invalidateAuthHeader()
|
||||
coVerify { storage.setEmail(null) }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package gq.kirmanak.mealient.data.baseurl
|
||||
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.BaseURLStorageImpl
|
||||
import gq.kirmanak.mealient.data.baseurl.impl.ServerInfoStorageImpl
|
||||
import gq.kirmanak.mealient.data.storage.PreferencesStorage
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
@@ -15,20 +15,22 @@ import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class BaseURLStorageImplTest {
|
||||
class ServerInfoStorageImplTest {
|
||||
|
||||
@MockK(relaxUnitFun = true)
|
||||
lateinit var preferencesStorage: PreferencesStorage
|
||||
|
||||
lateinit var subject: BaseURLStorage
|
||||
lateinit var subject: ServerInfoStorage
|
||||
|
||||
private val baseUrlKey = stringPreferencesKey("baseUrlKey")
|
||||
private val serverVersionKey = stringPreferencesKey("serverVersionKey")
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
subject = BaseURLStorageImpl(preferencesStorage)
|
||||
subject = ServerInfoStorageImpl(preferencesStorage)
|
||||
every { preferencesStorage.baseUrlKey } returns baseUrlKey
|
||||
every { preferencesStorage.serverVersionKey } returns serverVersionKey
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,30 +39,21 @@ class BaseURLStorageImplTest {
|
||||
assertThat(subject.getBaseURL()).isNull()
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException::class)
|
||||
fun `when requireBaseURL and preferences storage empty then IllegalStateException`() = runTest {
|
||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns null
|
||||
subject.requireBaseURL()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when getBaseUrl and preferences storage has value then value`() = runTest {
|
||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
|
||||
assertThat(subject.getBaseURL()).isEqualTo("baseUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when requireBaseURL and preferences storage has value then value`() = runTest {
|
||||
coEvery { preferencesStorage.getValue(eq(baseUrlKey)) } returns "baseUrl"
|
||||
assertThat(subject.requireBaseURL()).isEqualTo("baseUrl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when storeBaseURL then calls preferences storage`() = runTest {
|
||||
subject.storeBaseURL("baseUrl")
|
||||
subject.storeBaseURL("baseUrl", "v0.5.6")
|
||||
coVerify {
|
||||
preferencesStorage.baseUrlKey
|
||||
preferencesStorage.storeValues(eq(Pair(baseUrlKey, "baseUrl")))
|
||||
preferencesStorage.storeValues(
|
||||
eq(Pair(baseUrlKey, "baseUrl")),
|
||||
eq(Pair(serverVersionKey, "v0.5.6")),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
package gq.kirmanak.mealient.data.network
|
||||
|
||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.datasource.MealieDataSource
|
||||
import gq.kirmanak.mealient.datasource.models.NetworkError
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.datasource.NetworkError
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_AUTH_HEADER
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||
import gq.kirmanak.mealient.test.RecipeImplTestData.GET_CAKE_RESPONSE
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_SERVER_VERSION
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerifyAll
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
@@ -21,32 +24,37 @@ import java.io.IOException
|
||||
class MealieDataSourceWrapperTest {
|
||||
|
||||
@MockK
|
||||
lateinit var baseURLStorage: BaseURLStorage
|
||||
lateinit var serverInfoRepo: ServerInfoRepo
|
||||
|
||||
@MockK(relaxUnitFun = true)
|
||||
lateinit var authRepo: AuthRepo
|
||||
|
||||
@MockK
|
||||
lateinit var mealieDataSource: MealieDataSource
|
||||
lateinit var v0Source: MealieDataSourceV0
|
||||
|
||||
@MockK
|
||||
lateinit var v1Source: MealieDataSourceV1
|
||||
|
||||
lateinit var subject: MealieDataSourceWrapper
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
subject = MealieDataSourceWrapper(baseURLStorage, authRepo, mealieDataSource)
|
||||
subject = MealieDataSourceWrapper(serverInfoRepo, authRepo, v0Source, v1Source)
|
||||
}
|
||||
|
||||
@Test
|
||||
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 {
|
||||
mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake"))
|
||||
v0Source.requestRecipeInfo(eq(TEST_BASE_URL), isNull(), eq("cake"))
|
||||
} throws NetworkError.Unauthorized(IOException())
|
||||
val successResponse = mockk<GetRecipeResponseV0>(relaxed = true)
|
||||
coEvery {
|
||||
mealieDataSource.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake"))
|
||||
} returns GET_CAKE_RESPONSE
|
||||
v0Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq("cake"))
|
||||
} returns successResponse
|
||||
subject.requestRecipeInfo("cake")
|
||||
coVerifyAll {
|
||||
authRepo.getAuthHeader()
|
||||
|
||||
@@ -3,10 +3,6 @@ package gq.kirmanak.mealient.data.recipes.db
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import gq.kirmanak.mealient.database.AppDb
|
||||
import gq.kirmanak.mealient.database.recipe.entity.CategoryEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.CategoryRecipeEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.TagEntity
|
||||
import gq.kirmanak.mealient.database.recipe.entity.TagRecipeEntity
|
||||
import gq.kirmanak.mealient.test.HiltRobolectricTest
|
||||
import gq.kirmanak.mealient.test.RecipeImplTestData.BREAD_INGREDIENT
|
||||
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_BREAD_RECIPE_INGREDIENT_ENTITY
|
||||
@@ -36,28 +32,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
@Inject
|
||||
lateinit var appDb: AppDb
|
||||
|
||||
@Test
|
||||
fun `when saveRecipes then saves tags`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
val actualTags = appDb.recipeDao().queryAllTags()
|
||||
assertThat(actualTags).containsExactly(
|
||||
TagEntity(localId = 1, name = "gluten"),
|
||||
TagEntity(localId = 2, name = "allergic"),
|
||||
TagEntity(localId = 3, name = "milk")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveRecipes then saves categories`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
val actual = appDb.recipeDao().queryAllCategories()
|
||||
assertThat(actual).containsExactly(
|
||||
CategoryEntity(localId = 1, name = "dessert"),
|
||||
CategoryEntity(localId = 2, name = "tasty"),
|
||||
CategoryEntity(localId = 3, name = "porridge")
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveRecipes then saves recipes`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
@@ -68,30 +42,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveRecipes then saves category recipes`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
val actual = appDb.recipeDao().queryAllCategoryRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
|
||||
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
|
||||
CategoryRecipeEntity(categoryId = 3, recipeId = 2),
|
||||
CategoryRecipeEntity(categoryId = 2, recipeId = 2)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveRecipes then saves tag recipes`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
val actual = appDb.recipeDao().queryAllTagRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
TagRecipeEntity(tagId = 1, recipeId = 1),
|
||||
TagRecipeEntity(tagId = 2, recipeId = 1),
|
||||
TagRecipeEntity(tagId = 3, recipeId = 2),
|
||||
TagRecipeEntity(tagId = 1, recipeId = 2),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when refreshAll then old recipes aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
@@ -100,28 +50,6 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
assertThat(actual).containsExactly(CAKE_RECIPE_SUMMARY_ENTITY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when refreshAll then old category recipes aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
||||
val actual = appDb.recipeDao().queryAllCategoryRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
CategoryRecipeEntity(categoryId = 1, recipeId = 1),
|
||||
CategoryRecipeEntity(categoryId = 2, recipeId = 1),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when refreshAll then old tag recipes aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
subject.refreshAll(listOf(RECIPE_SUMMARY_CAKE))
|
||||
val actual = appDb.recipeDao().queryAllTagRecipes()
|
||||
assertThat(actual).containsExactly(
|
||||
TagRecipeEntity(tagId = 1, recipeId = 1),
|
||||
TagRecipeEntity(tagId = 2, recipeId = 1),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearAllLocalData then recipes aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
@@ -130,27 +58,11 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
assertThat(actual).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearAllLocalData then categories aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
subject.clearAllLocalData()
|
||||
val actual = appDb.recipeDao().queryAllCategories()
|
||||
assertThat(actual).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when clearAllLocalData then tags aren't preserved`() = runTest {
|
||||
subject.saveRecipes(TEST_RECIPE_SUMMARIES)
|
||||
subject.clearAllLocalData()
|
||||
val actual = appDb.recipeDao().queryAllTags()
|
||||
assertThat(actual).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveRecipeInfo then saves recipe info`() = runTest {
|
||||
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE))
|
||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo("1")
|
||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||
}
|
||||
|
||||
@@ -159,7 +71,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.saveRecipes(listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE))
|
||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||
subject.saveRecipeInfo(GET_PORRIDGE_RESPONSE)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo(2)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo("2")
|
||||
assertThat(actual).isEqualTo(FULL_PORRIDGE_INFO_ENTITY)
|
||||
}
|
||||
|
||||
@@ -169,7 +81,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||
val newRecipe = GET_CAKE_RESPONSE.copy(recipeIngredients = listOf(BREAD_INGREDIENT))
|
||||
subject.saveRecipeInfo(newRecipe)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeIngredients
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeIngredients
|
||||
val expected = listOf(CAKE_BREAD_RECIPE_INGREDIENT_ENTITY.copy(localId = 3))
|
||||
assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
@@ -180,7 +92,7 @@ class RecipeStorageImplTest : HiltRobolectricTest() {
|
||||
subject.saveRecipeInfo(GET_CAKE_RESPONSE)
|
||||
val newRecipe = GET_CAKE_RESPONSE.copy(recipeInstructions = listOf(MIX_INSTRUCTION))
|
||||
subject.saveRecipeInfo(newRecipe)
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo(1)?.recipeInstructions
|
||||
val actual = appDb.recipeDao().queryFullRecipeInfo("1")?.recipeInstructions
|
||||
val expected = listOf(MIX_CAKE_RECIPE_INSTRUCTION_ENTITY.copy(localId = 3))
|
||||
assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gq.kirmanak.mealient.data.recipes.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import gq.kirmanak.mealient.data.baseurl.BaseURLStorage
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerInfoRepo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
@@ -17,7 +17,7 @@ class RecipeImageUrlProviderImplTest {
|
||||
lateinit var subject: RecipeImageUrlProvider
|
||||
|
||||
@MockK
|
||||
lateinit var baseURLStorage: BaseURLStorage
|
||||
lateinit var serverInfoRepo: ServerInfoRepo
|
||||
|
||||
@MockK(relaxUnitFun = true)
|
||||
lateinit var logger: Logger
|
||||
@@ -25,7 +25,7 @@ class RecipeImageUrlProviderImplTest {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
subject = RecipeImageUrlProviderImpl(baseURLStorage, logger)
|
||||
subject = RecipeImageUrlProviderImpl(serverInfoRepo, logger)
|
||||
prepareBaseURL("https://google.com/")
|
||||
}
|
||||
|
||||
@@ -81,6 +81,6 @@ class RecipeImageUrlProviderImplTest {
|
||||
}
|
||||
|
||||
private fun prepareBaseURL(baseURL: String?) {
|
||||
coEvery { baseURLStorage.getBaseURL() } returns baseURL
|
||||
coEvery { serverInfoRepo.getUrl() } returns baseURL
|
||||
}
|
||||
}
|
||||
@@ -47,24 +47,24 @@ class RecipeRepoImplTest {
|
||||
@Test
|
||||
fun `when loadRecipeInfo then loads recipe`() = runTest {
|
||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
||||
val actual = subject.loadRecipeInfo(1, "cake")
|
||||
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||
val actual = subject.loadRecipeInfo("1", "cake")
|
||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when loadRecipeInfo then saves to DB`() = runTest {
|
||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } returns GET_CAKE_RESPONSE
|
||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
||||
subject.loadRecipeInfo(1, "cake")
|
||||
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||
subject.loadRecipeInfo("1", "cake")
|
||||
coVerify { storage.saveRecipeInfo(eq(GET_CAKE_RESPONSE)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when loadRecipeInfo with error then loads from DB`() = runTest {
|
||||
coEvery { dataSource.requestRecipeInfo(eq("cake")) } throws RuntimeException()
|
||||
coEvery { storage.queryRecipeInfo(eq(1)) } returns FULL_CAKE_INFO_ENTITY
|
||||
val actual = subject.loadRecipeInfo(1, "cake")
|
||||
coEvery { storage.queryRecipeInfo(eq("1")) } returns FULL_CAKE_INFO_ENTITY
|
||||
val actual = subject.loadRecipeInfo("1", "cake")
|
||||
assertThat(actual).isEqualTo(FULL_CAKE_INFO_ENTITY)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.google.common.truth.Truth.assertThat
|
||||
import gq.kirmanak.mealient.data.recipes.db.RecipeStorage
|
||||
import gq.kirmanak.mealient.data.recipes.network.RecipeDataSource
|
||||
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
|
||||
import gq.kirmanak.mealient.datasource.models.NetworkError.Unauthorized
|
||||
import gq.kirmanak.mealient.datasource.NetworkError.Unauthorized
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.test.RecipeImplTestData.TEST_RECIPE_SUMMARIES
|
||||
import io.mockk.MockKAnnotations
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package gq.kirmanak.mealient.extensions
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeIngredient
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeInstruction
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeSettings
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeIngredientInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeInstructionInfo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeSettingsInfo
|
||||
import gq.kirmanak.mealient.datastore.recipe.AddRecipeDraft
|
||||
import org.junit.Test
|
||||
|
||||
@@ -22,42 +22,42 @@ class RemoteToLocalMappingsTest {
|
||||
areCommentsDisabled = true,
|
||||
)
|
||||
|
||||
val expected = AddRecipeRequest(
|
||||
val expected = AddRecipeInfo(
|
||||
name = "Recipe name",
|
||||
description = "Recipe description",
|
||||
recipeYield = "Recipe yield",
|
||||
recipeIngredient = listOf(
|
||||
AddRecipeIngredient(note = "Recipe ingredient 1"),
|
||||
AddRecipeIngredient(note = "Recipe ingredient 2")
|
||||
AddRecipeIngredientInfo(note = "Recipe ingredient 1"),
|
||||
AddRecipeIngredientInfo(note = "Recipe ingredient 2")
|
||||
),
|
||||
recipeInstructions = listOf(
|
||||
AddRecipeInstruction(text = "Recipe instruction 1"),
|
||||
AddRecipeInstruction(text = "Recipe instruction 2")
|
||||
AddRecipeInstructionInfo(text = "Recipe instruction 1"),
|
||||
AddRecipeInstructionInfo(text = "Recipe instruction 2")
|
||||
),
|
||||
settings = AddRecipeSettings(
|
||||
settings = AddRecipeSettingsInfo(
|
||||
public = false,
|
||||
disableComments = true,
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(input.toAddRecipeRequest()).isEqualTo(expected)
|
||||
assertThat(input.toAddRecipeInfo()).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when toDraft then fills fields correctly`() {
|
||||
val request = AddRecipeRequest(
|
||||
val request = AddRecipeInfo(
|
||||
name = "Recipe name",
|
||||
description = "Recipe description",
|
||||
recipeYield = "Recipe yield",
|
||||
recipeIngredient = listOf(
|
||||
AddRecipeIngredient(note = "Recipe ingredient 1"),
|
||||
AddRecipeIngredient(note = "Recipe ingredient 2")
|
||||
AddRecipeIngredientInfo(note = "Recipe ingredient 1"),
|
||||
AddRecipeIngredientInfo(note = "Recipe ingredient 2")
|
||||
),
|
||||
recipeInstructions = listOf(
|
||||
AddRecipeInstruction(text = "Recipe instruction 1"),
|
||||
AddRecipeInstruction(text = "Recipe instruction 2")
|
||||
AddRecipeInstructionInfo(text = "Recipe instruction 1"),
|
||||
AddRecipeInstructionInfo(text = "Recipe instruction 2")
|
||||
),
|
||||
settings = AddRecipeSettings(
|
||||
settings = AddRecipeSettingsInfo(
|
||||
public = false,
|
||||
disableComments = true,
|
||||
)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package gq.kirmanak.mealient.test
|
||||
|
||||
import gq.kirmanak.mealient.data.baseurl.ServerVersion
|
||||
|
||||
object AuthImplTestData {
|
||||
const val TEST_USERNAME = "TEST_USERNAME"
|
||||
const val TEST_PASSWORD = "TEST_PASSWORD"
|
||||
const val TEST_BASE_URL = "https://example.com/"
|
||||
const val TEST_TOKEN = "TEST_TOKEN"
|
||||
const val TEST_AUTH_HEADER = "Bearer TEST_TOKEN"
|
||||
const val TEST_URL = "TEST_URL"
|
||||
const val TEST_VERSION = "v0.5.6"
|
||||
val TEST_SERVER_VERSION = ServerVersion.V0
|
||||
}
|
||||
@@ -1,133 +1,95 @@
|
||||
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.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.LocalDateTime
|
||||
|
||||
object RecipeImplTestData {
|
||||
val RECIPE_SUMMARY_CAKE = GetRecipeSummaryResponse(
|
||||
remoteId = 1,
|
||||
val RECIPE_SUMMARY_CAKE = RecipeSummaryInfo(
|
||||
remoteId = "1",
|
||||
name = "Cake",
|
||||
slug = "cake",
|
||||
image = "86",
|
||||
description = "A tasty cake",
|
||||
recipeCategories = listOf("dessert", "tasty"),
|
||||
tags = listOf("gluten", "allergic"),
|
||||
rating = 4,
|
||||
dateAdded = LocalDate.parse("2021-11-13"),
|
||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
||||
imageId = "cake",
|
||||
)
|
||||
|
||||
val RECIPE_SUMMARY_PORRIDGE = GetRecipeSummaryResponse(
|
||||
remoteId = 2,
|
||||
val RECIPE_SUMMARY_PORRIDGE = RecipeSummaryInfo(
|
||||
remoteId = "2",
|
||||
name = "Porridge",
|
||||
slug = "porridge",
|
||||
image = "89",
|
||||
description = "A tasty porridge",
|
||||
recipeCategories = listOf("porridge", "tasty"),
|
||||
tags = listOf("gluten", "milk"),
|
||||
rating = 5,
|
||||
dateAdded = LocalDate.parse("2021-11-12"),
|
||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
||||
imageId = "porridge",
|
||||
)
|
||||
|
||||
val TEST_RECIPE_SUMMARIES = listOf(RECIPE_SUMMARY_CAKE, RECIPE_SUMMARY_PORRIDGE)
|
||||
|
||||
val CAKE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
||||
remoteId = 1,
|
||||
remoteId = "1",
|
||||
name = "Cake",
|
||||
slug = "cake",
|
||||
image = "86",
|
||||
description = "A tasty cake",
|
||||
rating = 4,
|
||||
dateAdded = LocalDate.parse("2021-11-13"),
|
||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13")
|
||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
||||
imageId = "cake",
|
||||
)
|
||||
|
||||
val PORRIDGE_RECIPE_SUMMARY_ENTITY = RecipeSummaryEntity(
|
||||
remoteId = 2,
|
||||
remoteId = "2",
|
||||
name = "Porridge",
|
||||
slug = "porridge",
|
||||
image = "89",
|
||||
description = "A tasty porridge",
|
||||
rating = 5,
|
||||
dateAdded = LocalDate.parse("2021-11-12"),
|
||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
||||
imageId = "porridge",
|
||||
)
|
||||
|
||||
private val SUGAR_INGREDIENT = GetRecipeIngredientResponse(
|
||||
title = "Sugar",
|
||||
private val SUGAR_INGREDIENT = RecipeIngredientInfo(
|
||||
note = "2 oz of white sugar",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = true,
|
||||
quantity = 1
|
||||
)
|
||||
|
||||
val BREAD_INGREDIENT = GetRecipeIngredientResponse(
|
||||
title = "Bread",
|
||||
val BREAD_INGREDIENT = RecipeIngredientInfo(
|
||||
note = "2 oz of white bread",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = false,
|
||||
quantity = 2
|
||||
)
|
||||
|
||||
private val MILK_INGREDIENT = GetRecipeIngredientResponse(
|
||||
title = "Milk",
|
||||
private val MILK_INGREDIENT = RecipeIngredientInfo(
|
||||
note = "2 oz of white milk",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = true,
|
||||
quantity = 3
|
||||
)
|
||||
|
||||
val MIX_INSTRUCTION = GetRecipeInstructionResponse(
|
||||
title = "Mix",
|
||||
val MIX_INSTRUCTION = RecipeInstructionInfo(
|
||||
text = "Mix the ingredients"
|
||||
)
|
||||
|
||||
private val BAKE_INSTRUCTION = GetRecipeInstructionResponse(
|
||||
title = "Bake",
|
||||
private val BAKE_INSTRUCTION = RecipeInstructionInfo(
|
||||
text = "Bake the ingredients"
|
||||
)
|
||||
|
||||
private val BOIL_INSTRUCTION = GetRecipeInstructionResponse(
|
||||
title = "Boil",
|
||||
private val BOIL_INSTRUCTION = RecipeInstructionInfo(
|
||||
text = "Boil the ingredients"
|
||||
)
|
||||
|
||||
val GET_CAKE_RESPONSE = GetRecipeResponse(
|
||||
remoteId = 1,
|
||||
val GET_CAKE_RESPONSE = FullRecipeInfo(
|
||||
remoteId = "1",
|
||||
name = "Cake",
|
||||
slug = "cake",
|
||||
image = "86",
|
||||
description = "A tasty cake",
|
||||
recipeCategories = listOf("dessert", "tasty"),
|
||||
tags = listOf("gluten", "allergic"),
|
||||
rating = 4,
|
||||
dateAdded = LocalDate.parse("2021-11-13"),
|
||||
dateUpdated = LocalDateTime.parse("2021-11-13T15:30:13"),
|
||||
recipeYield = "4 servings",
|
||||
recipeIngredients = listOf(SUGAR_INGREDIENT, BREAD_INGREDIENT),
|
||||
recipeInstructions = listOf(MIX_INSTRUCTION, BAKE_INSTRUCTION)
|
||||
)
|
||||
|
||||
val GET_PORRIDGE_RESPONSE = GetRecipeResponse(
|
||||
remoteId = 2,
|
||||
val GET_PORRIDGE_RESPONSE = FullRecipeInfo(
|
||||
remoteId = "2",
|
||||
name = "Porridge",
|
||||
slug = "porridge",
|
||||
image = "89",
|
||||
description = "A tasty porridge",
|
||||
recipeCategories = listOf("porridge", "tasty"),
|
||||
tags = listOf("gluten", "milk"),
|
||||
rating = 5,
|
||||
dateAdded = LocalDate.parse("2021-11-12"),
|
||||
dateUpdated = LocalDateTime.parse("2021-10-13T17:35:23"),
|
||||
recipeYield = "3 servings",
|
||||
recipeIngredients = listOf(SUGAR_INGREDIENT, MILK_INGREDIENT),
|
||||
recipeInstructions = listOf(MIX_INSTRUCTION, BOIL_INSTRUCTION)
|
||||
@@ -135,46 +97,34 @@ object RecipeImplTestData {
|
||||
|
||||
val MIX_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||
localId = 1,
|
||||
recipeId = 1,
|
||||
title = "Mix",
|
||||
recipeId = "1",
|
||||
text = "Mix the ingredients",
|
||||
)
|
||||
|
||||
private val BAKE_CAKE_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||
localId = 2,
|
||||
recipeId = 1,
|
||||
title = "Bake",
|
||||
recipeId = "1",
|
||||
text = "Bake the ingredients",
|
||||
)
|
||||
|
||||
private val CAKE_RECIPE_ENTITY = RecipeEntity(
|
||||
remoteId = 1,
|
||||
remoteId = "1",
|
||||
recipeYield = "4 servings"
|
||||
)
|
||||
|
||||
private val CAKE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||
localId = 1,
|
||||
recipeId = 1,
|
||||
title = "Sugar",
|
||||
recipeId = "1",
|
||||
note = "2 oz of white sugar",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = true,
|
||||
quantity = 1
|
||||
)
|
||||
|
||||
val CAKE_BREAD_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||
localId = 2,
|
||||
recipeId = 1,
|
||||
title = "Bread",
|
||||
recipeId = "1",
|
||||
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,
|
||||
recipeSummaryEntity = CAKE_RECIPE_SUMMARY_ENTITY,
|
||||
recipeIngredients = listOf(
|
||||
@@ -188,47 +138,35 @@ object RecipeImplTestData {
|
||||
)
|
||||
|
||||
private val PORRIDGE_RECIPE_ENTITY_FULL = RecipeEntity(
|
||||
remoteId = 2,
|
||||
remoteId = "2",
|
||||
recipeYield = "3 servings"
|
||||
)
|
||||
|
||||
private val PORRIDGE_MILK_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||
localId = 4,
|
||||
recipeId = 2,
|
||||
title = "Milk",
|
||||
recipeId = "2",
|
||||
note = "2 oz of white milk",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = true,
|
||||
quantity = 3
|
||||
)
|
||||
|
||||
private val PORRIDGE_SUGAR_RECIPE_INGREDIENT_ENTITY = RecipeIngredientEntity(
|
||||
localId = 3,
|
||||
recipeId = 2,
|
||||
title = "Sugar",
|
||||
recipeId = "2",
|
||||
note = "2 oz of white sugar",
|
||||
unit = "",
|
||||
food = "",
|
||||
disableAmount = true,
|
||||
quantity = 1
|
||||
)
|
||||
|
||||
private val PORRIDGE_MIX_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||
localId = 3,
|
||||
recipeId = 2,
|
||||
title = "Mix",
|
||||
recipeId = "2",
|
||||
text = "Mix the ingredients"
|
||||
)
|
||||
|
||||
private val PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY = RecipeInstructionEntity(
|
||||
localId = 4,
|
||||
recipeId = 2,
|
||||
title = "Boil",
|
||||
recipeId = "2",
|
||||
text = "Boil the ingredients"
|
||||
)
|
||||
|
||||
val FULL_PORRIDGE_INFO_ENTITY = FullRecipeInfo(
|
||||
val FULL_PORRIDGE_INFO_ENTITY = FullRecipeEntity(
|
||||
recipeEntity = PORRIDGE_RECIPE_ENTITY_FULL,
|
||||
recipeSummaryEntity = PORRIDGE_RECIPE_SUMMARY_ENTITY,
|
||||
recipeIngredients = listOf(
|
||||
@@ -240,4 +178,21 @@ object RecipeImplTestData {
|
||||
PORRIDGE_BOIL_RECIPE_INSTRUCTION_ENTITY,
|
||||
)
|
||||
)
|
||||
|
||||
val PORRIDGE_ADD_RECIPE_INFO = AddRecipeInfo(
|
||||
name = "Porridge",
|
||||
description = "Tasty breakfast",
|
||||
recipeYield = "5 servings",
|
||||
recipeIngredient = listOf(
|
||||
AddRecipeIngredientInfo("Milk"),
|
||||
AddRecipeIngredientInfo("Sugar"),
|
||||
AddRecipeIngredientInfo("Salt"),
|
||||
AddRecipeIngredientInfo("Porridge"),
|
||||
),
|
||||
recipeInstructions = listOf(
|
||||
AddRecipeInstructionInfo("Mix"),
|
||||
AddRecipeInstructionInfo("Cook"),
|
||||
),
|
||||
settings = AddRecipeSettingsInfo(disableComments = false, public = true),
|
||||
)
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package gq.kirmanak.mealient.ui.add
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||
import gq.kirmanak.mealient.datasource.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.test.RecipeImplTestData.PORRIDGE_ADD_RECIPE_INFO
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
@@ -61,21 +61,21 @@ class AddRecipeViewModelTest {
|
||||
|
||||
@Test
|
||||
fun `when preserve then doesn't update UI`() {
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest())
|
||||
subject.preserve(AddRecipeRequest())
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO)
|
||||
subject.preserve(PORRIDGE_ADD_RECIPE_INFO)
|
||||
coVerify(inverse = true) { addRecipeRepo.addRecipeRequestFlow }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when preservedAddRecipeRequest without loadPreservedRequest then empty`() = runTest {
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(AddRecipeRequest())
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(PORRIDGE_ADD_RECIPE_INFO)
|
||||
val actual = withTimeoutOrNull(10) { subject.preservedAddRecipeRequest.firstOrNull() }
|
||||
assertThat(actual).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when loadPreservedRequest then updates preservedAddRecipeRequest`() = runTest {
|
||||
val expected = AddRecipeRequest()
|
||||
val expected = PORRIDGE_ADD_RECIPE_INFO
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
||||
subject.loadPreservedRequest()
|
||||
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
||||
@@ -83,7 +83,7 @@ class AddRecipeViewModelTest {
|
||||
|
||||
@Test
|
||||
fun `when clear then updates preservedAddRecipeRequest`() = runTest {
|
||||
val expected = AddRecipeRequest()
|
||||
val expected = PORRIDGE_ADD_RECIPE_INFO
|
||||
coEvery { addRecipeRepo.addRecipeRequestFlow } returns flowOf(expected)
|
||||
subject.clear()
|
||||
assertThat(subject.preservedAddRecipeRequest.first()).isSameInstanceAs(expected)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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.VersionInfo
|
||||
import gq.kirmanak.mealient.logging.Logger
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_BASE_URL
|
||||
import gq.kirmanak.mealient.test.AuthImplTestData.TEST_VERSION
|
||||
import gq.kirmanak.mealient.test.RobolectricTest
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.coEvery
|
||||
@@ -20,7 +21,7 @@ import org.junit.Test
|
||||
class BaseURLViewModelTest : RobolectricTest() {
|
||||
|
||||
@MockK(relaxUnitFun = true)
|
||||
lateinit var baseURLStorage: BaseURLStorage
|
||||
lateinit var serverInfoRepo: ServerInfoRepo
|
||||
|
||||
@MockK
|
||||
lateinit var versionDataSource: VersionDataSource
|
||||
@@ -33,16 +34,16 @@ class BaseURLViewModelTest : RobolectricTest() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
subject = BaseURLViewModel(baseURLStorage, versionDataSource, logger)
|
||||
subject = BaseURLViewModel(serverInfoRepo, versionDataSource, logger)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when saveBaseUrl and getVersionInfo returns result then saves to storage`() = runTest {
|
||||
coEvery {
|
||||
versionDataSource.getVersionInfo(eq(TEST_BASE_URL))
|
||||
} returns VersionInfo(true, "0.5.6", true)
|
||||
} returns VersionInfo(TEST_VERSION)
|
||||
subject.saveBaseUrl(TEST_BASE_URL)
|
||||
advanceUntilIdle()
|
||||
coVerify { baseURLStorage.storeBaseURL(eq(TEST_BASE_URL)) }
|
||||
coVerify { 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
|
||||
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.*
|
||||
import androidx.room.migration.AutoMigrationSpec
|
||||
import gq.kirmanak.mealient.database.recipe.RecipeDao
|
||||
import gq.kirmanak.mealient.database.recipe.entity.*
|
||||
|
||||
@Database(
|
||||
version = 2,
|
||||
version = 6,
|
||||
entities = [
|
||||
CategoryEntity::class,
|
||||
CategoryRecipeEntity::class,
|
||||
TagEntity::class,
|
||||
TagRecipeEntity::class,
|
||||
RecipeSummaryEntity::class,
|
||||
RecipeEntity::class,
|
||||
RecipeIngredientEntity::class,
|
||||
@@ -21,10 +15,29 @@ import gq.kirmanak.mealient.database.recipe.entity.*
|
||||
],
|
||||
exportSchema = true,
|
||||
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)
|
||||
abstract class AppDb : RoomDatabase() {
|
||||
abstract fun recipeDao(): RecipeDao
|
||||
|
||||
@DeleteColumn(tableName = "recipe_instruction", columnName = "title")
|
||||
@DeleteColumn(tableName = "recipe_ingredient", columnName = "title")
|
||||
@DeleteColumn(tableName = "recipe_ingredient", columnName = "unit")
|
||||
@DeleteColumn(tableName = "recipe_ingredient", columnName = "food")
|
||||
@DeleteColumn(tableName = "recipe_ingredient", columnName = "disable_amount")
|
||||
@DeleteColumn(tableName = "recipe_ingredient", columnName = "quantity")
|
||||
class From4To5Migration : AutoMigrationSpec
|
||||
|
||||
@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
|
||||
@Singleton
|
||||
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
|
||||
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")
|
||||
fun queryRecipesByPages(): PagingSource<Int, RecipeSummaryEntity>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertRecipe(recipeSummaryEntity: RecipeSummaryEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertTag(tagEntity: TagEntity): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertTagRecipeEntity(tagRecipeEntity: TagRecipeEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertCategory(categoryEntity: CategoryEntity): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertCategoryRecipeEntity(categoryRecipeEntity: CategoryRecipeEntity)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertTagRecipeEntities(tagRecipeEntities: Set<TagRecipeEntity>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun insertCategoryRecipeEntities(categoryRecipeEntities: Set<CategoryRecipeEntity>)
|
||||
|
||||
@Query("DELETE FROM recipe_summaries")
|
||||
suspend fun removeAllRecipes()
|
||||
|
||||
@Query("DELETE FROM tags")
|
||||
suspend fun removeAllTags()
|
||||
|
||||
@Query("DELETE FROM categories")
|
||||
suspend fun removeAllCategories()
|
||||
|
||||
@Query("SELECT * FROM recipe_summaries ORDER BY date_updated DESC")
|
||||
suspend fun queryAllRecipes(): List<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)
|
||||
suspend fun insertRecipe(recipe: RecipeEntity)
|
||||
|
||||
@@ -66,11 +30,11 @@ interface RecipeDao {
|
||||
@Transaction
|
||||
@SuppressWarnings(RoomWarnings.CURSOR_MISMATCH) // The lint is wrong, the columns are actually used
|
||||
@Query("SELECT * FROM recipe JOIN recipe_summaries ON recipe.remote_id = recipe_summaries.remote_id JOIN recipe_ingredient ON recipe_ingredient.recipe_id = recipe.remote_id JOIN recipe_instruction ON recipe_instruction.recipe_id = recipe.remote_id WHERE recipe.remote_id = :recipeId")
|
||||
suspend fun queryFullRecipeInfo(recipeId: Long): FullRecipeInfo?
|
||||
suspend fun queryFullRecipeInfo(recipeId: String): FullRecipeEntity?
|
||||
|
||||
@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")
|
||||
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.Relation
|
||||
|
||||
data class FullRecipeInfo(
|
||||
data class FullRecipeEntity(
|
||||
@Embedded val recipeEntity: RecipeEntity,
|
||||
@Relation(
|
||||
parentColumn = "remote_id",
|
||||
@@ -6,6 +6,6 @@ import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "recipe")
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -7,11 +7,6 @@ import androidx.room.PrimaryKey
|
||||
@Entity(tableName = "recipe_ingredient")
|
||||
data class RecipeIngredientEntity(
|
||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "recipe_id") val recipeId: 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")
|
||||
data class RecipeInstructionEntity(
|
||||
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "local_id") val localId: Long = 0,
|
||||
@ColumnInfo(name = "recipe_id") val recipeId: Long,
|
||||
@ColumnInfo(name = "title") val title: String,
|
||||
@ColumnInfo(name = "recipe_id") val recipeId: String,
|
||||
@ColumnInfo(name = "text") val text: String,
|
||||
)
|
||||
|
||||
@@ -8,16 +8,11 @@ import kotlinx.datetime.LocalDateTime
|
||||
|
||||
@Entity(tableName = "recipe_summaries")
|
||||
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 = "slug") val slug: String,
|
||||
@ColumnInfo(name = "image") val image: String?,
|
||||
@ColumnInfo(name = "description") val description: String,
|
||||
@ColumnInfo(name = "rating") val rating: Int?,
|
||||
@ColumnInfo(name = "date_added") val dateAdded: LocalDate,
|
||||
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "RecipeSummaryEntity(remoteId=$remoteId, name='$name')"
|
||||
}
|
||||
}
|
||||
@ColumnInfo(name = "date_updated") val dateUpdated: LocalDateTime,
|
||||
@ColumnInfo(name = "image_id") val imageId: 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 = "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.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieDataSourceV0Impl
|
||||
import gq.kirmanak.mealient.datasource.v0.MealieServiceV0
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl
|
||||
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@@ -49,9 +55,13 @@ interface DataSourceModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMealieService(retrofit: Retrofit): MealieService =
|
||||
fun provideMealieService(retrofit: Retrofit): MealieServiceV0 =
|
||||
retrofit.create()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideMealieServiceV1(retrofit: Retrofit): MealieServiceV1 =
|
||||
retrofit.create()
|
||||
}
|
||||
|
||||
@Binds
|
||||
@@ -64,5 +74,13 @@ interface DataSourceModule {
|
||||
|
||||
@Binds
|
||||
@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) {
|
||||
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.models.GetRecipeResponse
|
||||
import gq.kirmanak.mealient.datasource.models.GetRecipeSummaryResponse
|
||||
import gq.kirmanak.mealient.datasource.models.VersionResponse
|
||||
import gq.kirmanak.mealient.datasource.v0.models.AddRecipeRequestV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeResponseV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.GetRecipeSummaryResponseV0
|
||||
import gq.kirmanak.mealient.datasource.v0.models.VersionResponseV0
|
||||
|
||||
interface MealieDataSource {
|
||||
interface MealieDataSourceV0 {
|
||||
|
||||
suspend fun addRecipe(
|
||||
baseUrl: String,
|
||||
token: String?,
|
||||
recipe: AddRecipeRequest,
|
||||
recipe: AddRecipeRequestV0,
|
||||
): String
|
||||
|
||||
/**
|
||||
@@ -24,18 +24,18 @@ interface MealieDataSource {
|
||||
|
||||
suspend fun getVersionInfo(
|
||||
baseUrl: String,
|
||||
): VersionResponse
|
||||
): VersionResponseV0
|
||||
|
||||
suspend fun requestRecipes(
|
||||
baseUrl: String,
|
||||
token: String?,
|
||||
start: Int,
|
||||
limit: Int,
|
||||
): List<GetRecipeSummaryResponse>
|
||||
): List<GetRecipeSummaryResponseV0>
|
||||
|
||||
suspend fun requestRecipeInfo(
|
||||
baseUrl: String,
|
||||
token: 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.models.*
|
||||
import gq.kirmanak.mealient.datasource.v0.models.*
|
||||
import retrofit2.http.*
|
||||
|
||||
interface MealieService {
|
||||
interface MealieServiceV0 {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
@@ -12,19 +12,19 @@ interface MealieService {
|
||||
@Url url: String,
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String,
|
||||
): GetTokenResponse
|
||||
): GetTokenResponseV0
|
||||
|
||||
@POST
|
||||
suspend fun addRecipe(
|
||||
@Url url: String,
|
||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||
@Body addRecipeRequest: AddRecipeRequest,
|
||||
@Body addRecipeRequestV0: AddRecipeRequestV0,
|
||||
): String
|
||||
|
||||
@GET
|
||||
suspend fun getVersion(
|
||||
@Url url: String,
|
||||
): VersionResponse
|
||||
): VersionResponseV0
|
||||
|
||||
@GET
|
||||
suspend fun getRecipeSummary(
|
||||
@@ -32,11 +32,11 @@ interface MealieService {
|
||||
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
|
||||
@Query("start") start: Int,
|
||||
@Query("limit") limit: Int,
|
||||
): List<GetRecipeSummaryResponse>
|
||||
): List<GetRecipeSummaryResponseV0>
|
||||
|
||||
@GET
|
||||
suspend fun getRecipe(
|
||||
@Url url: 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