mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
@@ -10,11 +10,10 @@ import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.request.post
|
||||
import io.ktor.client.request.setBody
|
||||
import io.ktor.http.ContentType
|
||||
import io.ktor.http.URLBuilder
|
||||
import io.ktor.http.contentType
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
import ru.fromchat.API_HOST
|
||||
import ru.fromchat.utils.failOnError
|
||||
|
||||
object ApiClient {
|
||||
private val json = Json {
|
||||
@@ -45,28 +44,21 @@ object ApiClient {
|
||||
var token: String? = null
|
||||
private set
|
||||
|
||||
private fun apiUrlBuilder(): URLBuilder {
|
||||
return URLBuilder().apply {
|
||||
protocol = io.ktor.http.URLProtocol.HTTPS
|
||||
host = API_HOST
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun login(request: LoginRequest): LoginResponse {
|
||||
// Endpoint exists in web: POST /login with {username,password}
|
||||
val response = http.post("https://fromchat.ru/api/login") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}.body<LoginResponse>()
|
||||
token = response.token
|
||||
return response
|
||||
}
|
||||
|
||||
suspend fun register(request: RegisterRequest) {
|
||||
// Endpoint exists in web: POST /register with {username,password,confirm_password}
|
||||
http.post("https://fromchat.ru/api/register") {
|
||||
suspend fun login(request: LoginRequest) =
|
||||
http
|
||||
.post("https://fromchat.ru/api/login") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
.failOnError()
|
||||
.body<LoginResponse>()
|
||||
.also { token = it.token }
|
||||
|
||||
suspend fun register(request: RegisterRequest) =
|
||||
http
|
||||
.post("https://fromchat.ru/api/register") {
|
||||
contentType(ContentType.Application.Json)
|
||||
setBody(request)
|
||||
}
|
||||
.failOnError()
|
||||
}
|
||||
@@ -17,7 +17,7 @@ data class RegisterRequest(
|
||||
|
||||
@Serializable
|
||||
data class ErrorResponse(
|
||||
val message: String? = null
|
||||
val detail: String
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package ru.fromchat.api
|
||||
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.plugins.ClientRequestException
|
||||
|
||||
suspend inline fun <Response> apiRequest(
|
||||
onError: (String, Exception) -> Unit,
|
||||
onSuccess: (Response) -> Unit,
|
||||
request: suspend () -> Response
|
||||
): Result<Response> {
|
||||
try {
|
||||
val response = request()
|
||||
onSuccess(response)
|
||||
return Result.success(response)
|
||||
} catch (e: ClientRequestException) {
|
||||
onError(
|
||||
if (e.response.status.value in arrayOf(401, 403)) {
|
||||
e.response.body<ErrorResponse>().detail
|
||||
} else {
|
||||
"Unexpected error"
|
||||
},
|
||||
e
|
||||
)
|
||||
return Result.failure(e)
|
||||
} catch (e: Exception) {
|
||||
onError("Unexpected error", e)
|
||||
return Result.failure(e)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package ru.fromchat.ui.auth
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
@@ -16,16 +17,16 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.fromchat.api.ApiClient
|
||||
import ru.fromchat.api.LoginRequest
|
||||
import ru.fromchat.api.apiRequest
|
||||
import ru.fromchat.ui.RowHeader
|
||||
|
||||
@Composable
|
||||
@@ -38,6 +39,8 @@ fun LoginScreen(
|
||||
var password by remember { mutableStateOf("") }
|
||||
var alert by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -84,12 +87,15 @@ fun LoginScreen(
|
||||
return@Button
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
scope.launch {
|
||||
apiRequest(
|
||||
onError = { message, e ->
|
||||
Log.e("LoginScreen", "An error ocurred while logging in:", e)
|
||||
alert = message
|
||||
},
|
||||
onSuccess = { onLoginSuccess() }
|
||||
) {
|
||||
ApiClient.login(LoginRequest(username, password))
|
||||
onLoginSuccess()
|
||||
} catch (e: Exception) {
|
||||
alert = "Неверное имя пользователя или пароль"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import ru.fromchat.api.ApiClient
|
||||
import ru.fromchat.api.RegisterRequest
|
||||
import ru.fromchat.api.apiRequest
|
||||
import ru.fromchat.ui.RowHeader
|
||||
|
||||
@Composable
|
||||
@@ -38,6 +38,8 @@ fun RegisterScreen(
|
||||
var confirmPassword by remember { mutableStateOf("") }
|
||||
var alert by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -105,8 +107,14 @@ fun RegisterScreen(
|
||||
}
|
||||
|
||||
// Send the register request
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
scope.launch {
|
||||
apiRequest(
|
||||
onError = { message, e ->
|
||||
Log.d("RegisterScreen", "Error while registering:", e)
|
||||
alert = message
|
||||
},
|
||||
onSuccess = { onRegistered() }
|
||||
) {
|
||||
ApiClient.register(
|
||||
RegisterRequest(
|
||||
username,
|
||||
@@ -114,10 +122,6 @@ fun RegisterScreen(
|
||||
confirmPassword
|
||||
)
|
||||
)
|
||||
onRegistered()
|
||||
} catch (e: Exception) {
|
||||
Log.d("RegisterScreen", "Error while registering:", e)
|
||||
alert = "Ошибка при регистрации"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
package ru.fromchat.utils
|
||||
|
||||
import android.util.Log
|
||||
import io.ktor.client.HttpClientConfig
|
||||
import io.ktor.client.plugins.ClientRequestException
|
||||
import io.ktor.client.plugins.DefaultRequest
|
||||
import io.ktor.client.plugins.ResponseException
|
||||
import io.ktor.client.plugins.ServerResponseException
|
||||
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
|
||||
import io.ktor.client.plugins.logging.DEFAULT
|
||||
import io.ktor.client.plugins.logging.LogLevel
|
||||
import io.ktor.client.plugins.logging.Logger
|
||||
import io.ktor.client.plugins.logging.Logging
|
||||
import io.ktor.client.plugins.logging.LoggingConfig
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import io.ktor.serialization.Configuration
|
||||
import io.ktor.serialization.kotlinx.json.json
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonBuilder
|
||||
|
||||
/**
|
||||
* Configures the Ktor client for kotlinx.serialization JSON with custom settings.
|
||||
* @param settings Lambda to configure the [JsonBuilder].
|
||||
*/
|
||||
@PublishedApi
|
||||
internal inline fun Configuration.json(crossinline settings: JsonBuilder.() -> Unit) {
|
||||
json(
|
||||
Json {
|
||||
settings()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs [ContentNegotiation] with JSON support and custom settings in a Ktor client config.
|
||||
* @param settings Lambda to configure the [JsonBuilder].
|
||||
*/
|
||||
inline fun HttpClientConfig<*>.jsonConfig(
|
||||
crossinline settings: JsonBuilder.() -> Unit
|
||||
) = install(ContentNegotiation) {
|
||||
json {
|
||||
settings()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs [DefaultRequest] in a Ktor client config.
|
||||
* @param settings Lambda to configure the [DefaultRequest.DefaultRequestBuilder].
|
||||
*/
|
||||
inline fun HttpClientConfig<*>.defaultRequest(
|
||||
crossinline settings: DefaultRequest.DefaultRequestBuilder.() -> Unit
|
||||
) = install(DefaultRequest) {
|
||||
settings()
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs [Logging] in a Ktor client config.
|
||||
* @param settings Lambda to configure the [LoggingConfig].
|
||||
*/
|
||||
inline fun HttpClientConfig<*>.logging(
|
||||
crossinline settings: LoggingConfig.() -> Unit
|
||||
) = install(Logging) {
|
||||
settings()
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs [Logging] in a Ktor client config with default settings (all logs).
|
||||
*/
|
||||
inline fun HttpClientConfig<*>.logging() = logging {
|
||||
logger = Logger.DEFAULT
|
||||
level = LogLevel.ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws if the HTTP response status code is an error (>=400).
|
||||
* @return The [HttpResponse] if successful.
|
||||
* @throws ClientRequestException
|
||||
* @throws ServerResponseException
|
||||
* @throws ResponseException on error.
|
||||
*/
|
||||
suspend fun HttpResponse.failOnError(): HttpResponse {
|
||||
Log.d("failOnError", "Checking the response code...")
|
||||
val statusCode = status.value
|
||||
suspend fun body() = bodyAsText()
|
||||
|
||||
Log.d("failOnError", "code: $statusCode")
|
||||
|
||||
when (statusCode) {
|
||||
in 400..499 -> throw ClientRequestException(this, body())
|
||||
in 500..599 -> throw ServerResponseException(this, body())
|
||||
}
|
||||
|
||||
if (statusCode >= 600) {
|
||||
throw ResponseException(this, body())
|
||||
}
|
||||
|
||||
Log.d("failOnError", "Success")
|
||||
|
||||
return this
|
||||
}
|
||||
Reference in New Issue
Block a user