android: add auth network core, token store, and DI wiring
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package ru.daemonlord.messenger.core.network
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import ru.daemonlord.messenger.core.token.TokenRepository
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class AuthHeaderInterceptor @Inject constructor(
|
||||
private val tokenRepository: TokenRepository,
|
||||
) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val originalRequest = chain.request()
|
||||
val noAuthHeader = originalRequest.header(NO_AUTH_HEADER)
|
||||
|
||||
if (noAuthHeader == "true") {
|
||||
val requestWithoutMarker = originalRequest.newBuilder()
|
||||
.removeHeader(NO_AUTH_HEADER)
|
||||
.build()
|
||||
return chain.proceed(requestWithoutMarker)
|
||||
}
|
||||
|
||||
val accessToken = runBlocking { tokenRepository.getTokens()?.accessToken }
|
||||
val requestBuilder = originalRequest.newBuilder()
|
||||
if (!accessToken.isNullOrBlank()) {
|
||||
requestBuilder.header("Authorization", "Bearer $accessToken")
|
||||
}
|
||||
return chain.proceed(requestBuilder.build())
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val NO_AUTH_HEADER = "No-Auth"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package ru.daemonlord.messenger.core.network
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import ru.daemonlord.messenger.core.token.TokenBundle
|
||||
import ru.daemonlord.messenger.core.token.TokenRepository
|
||||
import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
||||
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TokenRefreshAuthenticator @Inject constructor(
|
||||
private val tokenRepository: TokenRepository,
|
||||
private val refreshAuthApiService: AuthApiService,
|
||||
) : Authenticator {
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
if (responseCount(response) >= MAX_RETRIES) {
|
||||
return null
|
||||
}
|
||||
|
||||
val marker = response.request.header(NO_AUTH_HEADER)
|
||||
if (marker == "true") {
|
||||
return null
|
||||
}
|
||||
|
||||
val refreshedAccessToken = synchronized(this) {
|
||||
runBlocking {
|
||||
val currentTokens = tokenRepository.getTokens() ?: return@runBlocking null
|
||||
tryRefreshTokens(currentTokens)
|
||||
}
|
||||
} ?: return null
|
||||
|
||||
return response.request.newBuilder()
|
||||
.header("Authorization", "Bearer $refreshedAccessToken")
|
||||
.build()
|
||||
}
|
||||
|
||||
private suspend fun tryRefreshTokens(tokens: TokenBundle): String? {
|
||||
return try {
|
||||
val refreshed = refreshAuthApiService.refresh(
|
||||
request = RefreshTokenRequestDto(refreshToken = tokens.refreshToken)
|
||||
)
|
||||
tokenRepository.saveTokens(
|
||||
TokenBundle(
|
||||
accessToken = refreshed.accessToken,
|
||||
refreshToken = refreshed.refreshToken,
|
||||
savedAtMillis = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
refreshed.accessToken
|
||||
} catch (_: IOException) {
|
||||
null
|
||||
} catch (_: Exception) {
|
||||
tokenRepository.clearTokens()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun responseCount(response: Response): Int {
|
||||
var current: Response? = response
|
||||
var count = 1
|
||||
while (current?.priorResponse != null) {
|
||||
count++
|
||||
current = current.priorResponse
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAX_RETRIES = 2
|
||||
const val NO_AUTH_HEADER = "No-Auth"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package ru.daemonlord.messenger.core.token
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DataStoreTokenRepository @Inject constructor(
|
||||
private val dataStore: DataStore<Preferences>,
|
||||
) : TokenRepository {
|
||||
|
||||
override fun observeTokens(): Flow<TokenBundle?> = dataStore.data.map { preferences ->
|
||||
preferences.toTokenBundleOrNull()
|
||||
}
|
||||
|
||||
override suspend fun getTokens(): TokenBundle? {
|
||||
return observeTokens().first()
|
||||
}
|
||||
|
||||
override suspend fun saveTokens(tokens: TokenBundle) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[ACCESS_TOKEN_KEY] = tokens.accessToken
|
||||
preferences[REFRESH_TOKEN_KEY] = tokens.refreshToken
|
||||
preferences[SAVED_AT_KEY] = tokens.savedAtMillis
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearTokens() {
|
||||
dataStore.edit { preferences ->
|
||||
preferences.remove(ACCESS_TOKEN_KEY)
|
||||
preferences.remove(REFRESH_TOKEN_KEY)
|
||||
preferences.remove(SAVED_AT_KEY)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Preferences.toTokenBundleOrNull(): TokenBundle? {
|
||||
val access = this[ACCESS_TOKEN_KEY]
|
||||
val refresh = this[REFRESH_TOKEN_KEY]
|
||||
val savedAt = this[SAVED_AT_KEY]
|
||||
|
||||
if (access.isNullOrBlank() || refresh.isNullOrBlank() || savedAt == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return TokenBundle(
|
||||
accessToken = access,
|
||||
refreshToken = refresh,
|
||||
savedAtMillis = savedAt,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
|
||||
val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token")
|
||||
val SAVED_AT_KEY = longPreferencesKey("tokens_saved_at")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package ru.daemonlord.messenger.data.auth.api
|
||||
|
||||
import ru.daemonlord.messenger.data.auth.dto.AuthUserDto
|
||||
import ru.daemonlord.messenger.data.auth.dto.LoginRequestDto
|
||||
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
||||
import ru.daemonlord.messenger.data.auth.dto.TokenResponseDto
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface AuthApiService {
|
||||
@Headers("No-Auth: true")
|
||||
@POST("/api/v1/auth/login")
|
||||
suspend fun login(@Body request: LoginRequestDto): TokenResponseDto
|
||||
|
||||
@Headers("No-Auth: true")
|
||||
@POST("/api/v1/auth/refresh")
|
||||
suspend fun refresh(@Body request: RefreshTokenRequestDto): TokenResponseDto
|
||||
|
||||
@GET("/api/v1/auth/me")
|
||||
suspend fun me(): AuthUserDto
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package ru.daemonlord.messenger.data.auth.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LoginRequestDto(
|
||||
val email: String,
|
||||
val password: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class RefreshTokenRequestDto(
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TokenResponseDto(
|
||||
@SerialName("access_token")
|
||||
val accessToken: String,
|
||||
@SerialName("refresh_token")
|
||||
val refreshToken: String,
|
||||
@SerialName("token_type")
|
||||
val tokenType: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuthUserDto(
|
||||
val id: Long,
|
||||
val email: String,
|
||||
val name: String,
|
||||
val username: String,
|
||||
@SerialName("avatar_url")
|
||||
val avatarUrl: String? = null,
|
||||
@SerialName("email_verified")
|
||||
val emailVerified: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ErrorResponseDto(
|
||||
val detail: String? = null,
|
||||
)
|
||||
@@ -0,0 +1,125 @@
|
||||
package ru.daemonlord.messenger.data.auth.repository
|
||||
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import retrofit2.HttpException
|
||||
import ru.daemonlord.messenger.core.token.TokenBundle
|
||||
import ru.daemonlord.messenger.core.token.TokenRepository
|
||||
import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
||||
import ru.daemonlord.messenger.data.auth.dto.AuthUserDto
|
||||
import ru.daemonlord.messenger.data.auth.dto.LoginRequestDto
|
||||
import ru.daemonlord.messenger.data.auth.dto.RefreshTokenRequestDto
|
||||
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||
import ru.daemonlord.messenger.domain.common.AppError
|
||||
import ru.daemonlord.messenger.domain.common.AppResult
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class NetworkAuthRepository @Inject constructor(
|
||||
private val authApiService: AuthApiService,
|
||||
private val tokenRepository: TokenRepository,
|
||||
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||
) : AuthRepository {
|
||||
|
||||
override suspend fun login(email: String, password: String): AppResult<AuthUser> = withContext(ioDispatcher) {
|
||||
try {
|
||||
val tokenResponse = authApiService.login(
|
||||
request = LoginRequestDto(
|
||||
email = email,
|
||||
password = password,
|
||||
)
|
||||
)
|
||||
tokenRepository.saveTokens(
|
||||
TokenBundle(
|
||||
accessToken = tokenResponse.accessToken,
|
||||
refreshToken = tokenResponse.refreshToken,
|
||||
savedAtMillis = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
getMe()
|
||||
} catch (error: Throwable) {
|
||||
AppResult.Error(error.toAppError(forLogin = true))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refreshTokens(): AppResult<Unit> = withContext(ioDispatcher) {
|
||||
val tokens = tokenRepository.getTokens()
|
||||
?: return@withContext AppResult.Error(AppError.Unauthorized)
|
||||
try {
|
||||
val refreshed = authApiService.refresh(
|
||||
request = RefreshTokenRequestDto(refreshToken = tokens.refreshToken)
|
||||
)
|
||||
tokenRepository.saveTokens(
|
||||
TokenBundle(
|
||||
accessToken = refreshed.accessToken,
|
||||
refreshToken = refreshed.refreshToken,
|
||||
savedAtMillis = System.currentTimeMillis(),
|
||||
)
|
||||
)
|
||||
AppResult.Success(Unit)
|
||||
} catch (error: Throwable) {
|
||||
tokenRepository.clearTokens()
|
||||
AppResult.Error(error.toAppError(forLogin = false))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMe(): AppResult<AuthUser> = withContext(ioDispatcher) {
|
||||
try {
|
||||
val user = authApiService.me().toDomain()
|
||||
AppResult.Success(user)
|
||||
} catch (error: Throwable) {
|
||||
AppResult.Error(error.toAppError(forLogin = false))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun restoreSession(): AppResult<AuthUser> = withContext(ioDispatcher) {
|
||||
val tokens = tokenRepository.getTokens()
|
||||
?: return@withContext AppResult.Error(AppError.Unauthorized)
|
||||
|
||||
if (tokens.accessToken.isBlank() || tokens.refreshToken.isBlank()) {
|
||||
tokenRepository.clearTokens()
|
||||
return@withContext AppResult.Error(AppError.Unauthorized)
|
||||
}
|
||||
|
||||
when (val meResult = getMe()) {
|
||||
is AppResult.Success -> meResult
|
||||
is AppResult.Error -> {
|
||||
if (meResult.reason is AppError.Unauthorized) {
|
||||
tokenRepository.clearTokens()
|
||||
}
|
||||
meResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun logout() {
|
||||
tokenRepository.clearTokens()
|
||||
}
|
||||
|
||||
private fun AuthUserDto.toDomain(): AuthUser {
|
||||
return AuthUser(
|
||||
id = id,
|
||||
email = email,
|
||||
name = name,
|
||||
username = username,
|
||||
avatarUrl = avatarUrl,
|
||||
emailVerified = emailVerified,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Throwable.toAppError(forLogin: Boolean): AppError {
|
||||
return when (this) {
|
||||
is IOException -> AppError.Network
|
||||
is HttpException -> when (code()) {
|
||||
400 -> if (forLogin) AppError.InvalidCredentials else AppError.Server(message = message())
|
||||
401, 403 -> if (forLogin) AppError.InvalidCredentials else AppError.Unauthorized
|
||||
else -> AppError.Server(message = message())
|
||||
}
|
||||
else -> AppError.Unknown(cause = this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package ru.daemonlord.messenger.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import ru.daemonlord.messenger.BuildConfig
|
||||
import ru.daemonlord.messenger.core.network.AuthHeaderInterceptor
|
||||
import ru.daemonlord.messenger.core.network.TokenRefreshAuthenticator
|
||||
import ru.daemonlord.messenger.data.auth.api.AuthApiService
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object NetworkModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideJson(): Json {
|
||||
return Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
isLenient = true
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
return HttpLoggingInterceptor().apply {
|
||||
level = if (BuildConfig.DEBUG) {
|
||||
HttpLoggingInterceptor.Level.BODY
|
||||
} else {
|
||||
HttpLoggingInterceptor.Level.NONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@RefreshClient
|
||||
fun provideRefreshClient(
|
||||
loggingInterceptor: HttpLoggingInterceptor,
|
||||
): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.connectTimeout(20, TimeUnit.SECONDS)
|
||||
.readTimeout(20, TimeUnit.SECONDS)
|
||||
.writeTimeout(20, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRefreshApiService(
|
||||
@RefreshClient refreshClient: OkHttpClient,
|
||||
json: Json,
|
||||
): AuthApiService {
|
||||
val contentType = "application/json".toMediaType()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.API_BASE_URL)
|
||||
.addConverterFactory(json.asConverterFactory(contentType))
|
||||
.client(refreshClient)
|
||||
.build()
|
||||
.create(AuthApiService::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiClient(
|
||||
loggingInterceptor: HttpLoggingInterceptor,
|
||||
authHeaderInterceptor: AuthHeaderInterceptor,
|
||||
tokenRefreshAuthenticator: TokenRefreshAuthenticator,
|
||||
): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(loggingInterceptor)
|
||||
.addInterceptor(authHeaderInterceptor)
|
||||
.authenticator(tokenRefreshAuthenticator)
|
||||
.connectTimeout(20, TimeUnit.SECONDS)
|
||||
.readTimeout(20, TimeUnit.SECONDS)
|
||||
.writeTimeout(20, TimeUnit.SECONDS)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(
|
||||
client: OkHttpClient,
|
||||
json: Json,
|
||||
): Retrofit {
|
||||
val contentType = "application/json".toMediaType()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BuildConfig.API_BASE_URL)
|
||||
.addConverterFactory(json.asConverterFactory(contentType))
|
||||
.client(client)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAuthApiService(retrofit: Retrofit): AuthApiService {
|
||||
return retrofit.create(AuthApiService::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.daemonlord.messenger.di
|
||||
|
||||
import javax.inject.Qualifier
|
||||
|
||||
@Qualifier
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class RefreshClient
|
||||
@@ -0,0 +1,20 @@
|
||||
package ru.daemonlord.messenger.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ru.daemonlord.messenger.data.auth.repository.NetworkAuthRepository
|
||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
abstract class RepositoryModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
abstract fun bindAuthRepository(
|
||||
repository: NetworkAuthRepository,
|
||||
): AuthRepository
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package ru.daemonlord.messenger.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import ru.daemonlord.messenger.core.token.DataStoreTokenRepository
|
||||
import ru.daemonlord.messenger.core.token.TokenRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object StorageModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePreferenceDataStore(
|
||||
@ApplicationContext context: Context,
|
||||
): DataStore<Preferences> {
|
||||
return PreferenceDataStoreFactory.create(
|
||||
produceFile = { context.preferencesDataStoreFile("messenger_tokens.preferences_pb") }
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTokenRepository(
|
||||
repository: DataStoreTokenRepository,
|
||||
): TokenRepository = repository
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package ru.daemonlord.messenger.domain.auth.model
|
||||
|
||||
data class AuthUser(
|
||||
val id: Long,
|
||||
val email: String,
|
||||
val name: String,
|
||||
val username: String,
|
||||
val avatarUrl: String?,
|
||||
val emailVerified: Boolean,
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package ru.daemonlord.messenger.domain.auth.repository
|
||||
|
||||
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
||||
import ru.daemonlord.messenger.domain.common.AppResult
|
||||
|
||||
interface AuthRepository {
|
||||
suspend fun login(email: String, password: String): AppResult<AuthUser>
|
||||
suspend fun refreshTokens(): AppResult<Unit>
|
||||
suspend fun getMe(): AppResult<AuthUser>
|
||||
suspend fun restoreSession(): AppResult<AuthUser>
|
||||
suspend fun logout()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.daemonlord.messenger.domain.auth.usecase
|
||||
|
||||
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||
import ru.daemonlord.messenger.domain.common.AppResult
|
||||
import javax.inject.Inject
|
||||
|
||||
class LoginUseCase @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
) {
|
||||
suspend operator fun invoke(email: String, password: String): AppResult<AuthUser> {
|
||||
return authRepository.login(email = email, password = password)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package ru.daemonlord.messenger.domain.auth.usecase
|
||||
|
||||
import ru.daemonlord.messenger.domain.auth.model.AuthUser
|
||||
import ru.daemonlord.messenger.domain.auth.repository.AuthRepository
|
||||
import ru.daemonlord.messenger.domain.common.AppResult
|
||||
import javax.inject.Inject
|
||||
|
||||
class RestoreSessionUseCase @Inject constructor(
|
||||
private val authRepository: AuthRepository,
|
||||
) {
|
||||
suspend operator fun invoke(): AppResult<AuthUser> {
|
||||
return authRepository.restoreSession()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ru.daemonlord.messenger.domain.common
|
||||
|
||||
sealed interface AppError {
|
||||
data object InvalidCredentials : AppError
|
||||
data object Unauthorized : AppError
|
||||
data object Network : AppError
|
||||
data class Server(val message: String?) : AppError
|
||||
data class Unknown(val cause: Throwable?) : AppError
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.daemonlord.messenger.domain.common
|
||||
|
||||
sealed interface AppResult<out T> {
|
||||
data class Success<T>(val data: T) : AppResult<T>
|
||||
data class Error(val reason: AppError) : AppResult<Nothing>
|
||||
}
|
||||
Reference in New Issue
Block a user