Extract Authorization header to an interceptor

This commit is contained in:
Kirill Kamakin
2022-12-10 08:15:46 +01:00
parent 915fd77daa
commit 54d0c895a9
14 changed files with 103 additions and 106 deletions

View File

@@ -3,6 +3,7 @@ package gq.kirmanak.mealient.data.auth.impl
import gq.kirmanak.mealient.data.auth.AuthDataSource import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthRepo import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.auth.AuthStorage import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.datasource.AuthenticationProvider
import gq.kirmanak.mealient.datasource.runCatchingExceptCancel import gq.kirmanak.mealient.datasource.runCatchingExceptCancel
import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@@ -15,7 +16,7 @@ class AuthRepoImpl @Inject constructor(
private val authStorage: AuthStorage, private val authStorage: AuthStorage,
private val authDataSource: AuthDataSource, private val authDataSource: AuthDataSource,
private val logger: Logger, private val logger: Logger,
) : AuthRepo { ) : AuthRepo, AuthenticationProvider {
override val isAuthorizedFlow: Flow<Boolean> override val isAuthorizedFlow: Flow<Boolean>
get() = authStorage.authHeaderFlow.map { it != null } get() = authStorage.authHeaderFlow.map { it != null }

View File

@@ -35,12 +35,12 @@ class MealieDataSourceWrapper @Inject constructor(
override suspend fun addRecipe( override suspend fun addRecipe(
recipe: AddRecipeInfo, recipe: AddRecipeInfo,
): String = makeCall { token, url, version -> ): String = makeCall { url, version ->
when (version) { when (version) {
ServerVersion.V0 -> v0Source.addRecipe(url, token, recipe.toV0Request()) ServerVersion.V0 -> v0Source.addRecipe(url, recipe.toV0Request())
ServerVersion.V1 -> { ServerVersion.V1 -> {
val slug = v1Source.createRecipe(url, token, recipe.toV1CreateRequest()) val slug = v1Source.createRecipe(url, recipe.toV1CreateRequest())
v1Source.updateRecipe(url, token, slug, recipe.toV1UpdateRequest()) v1Source.updateRecipe(url, slug, recipe.toV1UpdateRequest())
slug slug
} }
} }
@@ -49,53 +49,53 @@ class MealieDataSourceWrapper @Inject constructor(
override suspend fun requestRecipes( override suspend fun requestRecipes(
start: Int, start: Int,
limit: Int, limit: Int,
): List<RecipeSummaryInfo> = makeCall { token, url, version -> ): List<RecipeSummaryInfo> = makeCall { url, version ->
when (version) { when (version) {
ServerVersion.V0 -> { ServerVersion.V0 -> {
v0Source.requestRecipes(url, token, start, limit).map { it.toRecipeSummaryInfo() } v0Source.requestRecipes(url, start, limit).map { it.toRecipeSummaryInfo() }
} }
ServerVersion.V1 -> { 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 // 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 val page = start / limit + 1
v1Source.requestRecipes(url, token, page, limit).map { it.toRecipeSummaryInfo() } v1Source.requestRecipes(url, page, limit).map { it.toRecipeSummaryInfo() }
} }
} }
} }
override suspend fun requestRecipeInfo( override suspend fun requestRecipeInfo(
slug: String, slug: String,
): FullRecipeInfo = makeCall { token, url, version -> ): FullRecipeInfo = makeCall { url, version ->
when (version) { when (version) {
ServerVersion.V0 -> v0Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() ServerVersion.V0 -> v0Source.requestRecipeInfo(url, slug).toFullRecipeInfo()
ServerVersion.V1 -> v1Source.requestRecipeInfo(url, token, slug).toFullRecipeInfo() ServerVersion.V1 -> v1Source.requestRecipeInfo(url, slug).toFullRecipeInfo()
} }
} }
override suspend fun parseRecipeFromURL( override suspend fun parseRecipeFromURL(
parseRecipeURLInfo: ParseRecipeURLInfo, parseRecipeURLInfo: ParseRecipeURLInfo,
): String = makeCall { token, url, version -> ): String = makeCall { url, version ->
when (version) { when (version) {
ServerVersion.V0 -> { ServerVersion.V0 -> {
v0Source.parseRecipeFromURL(url, token, parseRecipeURLInfo.toV0Request()) v0Source.parseRecipeFromURL(url, parseRecipeURLInfo.toV0Request())
} }
ServerVersion.V1 -> { ServerVersion.V1 -> {
v1Source.parseRecipeFromURL(url, token, parseRecipeURLInfo.toV1Request()) v1Source.parseRecipeFromURL(url, parseRecipeURLInfo.toV1Request())
} }
} }
} }
private suspend inline fun <T> makeCall(block: (String?, String, ServerVersion) -> T): T { private suspend inline fun <T> makeCall(block: (String, ServerVersion) -> T): T {
val authHeader = authRepo.getAuthHeader() val authHeader = authRepo.getAuthHeader()
val url = serverInfoRepo.requireUrl() val url = serverInfoRepo.requireUrl()
val version = serverInfoRepo.getVersion() val version = serverInfoRepo.getVersion()
return runCatchingExceptCancel { block(authHeader, url, version) }.getOrElse { return runCatchingExceptCancel { block(url, version) }.getOrElse {
if (it is NetworkError.Unauthorized) { if (it is NetworkError.Unauthorized) {
logger.e { "Unauthorized, trying to invalidate token" } logger.e { "Unauthorized, trying to invalidate token" }
authRepo.invalidateAuthHeader() authRepo.invalidateAuthHeader()
// Trying again with new authentication header // Trying again with new authentication header
val newHeader = authRepo.getAuthHeader() val newHeader = authRepo.getAuthHeader()
logger.e { "New token ${if (newHeader == authHeader) "matches" else "doesn't match"} old token" } logger.e { "New token ${if (newHeader == authHeader) "matches" else "doesn't match"} old token" }
if (newHeader == authHeader) throw it else block(newHeader, url, version) if (newHeader == authHeader) throw it else block(url, version)
} else { } else {
throw it throw it
} }

View File

@@ -14,6 +14,7 @@ import gq.kirmanak.mealient.data.auth.AuthStorage
import gq.kirmanak.mealient.data.auth.impl.AuthDataSourceImpl import gq.kirmanak.mealient.data.auth.impl.AuthDataSourceImpl
import gq.kirmanak.mealient.data.auth.impl.AuthRepoImpl import gq.kirmanak.mealient.data.auth.impl.AuthRepoImpl
import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl
import gq.kirmanak.mealient.datasource.AuthenticationProvider
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module
@@ -37,6 +38,10 @@ interface AuthModule {
@Singleton @Singleton
fun bindAuthRepo(authRepo: AuthRepoImpl): AuthRepo fun bindAuthRepo(authRepo: AuthRepoImpl): AuthRepo
@Binds
@Singleton
fun bindAuthProvider(authRepo: AuthRepoImpl): AuthenticationProvider
@Binds @Binds
@Singleton @Singleton
fun bindAuthStorage(authStorageImpl: AuthStorageImpl): AuthStorage fun bindAuthStorage(authStorageImpl: AuthStorageImpl): AuthStorage

View File

@@ -1,12 +1,12 @@
package gq.kirmanak.mealient.ui.recipes.images package gq.kirmanak.mealient.ui.recipes.images
import com.bumptech.glide.load.Options import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.* import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.ModelCache
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.stream.BaseGlideUrlLoader import com.bumptech.glide.load.model.stream.BaseGlideUrlLoader
import gq.kirmanak.mealient.data.auth.AuthRepo
import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider import gq.kirmanak.mealient.data.recipes.impl.RecipeImageUrlProvider
import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity import gq.kirmanak.mealient.database.recipe.entity.RecipeSummaryEntity
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
import gq.kirmanak.mealient.logging.Logger import gq.kirmanak.mealient.logging.Logger
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.io.InputStream import java.io.InputStream
@@ -16,7 +16,6 @@ import javax.inject.Singleton
class RecipeModelLoader private constructor( class RecipeModelLoader private constructor(
private val recipeImageUrlProvider: RecipeImageUrlProvider, private val recipeImageUrlProvider: RecipeImageUrlProvider,
private val logger: Logger, private val logger: Logger,
private val authRepo: AuthRepo,
concreteLoader: ModelLoader<GlideUrl, InputStream>, concreteLoader: ModelLoader<GlideUrl, InputStream>,
cache: ModelCache<RecipeSummaryEntity, GlideUrl>, cache: ModelCache<RecipeSummaryEntity, GlideUrl>,
) : BaseGlideUrlLoader<RecipeSummaryEntity>(concreteLoader, cache) { ) : BaseGlideUrlLoader<RecipeSummaryEntity>(concreteLoader, cache) {
@@ -25,13 +24,12 @@ class RecipeModelLoader private constructor(
class Factory @Inject constructor( class Factory @Inject constructor(
private val recipeImageUrlProvider: RecipeImageUrlProvider, private val recipeImageUrlProvider: RecipeImageUrlProvider,
private val logger: Logger, private val logger: Logger,
private val authRepo: AuthRepo,
) { ) {
fun build( fun build(
concreteLoader: ModelLoader<GlideUrl, InputStream>, concreteLoader: ModelLoader<GlideUrl, InputStream>,
cache: ModelCache<RecipeSummaryEntity, GlideUrl>, cache: ModelCache<RecipeSummaryEntity, GlideUrl>,
) = RecipeModelLoader(recipeImageUrlProvider, logger, authRepo, concreteLoader, cache) ) = RecipeModelLoader(recipeImageUrlProvider, logger, concreteLoader, cache)
} }
@@ -46,20 +44,4 @@ class RecipeModelLoader private constructor(
logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" } logger.v { "getUrl() called with: model = $model, width = $width, height = $height, options = $options" }
return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.imageId) } return runBlocking { recipeImageUrlProvider.generateImageUrl(model?.imageId) }
} }
override fun getHeaders(
model: RecipeSummaryEntity?,
width: Int,
height: Int,
options: Options?
): Headers? {
val authorization = runBlocking { authRepo.getAuthHeader() }
return if (authorization.isNullOrBlank()) {
super.getHeaders(model, width, height, options)
} else {
LazyHeaders.Builder()
.setHeader(AUTHORIZATION_HEADER_NAME, authorization)
.build()
}
}
} }

View File

@@ -57,11 +57,8 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
fun `when makeCall fails with Unauthorized expect it to invalidate token`() = runTest { fun `when makeCall fails with Unauthorized expect it to invalidate token`() = runTest {
val slug = "porridge" val slug = "porridge"
coEvery { coEvery {
v0Source.requestRecipeInfo(any(), isNull(), any()) v0Source.requestRecipeInfo(any(), any())
} throws NetworkError.Unauthorized(IOException()) } throws NetworkError.Unauthorized(IOException()) andThen PORRIDGE_RECIPE_RESPONSE_V0
coEvery {
v0Source.requestRecipeInfo(any(), eq(TEST_AUTH_HEADER), any())
} returns PORRIDGE_RECIPE_RESPONSE_V0
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER coEvery { authRepo.getAuthHeader() } returns null andThen TEST_AUTH_HEADER
@@ -79,7 +76,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest { fun `when server version v1 expect requestRecipeInfo to call v1`() = runTest {
val slug = "porridge" val slug = "porridge"
coEvery { coEvery {
v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(slug)) v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(slug))
} returns PORRIDGE_RECIPE_RESPONSE_V1 } returns PORRIDGE_RECIPE_RESPONSE_V1
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
@@ -87,7 +84,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
val actual = subject.requestRecipeInfo(slug) val actual = subject.requestRecipeInfo(slug)
coVerify { v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(slug)) } coVerify { v1Source.requestRecipeInfo(eq(TEST_BASE_URL), eq(slug)) }
assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO) assertThat(actual).isEqualTo(PORRIDGE_FULL_RECIPE_INFO)
} }
@@ -95,7 +92,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
@Test @Test
fun `when server version v1 expect requestRecipes to call v1`() = runTest { fun `when server version v1 expect requestRecipes to call v1`() = runTest {
coEvery { coEvery {
v1Source.requestRecipes(any(), any(), any(), any()) v1Source.requestRecipes(any(), any(), any())
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1) } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V1)
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
@@ -106,7 +103,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5) val page = 5 // 0-9 (1), 10-19 (2), 20-29 (3), 30-39 (4), 40-49 (5)
val perPage = 10 val perPage = 10
coVerify { coVerify {
v1Source.requestRecipes(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(page), eq(perPage)) v1Source.requestRecipes(eq(TEST_BASE_URL), eq(page), eq(perPage))
} }
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1)) assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V1))
@@ -115,7 +112,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
@Test @Test
fun `when server version v0 expect requestRecipes to call v0`() = runTest { fun `when server version v0 expect requestRecipes to call v0`() = runTest {
coEvery { coEvery {
v0Source.requestRecipes(any(), any(), any(), any()) v0Source.requestRecipes(any(), any(), any())
} returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0) } returns listOf(PORRIDGE_RECIPE_SUMMARY_RESPONSE_V0)
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
@@ -126,7 +123,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
val actual = subject.requestRecipes(start, limit) val actual = subject.requestRecipes(start, limit)
coVerify { coVerify {
v0Source.requestRecipes(eq(TEST_BASE_URL), eq(TEST_AUTH_HEADER), eq(start), eq(limit)) v0Source.requestRecipes(eq(TEST_BASE_URL), eq(start), eq(limit))
} }
assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0)) assertThat(actual).isEqualTo(listOf(RECIPE_SUMMARY_PORRIDGE_V0))
@@ -134,7 +131,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
@Test(expected = IOException::class) @Test(expected = IOException::class)
fun `when request fails expect addRecipe to rethrow`() = runTest { fun `when request fails expect addRecipe to rethrow`() = runTest {
coEvery { v0Source.addRecipe(any(), any(), any()) } throws IOException() coEvery { v0Source.addRecipe(any(), any()) } throws IOException()
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
@@ -145,7 +142,7 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
fun `when server version v0 expect addRecipe to call v0`() = runTest { fun `when server version v0 expect addRecipe to call v0`() = runTest {
val slug = "porridge" val slug = "porridge"
coEvery { v0Source.addRecipe(any(), any(), any()) } returns slug coEvery { v0Source.addRecipe(any(), any()) } returns slug
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V0
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER coEvery { authRepo.getAuthHeader() } returns TEST_AUTH_HEADER
@@ -155,7 +152,6 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
coVerify { coVerify {
v0Source.addRecipe( v0Source.addRecipe(
eq(TEST_BASE_URL), eq(TEST_BASE_URL),
eq(TEST_AUTH_HEADER),
eq(PORRIDGE_ADD_RECIPE_REQUEST_V0), eq(PORRIDGE_ADD_RECIPE_REQUEST_V0),
) )
} }
@@ -167,9 +163,9 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
fun `when server version v1 expect addRecipe to call v1`() = runTest { fun `when server version v1 expect addRecipe to call v1`() = runTest {
val slug = "porridge" val slug = "porridge"
coEvery { v1Source.createRecipe(any(), any(), any()) } returns slug coEvery { v1Source.createRecipe(any(), any()) } returns slug
coEvery { coEvery {
v1Source.updateRecipe(any(), any(), any(), any()) v1Source.updateRecipe(any(), any(), any())
} returns PORRIDGE_RECIPE_RESPONSE_V1 } returns PORRIDGE_RECIPE_RESPONSE_V1
coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1 coEvery { serverInfoRepo.getVersion() } returns TEST_SERVER_VERSION_V1
coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL coEvery { serverInfoRepo.requireUrl() } returns TEST_BASE_URL
@@ -180,13 +176,11 @@ class MealieDataSourceWrapperTest : BaseUnitTest() {
coVerifySequence { coVerifySequence {
v1Source.createRecipe( v1Source.createRecipe(
eq(TEST_BASE_URL), eq(TEST_BASE_URL),
eq(TEST_AUTH_HEADER),
eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1), eq(PORRIDGE_CREATE_RECIPE_REQUEST_V1),
) )
v1Source.updateRecipe( v1Source.updateRecipe(
eq(TEST_BASE_URL), eq(TEST_BASE_URL),
eq(TEST_AUTH_HEADER),
eq(slug), eq(slug),
eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1), eq(PORRIDGE_UPDATE_RECIPE_REQUEST_V1),
) )

View File

@@ -0,0 +1,6 @@
package gq.kirmanak.mealient.datasource
interface AuthenticationProvider {
suspend fun getAuthHeader(): String?
}

View File

@@ -6,6 +6,8 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
import gq.kirmanak.mealient.datasource.impl.AuthInterceptor
import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl import gq.kirmanak.mealient.datasource.impl.CacheBuilderImpl
import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl import gq.kirmanak.mealient.datasource.impl.NetworkRequestWrapperImpl
import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl import gq.kirmanak.mealient.datasource.impl.OkHttpBuilderImpl
@@ -18,6 +20,7 @@ import gq.kirmanak.mealient.datasource.v1.MealieDataSourceV1Impl
import gq.kirmanak.mealient.datasource.v1.MealieServiceV1 import gq.kirmanak.mealient.datasource.v1.MealieServiceV1
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Converter import retrofit2.Converter
@@ -31,8 +34,6 @@ interface DataSourceModule {
companion object { companion object {
const val AUTHORIZATION_HEADER_NAME = "Authorization"
@Provides @Provides
@Singleton @Singleton
fun provideJson(): Json = Json { fun provideJson(): Json = Json {
@@ -87,4 +88,9 @@ interface DataSourceModule {
@Binds @Binds
@Singleton @Singleton
fun bindNetworkRequestWrapper(networkRequestWrapperImpl: NetworkRequestWrapperImpl): NetworkRequestWrapper fun bindNetworkRequestWrapper(networkRequestWrapperImpl: NetworkRequestWrapperImpl): NetworkRequestWrapper
@Binds
@Singleton
@IntoSet
fun bindAuthInterceptor(authInterceptor: AuthInterceptor): Interceptor
} }

View File

@@ -0,0 +1,32 @@
package gq.kirmanak.mealient.datasource.impl
import gq.kirmanak.mealient.datasource.AuthenticationProvider
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Singleton
@Singleton
class AuthInterceptor @Inject constructor(
private val authenticationProvider: Provider<AuthenticationProvider>,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val token = runBlocking { authenticationProvider.get().getAuthHeader() }
val request = if (token == null) {
chain.request()
} else {
chain.request()
.newBuilder()
.header(HEADER_NAME, token)
.build()
}
return chain.proceed(request)
}
companion object {
private const val HEADER_NAME = "Authorization"
}
}

View File

@@ -10,7 +10,6 @@ interface MealieDataSourceV0 {
suspend fun addRecipe( suspend fun addRecipe(
baseUrl: String, baseUrl: String,
token: String?,
recipe: AddRecipeRequestV0, recipe: AddRecipeRequestV0,
): String ): String
@@ -29,20 +28,17 @@ interface MealieDataSourceV0 {
suspend fun requestRecipes( suspend fun requestRecipes(
baseUrl: String, baseUrl: String,
token: String?,
start: Int, start: Int,
limit: Int, limit: Int,
): List<GetRecipeSummaryResponseV0> ): List<GetRecipeSummaryResponseV0>
suspend fun requestRecipeInfo( suspend fun requestRecipeInfo(
baseUrl: String, baseUrl: String,
token: String?,
slug: String, slug: String,
): GetRecipeResponseV0 ): GetRecipeResponseV0
suspend fun parseRecipeFromURL( suspend fun parseRecipeFromURL(
baseUrl: String, baseUrl: String,
token: String?,
request: ParseRecipeURLRequestV0, request: ParseRecipeURLRequestV0,
): String ): String
} }

View File

@@ -26,12 +26,11 @@ class MealieDataSourceV0Impl @Inject constructor(
override suspend fun addRecipe( override suspend fun addRecipe(
baseUrl: String, baseUrl: String,
token: String?,
recipe: AddRecipeRequestV0, recipe: AddRecipeRequestV0,
): String = networkRequestWrapper.makeCallAndHandleUnauthorized( ): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.addRecipe("$baseUrl/api/recipes/create", token, recipe) }, block = { service.addRecipe("$baseUrl/api/recipes/create", recipe) },
logMethod = { "addRecipe" }, logMethod = { "addRecipe" },
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } logParameters = { "baseUrl = $baseUrl, recipe = $recipe" }
) )
override suspend fun authenticate( override suspend fun authenticate(
@@ -64,33 +63,30 @@ class MealieDataSourceV0Impl @Inject constructor(
override suspend fun requestRecipes( override suspend fun requestRecipes(
baseUrl: String, baseUrl: String,
token: String?,
start: Int, start: Int,
limit: Int, limit: Int,
): List<GetRecipeSummaryResponseV0> = networkRequestWrapper.makeCallAndHandleUnauthorized( ): List<GetRecipeSummaryResponseV0> = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getRecipeSummary("$baseUrl/api/recipes/summary", token, start, limit) }, block = { service.getRecipeSummary("$baseUrl/api/recipes/summary", start, limit) },
logMethod = { "requestRecipes" }, logMethod = { "requestRecipes" },
logParameters = { "baseUrl = $baseUrl, token = $token, start = $start, limit = $limit" } logParameters = { "baseUrl = $baseUrl, start = $start, limit = $limit" }
) )
override suspend fun requestRecipeInfo( override suspend fun requestRecipeInfo(
baseUrl: String, baseUrl: String,
token: String?,
slug: String, slug: String,
): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized( ): GetRecipeResponseV0 = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) }, block = { service.getRecipe("$baseUrl/api/recipes/$slug") },
logMethod = { "requestRecipeInfo" }, logMethod = { "requestRecipeInfo" },
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } logParameters = { "baseUrl = $baseUrl, slug = $slug" }
) )
override suspend fun parseRecipeFromURL( override suspend fun parseRecipeFromURL(
baseUrl: String, baseUrl: String,
token: String?,
request: ParseRecipeURLRequestV0 request: ParseRecipeURLRequestV0
): String = networkRequestWrapper.makeCallAndHandleUnauthorized( ): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", token, request) }, block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", request) },
logMethod = { "parseRecipeFromURL" }, logMethod = { "parseRecipeFromURL" },
logParameters = { "baseUrl = $baseUrl, token = $token, request = $request" } logParameters = { "baseUrl = $baseUrl, request = $request" }
) )
} }

