Implement adding recipes through app
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package gq.kirmanak.mealient.data.add
|
||||
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
|
||||
interface AddRecipeDataSource {
|
||||
|
||||
suspend fun addRecipe(recipe: AddRecipeRequest): String
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package gq.kirmanak.mealient.data.add
|
||||
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AddRecipeRepo {
|
||||
|
||||
val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
||||
|
||||
suspend fun preserve(recipe: AddRecipeRequest)
|
||||
|
||||
suspend fun clear()
|
||||
|
||||
suspend fun saveRecipe(): String
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package gq.kirmanak.mealient.data.add
|
||||
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AddRecipeStorage {
|
||||
|
||||
val updates: Flow<AddRecipeRequest>
|
||||
|
||||
suspend fun save(addRecipeRequest: AddRecipeRequest)
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package gq.kirmanak.mealient.data.add.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AddRecipeDataSourceImpl @Inject constructor(
|
||||
private val addRecipeServiceFactory: ServiceFactory<AddRecipeService>,
|
||||
) : AddRecipeDataSource {
|
||||
|
||||
override suspend fun addRecipe(recipe: AddRecipeRequest): String {
|
||||
Timber.v("addRecipe() called with: recipe = $recipe")
|
||||
val service = addRecipeServiceFactory.provideService()
|
||||
val response = logAndMapErrors(
|
||||
block = { service.addRecipe(recipe) }, logProvider = { "addRecipe: can't add recipe" }
|
||||
)
|
||||
Timber.v("addRecipe() response = $response")
|
||||
return response
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package gq.kirmanak.mealient.data.add.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeDataSource
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeRepo
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeStorage
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AddRecipeRepoImpl @Inject constructor(
|
||||
private val addRecipeDataSource: AddRecipeDataSource,
|
||||
private val addRecipeStorage: AddRecipeStorage,
|
||||
) : AddRecipeRepo {
|
||||
|
||||
override val addRecipeRequestFlow: Flow<AddRecipeRequest>
|
||||
get() = addRecipeStorage.updates
|
||||
|
||||
override suspend fun preserve(recipe: AddRecipeRequest) {
|
||||
Timber.v("preserveRecipe() called with: recipe = $recipe")
|
||||
addRecipeStorage.save(recipe)
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
Timber.v("clear() called")
|
||||
addRecipeStorage.clear()
|
||||
}
|
||||
|
||||
override suspend fun saveRecipe(): String {
|
||||
Timber.v("saveRecipe() called")
|
||||
return addRecipeDataSource.addRecipe(addRecipeRequestFlow.first())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package gq.kirmanak.mealient.data.add.impl
|
||||
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AddRecipeService {
|
||||
|
||||
@POST("/api/recipes/create")
|
||||
suspend fun addRecipe(@Body addRecipeRequest: AddRecipeRequest): String
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package gq.kirmanak.mealient.data.add.impl
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import gq.kirmanak.mealient.data.add.AddRecipeStorage
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeInput
|
||||
import gq.kirmanak.mealient.data.add.models.AddRecipeRequest
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AddRecipeStorageImpl @Inject constructor(
|
||||
private val dataStore: DataStore<AddRecipeInput>,
|
||||
) : AddRecipeStorage {
|
||||
|
||||
override val updates: Flow<AddRecipeRequest>
|
||||
get() = dataStore.data.map { AddRecipeRequest(it) }
|
||||
|
||||
override suspend fun save(addRecipeRequest: AddRecipeRequest) {
|
||||
Timber.v("saveRecipeInput() called with: addRecipeRequest = $addRecipeRequest")
|
||||
dataStore.updateData { addRecipeRequest.toInput() }
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
Timber.v("clearRecipeInput() called")
|
||||
dataStore.updateData { AddRecipeInput.getDefaultInstance() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package gq.kirmanak.mealient.data.add.models
|
||||
|
||||
import androidx.datastore.core.CorruptionException
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.google.protobuf.InvalidProtocolBufferException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
object AddRecipeInputSerializer : Serializer<AddRecipeInput> {
|
||||
override val defaultValue: AddRecipeInput = AddRecipeInput.getDefaultInstance()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): AddRecipeInput = try {
|
||||
AddRecipeInput.parseFrom(input)
|
||||
} catch (e: InvalidProtocolBufferException) {
|
||||
throw CorruptionException("Can't read proto file", e)
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: AddRecipeInput, output: OutputStream) = t.writeTo(output)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package gq.kirmanak.mealient.data.add.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(),
|
||||
) {
|
||||
constructor(input: AddRecipeInput) : this(
|
||||
name = input.recipeName,
|
||||
description = input.recipeDescription,
|
||||
recipeYield = input.recipeYield,
|
||||
recipeIngredient = input.recipeIngredientsList.map { AddRecipeIngredient(note = it) },
|
||||
recipeInstructions = input.recipeInstructionsList.map { AddRecipeInstruction(text = it) },
|
||||
settings = AddRecipeSettings(
|
||||
public = input.isRecipePublic,
|
||||
disableComments = input.areCommentsDisabled,
|
||||
)
|
||||
)
|
||||
|
||||
fun toInput(): AddRecipeInput = AddRecipeInput.newBuilder()
|
||||
.setRecipeName(name)
|
||||
.setRecipeDescription(description)
|
||||
.setRecipeYield(recipeYield)
|
||||
.setIsRecipePublic(settings.public)
|
||||
.setAreCommentsDisabled(settings.disableComments)
|
||||
.addAllRecipeIngredients(recipeIngredient.map { it.note })
|
||||
.addAllRecipeInstructions(recipeInstructions.map { it.text })
|
||||
.build()
|
||||
}
|
||||
|
||||
@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,
|
||||
)
|
||||
@@ -6,8 +6,7 @@ import gq.kirmanak.mealient.data.network.NetworkError.NotMealie
|
||||
import gq.kirmanak.mealient.data.network.NetworkError.Unauthorized
|
||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||
import gq.kirmanak.mealient.extensions.decodeErrorBodyOrNull
|
||||
import gq.kirmanak.mealient.extensions.mapToNetworkError
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
||||
import kotlinx.serialization.json.Json
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
@@ -34,12 +33,10 @@ class AuthDataSourceImpl @Inject constructor(
|
||||
authService: AuthService,
|
||||
username: String,
|
||||
password: String
|
||||
): Response<GetTokenResponse> = runCatchingExceptCancel {
|
||||
authService.getToken(username, password)
|
||||
}.getOrElse {
|
||||
Timber.e(it, "sendRequest: can't request token")
|
||||
throw it.mapToNetworkError()
|
||||
}
|
||||
): Response<GetTokenResponse> = logAndMapErrors(
|
||||
block = { authService.getToken(username = username, password = password) },
|
||||
logProvider = { "sendRequest: can't get token" },
|
||||
)
|
||||
|
||||
private fun parseToken(
|
||||
response: Response<GetTokenResponse>
|
||||
|
||||
@@ -3,8 +3,7 @@ package gq.kirmanak.mealient.data.baseurl.impl
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionDataSource
|
||||
import gq.kirmanak.mealient.data.baseurl.VersionInfo
|
||||
import gq.kirmanak.mealient.data.network.ServiceFactory
|
||||
import gq.kirmanak.mealient.extensions.mapToNetworkError
|
||||
import gq.kirmanak.mealient.extensions.runCatchingExceptCancel
|
||||
import gq.kirmanak.mealient.extensions.logAndMapErrors
|
||||
import gq.kirmanak.mealient.extensions.versionInfo
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
@@ -19,12 +18,10 @@ class VersionDataSourceImpl @Inject constructor(
|
||||
Timber.v("getVersionInfo() called with: baseUrl = $baseUrl")
|
||||
|
||||
val service = serviceFactory.provideService(baseUrl)
|
||||
val response = runCatchingExceptCancel {
|
||||
service.getVersion()
|
||||
}.getOrElse {
|
||||
Timber.e(it, "getVersionInfo: can't request version")
|
||||
throw it.mapToNetworkError()
|
||||
}
|
||||
val response = logAndMapErrors(
|
||||
block = { service.getVersion() },
|
||||
logProvider = { "getVersionInfo: can't request version" }
|
||||
)
|
||||
|
||||
return response.versionInfo()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user