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 androidx.datastore.preferences.preferencesDataStore
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import ru.myitschool.work.App
|
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
|
import ru.myitschool.work.data.source.NetworkDataSource
|
||||||
|
|
||||||
object AuthRepository {
|
object AuthRepository {
|
||||||
private const val STORE = "AUTH-STORE"
|
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> {
|
suspend fun checkAndSave(login: String, password: String): Result<AuthResponseDto> {
|
||||||
return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
val data = AuthRequestDto(login=login, password=password)
|
||||||
if (success) {
|
return NetworkDataSource.checkAuth(data).onSuccess { success ->
|
||||||
codeCache = text
|
tokenCache = success.token
|
||||||
App.context.userDataStore.edit { preferences ->
|
App.context.userDataStore.edit { preferences ->
|
||||||
val prefKey = stringPreferencesKey(CODE_KEY)
|
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||||
preferences[prefKey] = text
|
preferences[prefKey] = success.token
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCode(): String? {
|
suspend fun getToken(): String? {
|
||||||
if (codeCache == null) {
|
if (tokenCache == null) {
|
||||||
codeCache = App.context.userDataStore.data
|
tokenCache = App.context.userDataStore.data
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
?.let { preferences ->
|
?.let { preferences ->
|
||||||
preferences[stringPreferencesKey(CODE_KEY)]
|
preferences[stringPreferencesKey(TOKEN_KEY)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return codeCache
|
return tokenCache
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun logout() {
|
suspend fun logout() {
|
||||||
codeCache = null
|
tokenCache = null
|
||||||
App.context.userDataStore.edit { preferences ->
|
App.context.userDataStore.edit { preferences ->
|
||||||
val prefKey = stringPreferencesKey(CODE_KEY)
|
val prefKey = stringPreferencesKey(TOKEN_KEY)
|
||||||
preferences.remove(prefKey)
|
preferences.remove(prefKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ class BookRepository(
|
|||||||
private val authRepository: AuthRepository
|
private val authRepository: AuthRepository
|
||||||
) {
|
) {
|
||||||
suspend fun getInfo(): Result<MainInfoEntity> {
|
suspend fun getInfo(): Result<MainInfoEntity> {
|
||||||
val code = authRepository.getCode() ?: return getNoAuthResult()
|
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||||
return NetworkDataSource.getInfo(code).mapCatching { dto ->
|
return NetworkDataSource.getInfo(token).mapCatching { dto ->
|
||||||
MainInfoEntity(
|
MainInfoEntity(
|
||||||
name = dto.name ?: error("Name is null"),
|
name = dto.name ?: error("Name is null"),
|
||||||
photoUrl = dto.photoUrl ?: error("Photo url is null"),
|
photoUrl = dto.photoUrl ?: error("Photo url is null"),
|
||||||
@ -26,8 +26,8 @@ class BookRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getBookingInfo(): Result<List<BookingData>> {
|
suspend fun getBookingInfo(): Result<List<BookingData>> {
|
||||||
val code = authRepository.getCode() ?: return getNoAuthResult()
|
val token = authRepository.getToken() ?: return getNoAuthResult()
|
||||||
return NetworkDataSource.getBooking(code).mapCatching { dto ->
|
return NetworkDataSource.getBooking(token).mapCatching { dto ->
|
||||||
dto?.map { (date, places) ->
|
dto?.map { (date, places) ->
|
||||||
BookingData(
|
BookingData(
|
||||||
date = date,
|
date = date,
|
||||||
@ -43,9 +43,9 @@ class BookRepository(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun sendBook(data: BookRequestData): Result<Boolean> {
|
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)
|
val dto = BookRequestDto(data.date, data.placeId)
|
||||||
return NetworkDataSource.addBook(code, dto)
|
return NetworkDataSource.addBook(token, dto)
|
||||||
}
|
}
|
||||||
private fun <T> getNoAuthResult() = Result.failure<T>(
|
private fun <T> getNoAuthResult() = Result.failure<T>(
|
||||||
IllegalStateException("No auth")
|
IllegalStateException("No auth")
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import io.ktor.client.call.body
|
|||||||
import io.ktor.client.engine.cio.CIO
|
import io.ktor.client.engine.cio.CIO
|
||||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||||
import io.ktor.client.request.get
|
import io.ktor.client.request.get
|
||||||
|
import io.ktor.client.request.headers
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
import io.ktor.client.request.setBody
|
import io.ktor.client.request.setBody
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
@ -16,6 +17,8 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import ru.myitschool.work.core.Constants
|
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.PlaceDto
|
||||||
import ru.myitschool.work.data.dto.BookRequestDto
|
import ru.myitschool.work.data.dto.BookRequestDto
|
||||||
import ru.myitschool.work.data.dto.UserDto
|
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 {
|
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) {
|
when (response.status) {
|
||||||
HttpStatusCode.OK -> true
|
HttpStatusCode.OK -> response.body<AuthResponseDto>()
|
||||||
else -> false
|
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 {
|
return@withContext runCatching {
|
||||||
println("!!!!!!!!!!!!!! getInfo $code")
|
println("!!!!!!!!!!!!!! getInfo")
|
||||||
val response = client.get(getUrl(code, Constants.INFO_URL))
|
val response = client.get(getUrl(Constants.INFO_URL)) {
|
||||||
|
headers {
|
||||||
|
append("Authorization", "Bearer $token")
|
||||||
|
}
|
||||||
|
}
|
||||||
if (response.status == HttpStatusCode.OK) {
|
if (response.status == HttpStatusCode.OK) {
|
||||||
println("!!!!!!!!!!!!!! getInfo OK ${response.bodyAsText()}")
|
println("!!!!!!!!!!!!!! getInfo OK ${response.bodyAsText()}")
|
||||||
response.body<UserDto>()
|
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 {
|
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) {
|
if (response.status == HttpStatusCode.OK) {
|
||||||
response.body<Map<String, List<PlaceDto>>>()
|
response.body<Map<String, List<PlaceDto>>>()
|
||||||
} else {
|
} 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 {
|
return@withContext runCatching {
|
||||||
val response = client.post(getUrl(code, Constants.BOOK_URL)) {
|
val response = client.post(getUrl(Constants.BOOK_URL)) {
|
||||||
contentType(ContentType.Application.Json)
|
contentType(ContentType.Application.Json)
|
||||||
setBody(data)
|
setBody(data)
|
||||||
|
headers {
|
||||||
|
append("Authorization", "Bearer $token")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
when (response.status) {
|
when (response.status) {
|
||||||
HttpStatusCode.Created -> true
|
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
|
package ru.myitschool.work.domain.auth
|
||||||
|
|
||||||
|
import ru.myitschool.work.data.dto.AuthResponseDto
|
||||||
import ru.myitschool.work.data.repo.AuthRepository
|
import ru.myitschool.work.data.repo.AuthRepository
|
||||||
|
|
||||||
class CheckAndSaveAuthCodeUseCase(
|
class CheckAndSaveAuthCodeUseCase(
|
||||||
private val repository: AuthRepository
|
private val repository: AuthRepository
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(
|
suspend operator fun invoke(
|
||||||
text: String
|
login: String,
|
||||||
): Result<Unit> {
|
password: String
|
||||||
return repository.checkAndSave(text).mapCatching { success ->
|
): Result<AuthResponseDto> {
|
||||||
if (!success) error("Code is incorrect")
|
return repository.checkAndSave(login, password)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,11 +2,18 @@ package ru.myitschool.work.domain.auth
|
|||||||
|
|
||||||
class CheckCodeFormatUseCase {
|
class CheckCodeFormatUseCase {
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
text: String
|
login: String, password: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return text.length == 4 && text.all { char ->
|
val passwordList = password.toList()
|
||||||
char.isLetterOrDigit() &&
|
var passwordCorrect = true
|
||||||
((char >= 'A' && char <= 'Z') || (char >= 'a' && char <= 'z') || char.isDigit())
|
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
|
private val repository: AuthRepository
|
||||||
) {
|
) {
|
||||||
suspend operator fun invoke(): String? {
|
suspend operator fun invoke(): String? {
|
||||||
return repository.getCode()
|
return repository.getToken()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package ru.myitschool.work.ui.screen.auth
|
package ru.myitschool.work.ui.screen.auth
|
||||||
|
|
||||||
sealed interface AuthIntent {
|
sealed interface AuthIntent {
|
||||||
data class Send(val text: String): AuthIntent
|
data class Send(val login: String, val password: String): AuthIntent
|
||||||
data class TextInput(val text: String): AuthIntent
|
data class TextInput(val login: String, val password: String): AuthIntent
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package ru.myitschool.work.ui.screen.auth
|
package ru.myitschool.work.ui.screen.auth
|
||||||
|
|
||||||
|
import android.os.CountDownTimer
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -28,28 +29,69 @@ class AuthViewModel : ViewModel() {
|
|||||||
private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow()
|
private val _actionFlow: MutableSharedFlow<AuthAction> = MutableSharedFlow()
|
||||||
val actionFlow: SharedFlow<AuthAction> = _actionFlow
|
val actionFlow: SharedFlow<AuthAction> = _actionFlow
|
||||||
|
|
||||||
|
private var authTries: Int = 0
|
||||||
|
private var timerSecs: Int = 0
|
||||||
|
|
||||||
fun onIntent(intent: AuthIntent) {
|
fun onIntent(intent: AuthIntent) {
|
||||||
when (intent) {
|
when (intent) {
|
||||||
is AuthIntent.Send -> {
|
is AuthIntent.Send -> {
|
||||||
viewModelScope.launch {
|
if (authTries >= 5) {
|
||||||
checkAndSaveAuthCodeUseCase.invoke(intent.text).fold(
|
timerSecs = 60
|
||||||
onSuccess = {
|
val timer = object : CountDownTimer((timerSecs * 1000).toLong(), 1000) {
|
||||||
_actionFlow.emit(AuthAction.Open(MainScreenDestination))
|
override fun onTick(millisUntilFinished: Long) {
|
||||||
},
|
timerSecs -= 1
|
||||||
onFailure = { error ->
|
|
||||||
updateStateIfData { oldState ->
|
updateStateIfData { oldState ->
|
||||||
oldState.copy(
|
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 -> {
|
is AuthIntent.TextInput -> {
|
||||||
updateStateIfData { oldState ->
|
updateStateIfData { oldState ->
|
||||||
oldState.copy(
|
oldState.copy(
|
||||||
isEnabledSend = checkCodeFormatUseCase.invoke(intent.text),
|
isEnabledSend = checkCodeFormatUseCase.invoke(
|
||||||
|
login = intent.login,
|
||||||
|
password = intent.password
|
||||||
|
),
|
||||||
error = null
|
error = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user