View File

@@ -1,6 +1,5 @@
package gq.kirmanak.mealient.datasource.v0 package gq.kirmanak.mealient.datasource.v0
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
import gq.kirmanak.mealient.datasource.v0.models.* import gq.kirmanak.mealient.datasource.v0.models.*
import retrofit2.http.* import retrofit2.http.*
@@ -17,7 +16,6 @@ interface MealieServiceV0 {
@POST @POST
suspend fun addRecipe( suspend fun addRecipe(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Body addRecipeRequestV0: AddRecipeRequestV0, @Body addRecipeRequestV0: AddRecipeRequestV0,
): String ): String
@@ -29,7 +27,6 @@ interface MealieServiceV0 {
@GET @GET
suspend fun getRecipeSummary( suspend fun getRecipeSummary(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Query("start") start: Int, @Query("start") start: Int,
@Query("limit") limit: Int, @Query("limit") limit: Int,
): List<GetRecipeSummaryResponseV0> ): List<GetRecipeSummaryResponseV0>
@@ -37,13 +34,11 @@ interface MealieServiceV0 {
@GET @GET
suspend fun getRecipe( suspend fun getRecipe(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
): GetRecipeResponseV0 ): GetRecipeResponseV0
@POST @POST
suspend fun createRecipeFromURL( suspend fun createRecipeFromURL(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Body request: ParseRecipeURLRequestV0, @Body request: ParseRecipeURLRequestV0,
): String ): String
} }

View File

@@ -11,13 +11,11 @@ interface MealieDataSourceV1 {
suspend fun createRecipe( suspend fun createRecipe(
baseUrl: String, baseUrl: String,
token: String?,
recipe: CreateRecipeRequestV1, recipe: CreateRecipeRequestV1,
): String ): String
suspend fun updateRecipe( suspend fun updateRecipe(
baseUrl: String, baseUrl: String,
token: String?,
slug: String, slug: String,
recipe: UpdateRecipeRequestV1, recipe: UpdateRecipeRequestV1,
): GetRecipeResponseV1 ): GetRecipeResponseV1
@@ -37,20 +35,17 @@ interface MealieDataSourceV1 {
suspend fun requestRecipes( suspend fun requestRecipes(
baseUrl: String, baseUrl: String,
token: String?,
page: Int, page: Int,
perPage: Int, perPage: Int,
): List<GetRecipeSummaryResponseV1> ): List<GetRecipeSummaryResponseV1>
suspend fun requestRecipeInfo( suspend fun requestRecipeInfo(
baseUrl: String, baseUrl: String,
token: String?,
slug: String, slug: String,
): GetRecipeResponseV1 ): GetRecipeResponseV1
suspend fun parseRecipeFromURL( suspend fun parseRecipeFromURL(
baseUrl: String, baseUrl: String,
token: String?,
request: ParseRecipeURLRequestV1, request: ParseRecipeURLRequestV1,
): String ): String
} }

View File

@@ -27,23 +27,21 @@ class MealieDataSourceV1Impl @Inject constructor(
override suspend fun createRecipe( override suspend fun createRecipe(
baseUrl: String, baseUrl: String,
token: String?,
recipe: CreateRecipeRequestV1 recipe: CreateRecipeRequestV1
): String = networkRequestWrapper.makeCallAndHandleUnauthorized( ): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.createRecipe("$baseUrl/api/recipes", token, recipe) }, block = { service.createRecipe("$baseUrl/api/recipes", recipe) },
logMethod = { "createRecipe" }, logMethod = { "createRecipe" },
logParameters = { "baseUrl = $baseUrl, token = $token, recipe = $recipe" } logParameters = { "baseUrl = $baseUrl, recipe = $recipe" }
) )
override suspend fun updateRecipe( override suspend fun updateRecipe(
baseUrl: String, baseUrl: String,
token: String?,
slug: String, slug: String,
recipe: UpdateRecipeRequestV1 recipe: UpdateRecipeRequestV1
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.updateRecipe("$baseUrl/api/recipes/$slug", token, recipe) }, block = { service.updateRecipe("$baseUrl/api/recipes/$slug", recipe) },
logMethod = { "updateRecipe" }, logMethod = { "updateRecipe" },
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug, recipe = $recipe" } logParameters = { "baseUrl = $baseUrl, slug = $slug, recipe = $recipe" }
) )
override suspend fun authenticate( override suspend fun authenticate(
@@ -76,33 +74,30 @@ class MealieDataSourceV1Impl @Inject constructor(
override suspend fun requestRecipes( override suspend fun requestRecipes(
baseUrl: String, baseUrl: String,
token: String?,
page: Int, page: Int,
perPage: Int perPage: Int
): List<GetRecipeSummaryResponseV1> = networkRequestWrapper.makeCallAndHandleUnauthorized( ): List<GetRecipeSummaryResponseV1> = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getRecipeSummary("$baseUrl/api/recipes", token, page, perPage) }, block = { service.getRecipeSummary("$baseUrl/api/recipes", page, perPage) },
logMethod = { "requestRecipes" }, logMethod = { "requestRecipes" },
logParameters = { "baseUrl = $baseUrl, token = $token, page = $page, perPage = $perPage" } logParameters = { "baseUrl = $baseUrl, page = $page, perPage = $perPage" }
).items ).items
override suspend fun requestRecipeInfo( override suspend fun requestRecipeInfo(
baseUrl: String, baseUrl: String,
token: String?,
slug: String slug: String
): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized( ): GetRecipeResponseV1 = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.getRecipe("$baseUrl/api/recipes/$slug", token) }, block = { service.getRecipe("$baseUrl/api/recipes/$slug") },
logMethod = { "requestRecipeInfo" }, logMethod = { "requestRecipeInfo" },
logParameters = { "baseUrl = $baseUrl, token = $token, slug = $slug" } logParameters = { "baseUrl = $baseUrl, slug = $slug" }
) )
override suspend fun parseRecipeFromURL( override suspend fun parseRecipeFromURL(
baseUrl: String, baseUrl: String,
token: String?,
request: ParseRecipeURLRequestV1 request: ParseRecipeURLRequestV1
): String = networkRequestWrapper.makeCallAndHandleUnauthorized( ): String = networkRequestWrapper.makeCallAndHandleUnauthorized(
block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", token, request) }, block = { service.createRecipeFromURL("$baseUrl/api/recipes/create-url", request) },
logMethod = { "parseRecipeFromURL" }, logMethod = { "parseRecipeFromURL" },
logParameters = { "baseUrl = $baseUrl, token = $token, request = $request" } logParameters = { "baseUrl = $baseUrl, request = $request" }
) )

