|
|
|
@@ -6,8 +6,8 @@ import kotlinx.serialization.ExperimentalSerializationApi
|
|
|
|
import kotlinx.serialization.SerializationException
|
|
|
|
import kotlinx.serialization.SerializationException
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
import kotlinx.serialization.json.Json
|
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
import kotlinx.serialization.json.decodeFromStream
|
|
|
|
|
|
|
|
import okhttp3.ResponseBody
|
|
|
|
import retrofit2.HttpException
|
|
|
|
import retrofit2.HttpException
|
|
|
|
import retrofit2.Response
|
|
|
|
|
|
|
|
import javax.inject.Inject
|
|
|
|
import javax.inject.Inject
|
|
|
|
import javax.inject.Singleton
|
|
|
|
import javax.inject.Singleton
|
|
|
|
|
|
|
|
|
|
|
|
@@ -20,70 +20,65 @@ class MealieDataSourceImpl @Inject constructor(
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun addRecipe(
|
|
|
|
override suspend fun addRecipe(
|
|
|
|
baseUrl: String, token: String?, recipe: AddRecipeRequest
|
|
|
|
baseUrl: String, token: String?, recipe: AddRecipeRequest
|
|
|
|
): String {
|
|
|
|
): String = makeCall(
|
|
|
|
logger.v { "addRecipe() called with: baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
|
|
|
block = { addRecipe("$baseUrl/api/recipes/create", token, recipe) },
|
|
|
|
return mealieService.runCatching { addRecipe("$baseUrl/api/recipes/create", token, recipe) }
|
|
|
|
logMethod = { "addRecipe" },
|
|
|
|
.onFailure { logger.e(it) { "addRecipe() request failed with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } }
|
|
|
|
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" }
|
|
|
|
.onSuccess { logger.d { "addRecipe() request succeeded with: baseUrl = $baseUrl, token = $token, recipe = $recipe" } }
|
|
|
|
).getOrThrowUnauthorized()
|
|
|
|
.getOrThrowUnauthorized()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun authenticate(
|
|
|
|
override suspend fun authenticate(
|
|
|
|
baseUrl: String, username: String, password: String
|
|
|
|
baseUrl: String, username: String, password: String
|
|
|
|
): String {
|
|
|
|
): String = makeCall(
|
|
|
|
logger.v { "authenticate() called with: baseUrl = $baseUrl, username = $username, password = $password" }
|
|
|
|
block = { getToken("$baseUrl/api/auth/token", username, password) },
|
|
|
|
return mealieService.runCatching { getToken("$baseUrl/api/auth/token", username, password) }
|
|
|
|
logMethod = { "authenticate" },
|
|
|
|
.onFailure { logger.e(it) { "authenticate() request failed with: baseUrl = $baseUrl, username = $username, password = $password" } }
|
|
|
|
logParameters = { "baseUrl = $baseUrl, username = $username, password = $password" }
|
|
|
|
.onSuccess { logger.d { "authenticate() request succeeded with: baseUrl = $baseUrl, username = $username, password = $password" } }
|
|
|
|
).map { it.accessToken }.getOrElse {
|
|
|
|
.mapCatching { parseToken(it) }
|
|
|
|
val errorBody = (it as? HttpException)?.response()?.errorBody() ?: throw it
|
|
|
|
.getOrThrowUnauthorized()
|
|
|
|
val errorDetail = errorBody.decode<ErrorDetail>()
|
|
|
|
|
|
|
|
throw if (errorDetail.detail == "Unauthorized") NetworkError.Unauthorized(it) else it
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getVersionInfo(baseUrl: String): VersionResponse {
|
|
|
|
override suspend fun getVersionInfo(baseUrl: String): VersionResponse = makeCall(
|
|
|
|
logger.v { "getVersionInfo() called with: baseUrl = $baseUrl" }
|
|
|
|
block = { getVersion("$baseUrl/api/debug/version") },
|
|
|
|
return mealieService.runCatching { getVersion("$baseUrl/api/debug/version") }
|
|
|
|
logMethod = { "getVersionInfo" },
|
|
|
|
.onFailure { logger.e(it) { "getVersionInfo() request failed with: baseUrl = $baseUrl" } }
|
|
|
|
logParameters = { "baseUrl = $baseUrl" },
|
|
|
|
.onSuccess { logger.d { "getVersionInfo() request succeeded with: baseUrl = $baseUrl" } }
|
|
|
|
).getOrElse {
|
|
|
|
.getOrThrowUnauthorized()
|
|
|
|
throw when (it) {
|
|
|
|
|
|
|
|
is HttpException, is SerializationException -> NetworkError.NotMealie(it)
|
|
|
|
|
|
|
|
else -> NetworkError.MalformedUrl(it)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun requestRecipes(
|
|
|
|
override suspend fun requestRecipes(
|
|
|
|
baseUrl: String, token: String?, start: Int, limit: Int
|
|
|
|
baseUrl: String, token: String?, start: Int, limit: Int
|
|
|
|
): List<GetRecipeSummaryResponse> {
|
|
|
|
): List<GetRecipeSummaryResponse> =
|
|
|
|
logger.v { "requestRecipes() called with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
|
|
|
|
makeCall(
|
|
|
|
return mealieService.runCatching {
|
|
|
|
block = { getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) },
|
|
|
|
getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit)
|
|
|
|
logMethod = { "requestRecipes" },
|
|
|
|
}
|
|
|
|
logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" }
|
|
|
|
.onFailure { logger.e(it) { "requestRecipes() request failed with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } }
|
|
|
|
).getOrThrowUnauthorized()
|
|
|
|
.onSuccess { logger.d { "requestRecipes() request succeeded with: baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } }
|
|
|
|
|
|
|
|
.getOrThrowUnauthorized()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun requestRecipeInfo(
|
|
|
|
override suspend fun requestRecipeInfo(
|
|
|
|
baseUrl: String, token: String?, slug: String
|
|
|
|
baseUrl: String, token: String?, slug: String
|
|
|
|
): GetRecipeResponse {
|
|
|
|
): GetRecipeResponse = makeCall(
|
|
|
|
logger.v { "requestRecipeInfo() called with: baseUrl = $baseUrl, token = $token, slug = $slug" }
|
|
|
|
block = { getRecipe("$baseUrl/api/recipes/$slug", token) },
|
|
|
|
return mealieService.runCatching { getRecipe("$baseUrl/api/recipes/$slug", token) }
|
|
|
|
logMethod = { "requestRecipeInfo" },
|
|
|
|
.onFailure { logger.e(it) { "requestRecipeInfo() request failed with: baseUrl = $baseUrl, token = $token, slug = $slug" } }
|
|
|
|
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" }
|
|
|
|
.onSuccess { logger.d { "requestRecipeInfo() request succeeded with: baseUrl = $baseUrl, token = $token, slug = $slug" } }
|
|
|
|
).getOrThrowUnauthorized()
|
|
|
|
.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()}" } }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun parseToken(
|
|
|
|
@OptIn(ExperimentalSerializationApi::class)
|
|
|
|
response: Response<GetTokenResponse>
|
|
|
|
private inline fun <reified R> ResponseBody.decode(): R = json.decodeFromStream(byteStream())
|
|
|
|
): String = if (response.isSuccessful) {
|
|
|
|
|
|
|
|
response.body()?.accessToken
|
|
|
|
|
|
|
|
?: throw NetworkError.NotMealie(NullPointerException("Body is null"))
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
val cause = HttpException(response)
|
|
|
|
|
|
|
|
val errorDetail = json.runCatching<Json, ErrorDetail> { decodeErrorBody(response) }
|
|
|
|
|
|
|
|
.onFailure { logger.e(it) { "Can't decode error body" } }
|
|
|
|
|
|
|
|
.getOrNull()
|
|
|
|
|
|
|
|
throw when (errorDetail?.detail) {
|
|
|
|
|
|
|
|
"Unauthorized" -> NetworkError.Unauthorized(cause)
|
|
|
|
|
|
|
|
else -> NetworkError.NotMealie(cause)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
|
|
|
|
private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
|
|
|
|
@@ -93,14 +88,3 @@ private fun <T> Result<T>.getOrThrowUnauthorized(): T = getOrElse {
|
|
|
|
it
|
|
|
|
it
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@OptIn(ExperimentalSerializationApi::class)
|
|
|
|
|
|
|
|
private inline fun <T, reified R> Json.decodeErrorBody(response: Response<T>): R {
|
|
|
|
|
|
|
|
val responseBody = checkNotNull(response.errorBody()) { "Can't decode absent error body" }
|
|
|
|
|
|
|
|
return decodeFromStream(responseBody.byteStream())
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fun Throwable.mapToNetworkError(): NetworkError = when (this) {
|
|
|
|
|
|
|
|
is HttpException, is SerializationException -> NetworkError.NotMealie(this)
|
|
|
|
|
|
|
|
else -> NetworkError.NoServerConnection(this)
|
|
|
|
|
|
|
|
}
|
|
|
|
|