main #6
@ -0,0 +1,20 @@
|
||||
package ru.myitschool.work.data.dto
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AuthRequestDto(
|
||||
@SerialName("login")
|
||||
val login: String,
|
||||
@SerialName("password")
|
||||
val password: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AuthResponseDto(
|
||||
@SerialName("token")
|
||||
val token: String,
|
||||
@SerialName("expired")
|
||||
val expired: Int,
|
||||
)
|
||||
@ -8,41 +8,42 @@ import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import ru.myitschool.work.App
|
||||
import ru.myitschool.work.data.dto.AuthRequestDto
|
||||
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||
import ru.myitschool.work.data.source.NetworkDataSource
|
||||
|
||||
object AuthRepository {
|
||||
private const val STORE = "AUTH-STORE"
|
||||
private const val CODE_KEY = "CODE"
|
||||
private const val TOKEN_KEY = "TOKEN"
|
||||
|
||||
private var codeCache: String? = null
|
||||
private var tokenCache: String? = null
|
||||
|
||||
suspend fun checkAndSave(text: String): Result<Boolean> {
|
||||
return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
||||
if (success) {
|
||||
codeCache = text
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(CODE_KEY)
|
||||
preferences[prefKey] = text
|
||||
}
|
||||
suspend fun checkAndSave(login: String, password: String): Result<AuthResponseDto> {
|
||||
val data = AuthRequestDto(login=login, password=password)
|
||||
return NetworkDataSource.checkAuth(data).onSuccess { success ->
|
||||
tokenCache = success.token
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||
preferences[prefKey] = success.token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getCode(): String? {
|
||||
if (codeCache == null) {
|
||||
codeCache = App.context.userDataStore.data
|
||||
suspend fun getToken(): String? {
|
||||
if (tokenCache == null) {
|
||||
tokenCache = App.context.userDataStore.data
|
||||
.firstOrNull()
|
||||
?.let { preferences ->
|
||||
preferences[stringPreferencesKey(CODE_KEY)]
|
||||
preferences[stringPreferencesKey(TOKEN_KEY)]
|
||||
}
|
||||
}
|
||||
return codeCache
|
||||
return tokenCache
|
||||
}
|
||||
|
||||
suspend fun logout() {
|
||||
codeCache = null
|
||||
tokenCache = null
|
||||
App.context.userDataStore.edit { preferences ->
|
||||
val prefKey = stringPreferencesKey(CODE_KEY)
|
||||
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||
preferences.remove(prefKey)
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,8 +10,8 @@ class BookRepository(
|
||||
private val authRepository: AuthRepository
|
||||
) {
|
||||
suspend fun getInfo(): Result<MainInfoEntity> {
|
||||
val code = authRepository.getCode() ?: return getNoAuthResult()
|
||||
return NetworkDataSource.getInfo(code).mapCatching { dto ->
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
return NetworkDataSource.getInfo(token).mapCatching { dto ->
|
||||
MainInfoEntity(
|
||||
name = dto.name ?: error("Name is null"),
|
||||
photoUrl = dto.photoUrl ?: error("Photo url is null"),
|
||||
@ -26,8 +26,8 @@ class BookRepository(
|
||||
}
|
||||
|
||||
suspend fun getBookingInfo(): Result<List<BookingData>> {
|
||||
val code = authRepository.getCode() ?: return getNoAuthResult()
|
||||
return NetworkDataSource.getBooking(code).mapCatching { dto ->
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
return NetworkDataSource.getBooking(token).mapCatching { dto ->
|
||||
dto?.map { (date, places) ->
|
||||
BookingData(
|
||||
date = date,
|
||||
@ -43,9 +43,9 @@ class BookRepository(
|
||||
}
|
||||
|
||||
suspend fun sendBook(data: BookRequestData): Result<Boolean> {
|
||||
val code = authRepository.getCode() ?: return getNoAuthResult()
|
||||
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||
val dto = BookRequestDto(data.date, data.placeId)
|
||||
return NetworkDataSource.addBook(code, dto)
|
||||
return NetworkDataSource.addBook(token, dto)
|
||||
}
|
||||
private fun <T> getNoAuthResult() = Result.failure<T>(
|
||||
IllegalStateException("No auth")
|
||||
|
||||
@ -5,6 +5,7 @@ import io.ktor.client.call.body
|
||||
import io.ktor.client.engine.cio.CIO
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.request.get
|
||||
import io.ktor.client.request.headers
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
@ -16,6 +17,8 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import ru.myitschool.work.core.Constants
|
||||
import ru.myitschool.work.data.dto.AuthRequestDto
|
||||
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||
import ru.myitschool.work.data.dto.PlaceDto
|
||||
import ru.myitschool.work.data.dto.BookRequestDto
|
||||
import ru.myitschool.work.data.dto.UserDto
|
||||
@ -36,20 +39,27 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||
suspend fun checkAuth(data: AuthRequestDto): Result<AuthResponseDto> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.get(getUrl(code, Constants.AUTH_URL))
|
||||
val response = client.post(getUrl(Constants.BOOK_URL)) {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(data)
|
||||
}
|
||||
when (response.status) {
|
||||
HttpStatusCode.OK -> true
|
||||
else -> false
|
||||
HttpStatusCode.OK -> response.body<AuthResponseDto>()
|
||||
else -> error(response.bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getInfo(code: String): Result<UserDto> = withContext(Dispatchers.IO) {
|
||||
suspend fun getInfo(token: String): Result<UserDto> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
println("!!!!!!!!!!!!!! getInfo $code")
|
||||
val response = client.get(getUrl(code, Constants.INFO_URL))
|
||||
println("!!!!!!!!!!!!!! getInfo")
|
||||
val response = client.get(getUrl(Constants.INFO_URL)) {
|
||||
headers {
|
||||
append("Authorization", "Bearer $token")
|
||||
}
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
println("!!!!!!!!!!!!!! getInfo OK ${response.bodyAsText()}")
|
||||
response.body<UserDto>()
|
||||
@ -60,9 +70,13 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getBooking(code: String): Result<Map<String, List<PlaceDto>>?> = withContext(Dispatchers.IO) {
|
||||
suspend fun getBooking(token: String): Result<Map<String, List<PlaceDto>>?> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.get(getUrl(code, Constants.BOOKING_URL))
|
||||
val response = client.get(getUrl(Constants.BOOKING_URL)) {
|
||||
headers {
|
||||
append("Authorization", "Bearer $token")
|
||||
}
|
||||
}
|
||||
if (response.status == HttpStatusCode.OK) {
|
||||
response.body<Map<String, List<PlaceDto>>>()
|
||||
} else {
|
||||
@ -71,11 +85,14 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addBook(code: String, data: BookRequestDto): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||
suspend fun addBook(token: String, data: BookRequestDto): Result<Boolean> = withContext(Dispatchers.IO) {
|
||||
return@withContext runCatching {
|
||||
val response = client.post(getUrl(code, Constants.BOOK_URL)) {
|
||||
val response = client.post(getUrl(Constants.BOOK_URL)) {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(data)
|
||||
headers {
|
||||
append("Authorization", "Bearer $token")
|
||||
}
|
||||
}
|
||||
when (response.status) {
|
||||
HttpStatusCode.Created -> true
|
||||
@ -85,5 +102,5 @@ object NetworkDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
|
||||
private fun getUrl(targetUrl: String) = "${Constants.HOST}/api/$targetUrl"
|
||||
}
|
||||
@ -1,15 +1,15 @@
|
||||
package ru.myitschool.work.domain.auth
|
||||
|
||||
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||
import ru.myitschool.work.data.repo.AuthRepository
|
||||
|
||||
class CheckAndSaveAuthCodeUseCase(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
text: String
|
||||
): Result<Unit> {
|
||||
return repository.checkAndSave(text).mapCatching { success ->
|
||||
if (!success) error("Code is incorrect")
|
||||
}
|
||||
login: String,
|
||||
password: String
|
||||
): Result<AuthResponseDto> {
|
||||
return repository.checkAndSave(login, password)
|
||||
}
|
||||
}
|
||||
@ -2,11 +2,18 @@ package ru.myitschool.work.domain.auth
|
||||
|
||||
class CheckCodeFormatUseCase {
|
||||
operator fun invoke(
|
||||
text: String
|
||||
login: String, password: String
|
||||
): Boolean {
|
||||
return text.length == 4 && text.all { char ->
|
||||
char.isLetterOrDigit() &&
|
||||
((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit())
|
||||
val passwordList = password.toList()
|
||||
var passwordCorrect = true
|
||||
for (i in 1..(passwordList.size - 3)) {
|
||||
val s = StringBuilder().append(passwordList[i]).append(passwordList[i+1]).append(passwordList[i+2]).toString()
|
||||
if (login.contains(s)) {
|
||||
passwordCorrect = false
|
||||
}
|
||||
}
|
||||
return login.isNotEmpty() && password.length >= 8 && passwordCorrect && login.all { char ->
|
||||
char.isLetterOrDigit() && ((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit())
|
||||
} && password.all({ char -> password.count { it == char } < 3 }) && password.count { it == '!' } + password.count { it == '@' } + password.count { it == '#' } + password.count { it == '"' } + password.count { it == '№' } + password.count { it == ';' } + password.count { it == '$' } + password.count { it == '%' } + password.count { it == '^' } + password.count { it == ':' } + password.count { it == '&' } + password.count { it == '?' } + password.count { it == '*' } >= 1
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,6 @@ class GetCodeUseCase(
|
||||
private val repository: AuthRepository
|
||||
) {
|
||||
suspend operator fun invoke(): String? {
|
||||
return repository.getCode()
|
||||
return repository.getToken()
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package ru.myitschool.work.ui.screen.auth
|
||||
|
||||
sealed interface AuthIntent {
|
||||
data class Send(val text: String): AuthIntent
|
||||
data class TextInput(val text: String): AuthIntent
|
||||
data class Send(val login: String, val password: String): AuthIntent
|
||||
data class TextInput(val login: String, val password: String): AuthIntent
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package ru.myitschool.work.ui.screen.auth
|
||||
|
||||
import android.os.CountDownTimer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@ -28,28 +29,69 @@ class AuthViewModel : ViewModel() {
|
||||
private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow()
|
||||
val actionFlow: SharedFlow<AuthAction> = _actionFlow
|
||||
|
||||
private var authTries: Int = 0
|
||||
private var timerSecs: Int = 0
|
||||
|
||||
fun onIntent(intent: AuthIntent) {
|
||||
when (intent) {
|
||||
is AuthIntent.Send -> {
|
||||
viewModelScope.launch {
|
||||
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
|
||||
onSuccess = {
|
||||
_actionFlow.emit(AuthAction.Open(MainScreenDestination))
|
||||
},
|
||||
onFailure = { error ->
|
||||
if (authTries >= 5) {
|
||||
timerSecs = 60
|
||||
val timer = object : CountDownTimer((timerSecs * 1000).toLong(), 1000) {
|
||||
override fun onTick(millisUntilFinished: Long) {
|
||||
timerSecs -= 1
|
||||
updateStateIfData { oldState ->
|
||||
oldState.copy(
|
||||
error = error.message
|
||||
error = "Слишком много попыток входа, попробуйте через $timerSecs секунд"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
override fun onFinish() {
|
||||
authTries = 0
|
||||
updateStateIfData { oldState ->
|
||||
oldState.copy(
|
||||
isEnabledSend = checkCodeFormatUseCase.invoke(
|
||||
login = intent.login,
|
||||
password = intent.password
|
||||
),
|
||||
error = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.start()
|
||||
updateStateIfData { oldState ->
|
||||
oldState.copy(
|
||||
error = "Слишком много попыток входа, попробуйте через $timerSecs секунд"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
viewModelScope.launch {
|
||||
checkAndSaveAuthCodeUseCase.invoke(intent.login, intent.password).fold(
|
||||
onSuccess = {
|
||||
_actionFlow.emit(AuthAction.Open(MainScreenDestination))
|
||||
},
|
||||
onFailure = { error ->
|
||||
authTries += 1
|
||||
updateStateIfData { oldState ->
|
||||
oldState.copy(
|
||||
error = error.message
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is AuthIntent.TextInput -> {
|
||||
updateStateIfData { oldState ->
|
||||
oldState.copy(
|
||||
isEnabledSend = checkCodeFormatUseCase.invoke(intent.text),
|
||||
isEnabledSend = checkCodeFormatUseCase.invoke(
|
||||
login = intent.login,
|
||||
password = intent.password
|
||||
),
|
||||
error = null
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user