View File

@@ -1,6 +1,5 @@
package gq.kirmanak.mealient.datasource.v1 package gq.kirmanak.mealient.datasource.v1
import gq.kirmanak.mealient.datasource.DataSourceModule.Companion.AUTHORIZATION_HEADER_NAME
import gq.kirmanak.mealient.datasource.v1.models.* import gq.kirmanak.mealient.datasource.v1.models.*
import retrofit2.http.* import retrofit2.http.*
@@ -17,14 +16,12 @@ interface MealieServiceV1 {
@POST @POST
suspend fun createRecipe( suspend fun createRecipe(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Body addRecipeRequest: CreateRecipeRequestV1, @Body addRecipeRequest: CreateRecipeRequestV1,
): String ): String
@PATCH @PATCH
suspend fun updateRecipe( suspend fun updateRecipe(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Body addRecipeRequest: UpdateRecipeRequestV1, @Body addRecipeRequest: UpdateRecipeRequestV1,
): GetRecipeResponseV1 ): GetRecipeResponseV1
@@ -36,7 +33,6 @@ interface MealieServiceV1 {
@GET @GET
suspend fun getRecipeSummary( suspend fun getRecipeSummary(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Query("page") page: Int, @Query("page") page: Int,
@Query("perPage") perPage: Int, @Query("perPage") perPage: Int,
): GetRecipesResponseV1 ): GetRecipesResponseV1
@@ -44,13 +40,11 @@ interface MealieServiceV1 {
@GET @GET
suspend fun getRecipe( suspend fun getRecipe(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
): GetRecipeResponseV1 ): GetRecipeResponseV1
@POST @POST
suspend fun createRecipeFromURL( suspend fun createRecipeFromURL(
@Url url: String, @Url url: String,
@Header(AUTHORIZATION_HEADER_NAME) token: String?,
@Body request: ParseRecipeURLRequestV1, @Body request: ParseRecipeURLRequestV1,
): String ): String
} }