Add full Basic authorization and sync with backend-server

This commit is contained in:
Denis Oleynik 2025-02-19 17:45:05 +03:00
parent 60a7e7544e
commit 72b3324821
15 changed files with 64 additions and 47 deletions

View File

@ -4,7 +4,7 @@ import com.google.gson.annotations.SerializedName
class LoginDto(
@SerializedName("username")
val username: String?,
val username: String,
@SerializedName("password")
val password: String?
val password: String
)

View File

@ -0,0 +1,5 @@
package ru.myitschool.work.data.dto
enum class Role {
ADMIN, USER
}

View File

@ -3,12 +3,20 @@ package ru.myitschool.work.data.dto
import com.google.gson.annotations.SerializedName
class UserInfoDto(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val fullname: String?,
val fullname: String,
@SerializedName("login")
val login: String,
@SerializedName("role")
val role: Role,
@SerializedName("blocked")
val blocked: Boolean,
@SerializedName("photo")
val imageUrl: String?,
val imageUrl: String,
@SerializedName("position")
val position: String?,
val position: String,
@SerializedName("lastVisit")
val lastEntry: String?,
val lastEntry: String,
)

View File

@ -15,18 +15,18 @@ class AccountRepositoryImpl @Inject constructor(
private val accountNetworkDataSource: AccountNetworkDataSource,
private val userInfoMapper: Lazy<UserInfoMapper>,
): UserInfoRepository {
override suspend fun getInfo(username: String): Result<UserInfoEntity> {
override suspend fun getInfo(basicAuth: String): Result<UserInfoEntity> {
return withContext(Dispatchers.IO) {
accountNetworkDataSource.getInfo(username).fold(
accountNetworkDataSource.getInfo(basicAuth).fold(
onSuccess = { value -> userInfoMapper.get().invoke(value) },
onFailure = { error -> Result.failure(error) }
)
}
}
override suspend fun openByQr(username: String, content: String): Result<Unit> {
override suspend fun openByQr(basicAuth: String, content: String): Result<Unit> {
return withContext(Dispatchers.IO) {
accountNetworkDataSource.openByQr(username, content)
accountNetworkDataSource.openByQr(basicAuth, content)
}
}
}

View File

@ -1,5 +1,6 @@
package ru.myitschool.work.data.repo
import android.util.Base64
import dagger.Reusable
import ru.myitschool.work.data.source.AuthorizationNetworkDataSource
import ru.myitschool.work.data.source.AuthorizationStorageDataSource
@ -19,19 +20,21 @@ class AuthorizationRepositoryImpl @Inject constructor(
override suspend fun login(data: LoginDto): Result<Unit> {
return withContext(Dispatchers.IO) {
authorizationNetworkDataSource.get().checkLogin(data.username!!)
val basicAuthToken = Base64.encodeToString("${data.username}:${data.password}".toByteArray(), Base64.NO_WRAP)
authorizationNetworkDataSource.get().checkAuthCredentials(basicAuthToken)
.onSuccess {
authorizationStorageDataSource.get().updateLogin(data.username)
authorizationStorageDataSource.get().updateBasicAuth(basicAuthToken)
}
}
}
override suspend fun logout() {
authorizationStorageDataSource.get().updateLogin(null)
authorizationStorageDataSource.get().updateBasicAuth(null)
}
override suspend fun getLogin(): Result<String> {
val result = authorizationStorageDataSource.get().login.firstOrNull()
val result = authorizationStorageDataSource.get().basicAuthToken.firstOrNull()
return if (result == null) {
Result.failure(Exception("Not authorize"))
} else {

View File

@ -2,20 +2,21 @@ package ru.myitschool.work.data.source
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.PATCH
import retrofit2.http.Path
import ru.myitschool.work.data.dto.OpenQrDto
import ru.myitschool.work.data.dto.UserInfoDto
interface AccountApi {
@GET("api/{username}/info")
@GET("api/employee/info")
suspend fun getInfo(
@Path("username") username: String
@Header("Authorization") basicAuth: String
) : UserInfoDto
@PATCH("api/{username}/open")
@PATCH("api/employee/open")
suspend fun openByQr(
@Path("username") username: String,
@Header("Authorization") basicAuth: String,
@Body content: OpenQrDto
)
}

View File

@ -14,14 +14,14 @@ class AccountNetworkDataSource @Inject constructor(
retrofit.create(AccountApi::class.java)
}
suspend fun getInfo(username: String): Result<UserInfoDto> {
return kotlin.runCatching { api.getInfo(username = username) }
suspend fun getInfo(basicAuth: String): Result<UserInfoDto> {
return kotlin.runCatching { api.getInfo(basicAuth = basicAuth) }
}
suspend fun openByQr(username: String, content: String): Result<Unit> {
suspend fun openByQr(basicAuth: String, content: String): Result<Unit> {
return kotlin.runCatching {
api.openByQr(
username = username,
basicAuth = basicAuth,
content = OpenQrDto(value = content)
)
}

View File

@ -1,19 +1,14 @@
package ru.myitschool.work.data.source
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path
interface AuthorizationApi {
@GET("api/{username}/auth")
suspend fun checkLogin(
@Path("username") username: String
) : Response<Unit>
@GET("api/login")
@POST("api/login")
suspend fun login(
@Body content: AccountApi
@Header("Authorization") basicAuthorization: String,
): Response<Unit>
}

View File

@ -13,8 +13,8 @@ class AuthorizationNetworkDataSource @Inject constructor(
retrofit.create(AuthorizationApi::class.java)
}
suspend fun checkLogin(username: String): Result<Unit> {
return kotlin.runCatching { api.checkLogin(username = username) }.fold(
suspend fun checkAuthCredentials(basicAuth: String): Result<Unit> {
return kotlin.runCatching { api.login("Basic $basicAuth") }.fold(
onSuccess = { response ->
when (response.code()) {
200 -> Result.success(Unit)

View File

@ -17,22 +17,22 @@ class AuthorizationStorageDataSource @Inject constructor(
@ApplicationContext private val context: Context,
) {
private val Context.storage: DataStore<Preferences> by preferencesDataStore(name = NAME)
val login: Flow<String?> = context.storage.data.map { preferences ->
preferences[LOGIN_KEY]
val basicAuthToken: Flow<String?> = context.storage.data.map { preferences ->
preferences[TOKEN_KEY]
}
suspend fun updateLogin(username: String?) {
suspend fun updateBasicAuth(token: String?) {
context.storage.edit { settings ->
if (username != null) {
settings[LOGIN_KEY] = username
if (token != null) {
settings[TOKEN_KEY] = "Basic $token"
} else {
settings.remove(LOGIN_KEY)
settings.remove(TOKEN_KEY)
}
}
}
private companion object {
const val NAME = "auth_data"
val LOGIN_KEY = stringPreferencesKey("login")
val TOKEN_KEY = stringPreferencesKey("basicAuthToken")
}
}

View File

@ -11,7 +11,7 @@ class GetUserInfoUseCase @Inject constructor(
) {
suspend operator fun invoke(): Result<UserInfoEntity> {
return getLoginUseCase().fold(
onSuccess = { username -> repo.getInfo(username = username) },
onSuccess = { basicAuth -> repo.getInfo(basicAuth) },
onFailure = { error -> Result.failure(error) }
)
}

View File

@ -10,7 +10,7 @@ class OpenByQrUseCase @Inject constructor(
) {
suspend operator fun invoke(content: String): Result<Unit> {
return getLoginUseCase().fold(
onSuccess = { username -> repo.openByQr(username = username, content = content) },
onSuccess = { basicAuth -> repo.openByQr(basicAuth = basicAuth, content = content) },
onFailure = { error -> Result.failure(error) }
)
}

View File

@ -3,7 +3,7 @@ package ru.myitschool.work.domain.profile.repo
import ru.myitschool.work.domain.profile.entities.UserInfoEntity
interface UserInfoRepository {
suspend fun getInfo(username: String) : Result<UserInfoEntity>
suspend fun getInfo(basicAuth: String) : Result<UserInfoEntity>
suspend fun openByQr(username: String, content: String) : Result<Unit>
suspend fun openByQr(basicAuth: String, content: String) : Result<Unit>
}

View File

@ -64,6 +64,11 @@ class LoginFragment : Fragment(R.layout.fragment_login) {
viewModel.inputLogin(s.toString())
}
})
binding.password.addTextChangedListener(object : TextChangedListener() {
override fun afterTextChanged(s: Editable?) {
viewModel.inputPassword(s.toString())
}
})
}

View File

@ -32,12 +32,12 @@ class LoginViewModel @Inject constructor(
val state = _state.asStateFlow()
private var login: String = ""
private var password: String = "123"//пока пароль по дефалту
private var password: String = ""
fun clickLogin() {
viewModelScope.launch {
_state.update { State.Loading }
val data = LoginDto(login,password)
val data = LoginDto(login, password)
loginUseCase.get().invoke(data).fold(
onSuccess = {
_action.emit(Action.OpenProfile)
@ -56,7 +56,7 @@ class LoginViewModel @Inject constructor(
//Чек пароля на правильность
// Чек пароля на правильность
fun inputLogin(login: String) {
this.login = login
viewModelScope.launch {
@ -67,7 +67,7 @@ class LoginViewModel @Inject constructor(
}
}
}
fun passwordCheck(password: String){
fun inputPassword(password: String){
this.password = password
}