Replace "Mealie" with "Mealient" everywhere
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
15
app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt
Normal file
15
app/src/main/java/gq/kirmanak/mealient/data/auth/AuthRepo.kt
Normal 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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
Reference in New Issue
Block a user