Replace "Mealie" with "Mealient" everywhere

This commit is contained in:
Kirill Kamakin
2021-11-20 13:41:47 +03:00
parent d789bfcf97
commit 5866584d14
81 changed files with 283 additions and 284 deletions

View File

@@ -0,0 +1,8 @@
package gq.kirmanak.mealient.data.auth
interface AuthDataSource {
/**
* Tries to acquire authentication token using the provided credentials on specified server.
*/
suspend fun authenticate(username: String, password: String, baseUrl: String): String
}

View File

@@ -0,0 +1,26 @@
package gq.kirmanak.mealient.data.auth
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import gq.kirmanak.mealient.data.auth.impl.AuthDataSourceImpl
import gq.kirmanak.mealient.data.auth.impl.AuthRepoImpl
import gq.kirmanak.mealient.data.auth.impl.AuthStorageImpl
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.serialization.ExperimentalSerializationApi
@ExperimentalCoroutinesApi
@ExperimentalSerializationApi
@Module
@InstallIn(SingletonComponent::class)
interface AuthModule {
@Binds
fun bindAuthDataSource(authDataSourceImpl: AuthDataSourceImpl): AuthDataSource
@Binds
fun bindAuthStorage(authStorageImpl: AuthStorageImpl): AuthStorage
@Binds
fun bindAuthRepo(authRepo: AuthRepoImpl): AuthRepo
}

View File

@@ -0,0 +1,15 @@
package gq.kirmanak.mealient.data.auth
import kotlinx.coroutines.flow.Flow
interface AuthRepo {
suspend fun authenticate(username: String, password: String, baseUrl: String)
suspend fun getBaseUrl(): String?
suspend fun getToken(): String?
fun authenticationStatuses(): Flow<Boolean>
fun logout()
}

View File

@@ -0,0 +1,19 @@
package gq.kirmanak.mealient.data.auth
import gq.kirmanak.mealient.data.auth.impl.GetTokenResponse
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.POST
interface AuthService {
@FormUrlEncoded
@POST("/api/auth/token")
suspend fun getToken(
@Field("username") username: String,
@Field("password") password: String,
@Field("grant_type") grantType: String? = null,
@Field("scope") scope: String? = null,
@Field("client_id") clientId: String? = null,
@Field("client_secret") clientSecret: String? = null
): GetTokenResponse
}

View File

@@ -0,0 +1,15 @@
package gq.kirmanak.mealient.data.auth
import kotlinx.coroutines.flow.Flow
interface AuthStorage {
fun storeAuthData(token: String, baseUrl: String)
suspend fun getBaseUrl(): String?
suspend fun getToken(): String?
fun tokenObservable(): Flow<String?>
fun clearAuthData()
}

View File

@@ -0,0 +1,26 @@
package gq.kirmanak.mealient.data.auth.impl
import gq.kirmanak.mealient.data.auth.AuthDataSource
import gq.kirmanak.mealient.data.auth.AuthService
import gq.kirmanak.mealient.data.impl.RetrofitBuilder
import kotlinx.serialization.ExperimentalSerializationApi
import retrofit2.create
import timber.log.Timber
import javax.inject.Inject
@ExperimentalSerializationApi
class AuthDataSourceImpl @Inject constructor(
private val retrofitBuilder: RetrofitBuilder
) : AuthDataSource {
override suspend fun authenticate(
username: String,
password: String,
baseUrl: String
): String {
Timber.v("authenticate() called with: username = $username, password = $password, baseUrl = $baseUrl")
val authService = retrofitBuilder.buildRetrofit(baseUrl).create<AuthService>()
val response = authService.getToken(username, password)
Timber.d("authenticate() response is $response")
return response.accessToken
}
}

View File

@@ -0,0 +1,18 @@
package gq.kirmanak.mealient.data.auth.impl
import okhttp3.Interceptor
import okhttp3.Response
const val AUTHORIZATION_HEADER = "Authorization"
class AuthOkHttpInterceptor(token: String) : Interceptor {
private val headerValue = "Bearer $token"
override fun intercept(chain: Interceptor.Chain): Response {
val newRequest = chain.request()
.newBuilder()
.addHeader(AUTHORIZATION_HEADER, headerValue)
.build()
return chain.proceed(newRequest)
}
}

View File

@@ -0,0 +1,43 @@
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 kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
import javax.inject.Inject
class AuthRepoImpl @Inject constructor(
private val dataSource: AuthDataSource,
private val storage: AuthStorage
) : AuthRepo {
override suspend fun authenticate(
username: String,
password: String,
baseUrl: String
) {
Timber.v("authenticate() called with: username = $username, password = $password, baseUrl = $baseUrl")
val url = if (baseUrl.startsWith("http")) baseUrl else "https://$baseUrl"
val accessToken = dataSource.authenticate(username, password, url)
Timber.d("authenticate result is $accessToken")
storage.storeAuthData(accessToken, url)
}
override suspend fun getBaseUrl(): String? = storage.getBaseUrl()
override suspend fun getToken(): String? {
Timber.v("getToken() called")
return storage.getToken()
}
override fun authenticationStatuses(): Flow<Boolean> {
Timber.v("authenticationStatuses() called")
return storage.tokenObservable().map { it != null }
}
override fun logout() {
Timber.v("logout() called")
storage.clearAuthData()
}
}

View File

@@ -0,0 +1,83 @@
package gq.kirmanak.mealient.data.auth.impl
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import dagger.hilt.android.qualifiers.ApplicationContext
import gq.kirmanak.mealient.data.auth.AuthStorage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
private const val TOKEN_KEY = "AUTH_TOKEN"
private const val BASE_URL_KEY = "BASE_URL"
@ExperimentalCoroutinesApi
class AuthStorageImpl @Inject constructor(@ApplicationContext private val context: Context) :
AuthStorage {
private val sharedPreferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(context)
override fun storeAuthData(token: String, baseUrl: String) {
Timber.v("storeAuthData() called with: token = $token, baseUrl = $baseUrl")
sharedPreferences.edit()
.putString(TOKEN_KEY, token)
.putString(BASE_URL_KEY, baseUrl)
.apply()
}
override suspend fun getBaseUrl(): String? {
val baseUrl = getString(BASE_URL_KEY)
Timber.d("getBaseUrl: base url is $baseUrl")
return baseUrl
}
override suspend fun getToken(): String? {
Timber.v("getToken() called")
val token = getString(TOKEN_KEY)
Timber.d("getToken: token is $token")
return token
}
private suspend fun getString(key: String): String? = withContext(Dispatchers.Default) {
sharedPreferences.getString(key, null)
}
override fun tokenObservable(): Flow<String?> {
Timber.v("tokenObservable() called")
return callbackFlow {
val listener = SharedPreferences.OnSharedPreferenceChangeListener { prefs, key ->
Timber.v("tokenObservable: listener called with key $key")
val token = when (key) {
null -> null
TOKEN_KEY -> prefs.getString(key, null)
else -> return@OnSharedPreferenceChangeListener
}
Timber.d("tokenObservable: New token: $token")
trySendBlocking(token).onFailure { Timber.e(it, "Can't send new token") }
}
Timber.v("tokenObservable: registering listener")
send(getToken())
sharedPreferences.registerOnSharedPreferenceChangeListener(listener)
awaitClose {
Timber.v("tokenObservable: flow has been closed")
sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener)
}
}
}
override fun clearAuthData() {
Timber.v("clearAuthData() called")
sharedPreferences.edit()
.remove(TOKEN_KEY)
.remove(BASE_URL_KEY)
.apply()
}
}

View File

@@ -0,0 +1,10 @@
package gq.kirmanak.mealient.data.auth.impl
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GetTokenResponse(
@SerialName("access_token") val accessToken: String,
@SerialName("token_type") val tokenType: String
)