refactor: move api request logic completely out of core

This commit is contained in:
Cilly Leang 2026-02-05 01:52:14 +11:00
parent f606eb2e33
commit 0a5b0f532a
Signed by: cilly
GPG key ID: 6500251E087653C9
5 changed files with 74 additions and 57 deletions

View file

@ -11,6 +11,8 @@ import io.ktor.client.plugins.plugin
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.appendAll
@ -28,7 +30,7 @@ class ApiClient {
}
@OptIn(ExperimentalSerializationApi::class)
val client = HttpClient {
internal val client = HttpClient {
expectSuccess = true
install(ContentNegotiation) {
json(ApiConstants.json)
@ -37,6 +39,7 @@ class ApiClient {
install(HttpCookies)
defaultRequest {
url("https://discord.com/api/v9/")
contentType(ContentType.Application.Json)
userAgent(ApiConstants.userAgent)
headers.appendAll(ApiConstants.baseHeaders)
}

View file

@ -0,0 +1,46 @@
package moe.lava.neon.api.endpoints
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import kotlinx.serialization.Serializable
import moe.lava.neon.api.ApiClient
@Serializable
data class ExperimentResponse(
val fingerprint: String,
)
@Serializable
private data class LoginRequest(
val login: String,
val password: String,
val undelete: Boolean = false,
val loginSource: String? = null,
val giftCodeSkuId: String? = null,
)
@Serializable
data class LoginResponse(
val userId: String,
val token: String,
val userSettings: UserSettings,
) {
@Serializable
data class UserSettings(val locale: String, val theme: String)
}
suspend fun ApiClient.getExperiments() = client.get("experiments") {
parameter("with_guild_experiments", "true")
}.body<ExperimentResponse>()
suspend fun ApiClient.login(email: String, password: String, fingerprint: String) = client.post("auth/login") {
header("X-Fingerprint", fingerprint)
setBody(LoginRequest(
login = email,
password = password,
))
}.body<LoginResponse>()

View file

@ -21,8 +21,6 @@ kotlin {
implementation(project(":api:gateway"))
implementation(project(":api:rest"))
implementation(project(":common"))
implementation(libs.ktor.client.core)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.core)

View file

@ -1,43 +1,14 @@
package moe.lava.neon.core.repository
import co.touchlab.kermit.Logger
import io.ktor.client.call.body
import io.ktor.client.request.get
import io.ktor.client.request.header
import io.ktor.client.request.parameter
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.http.ContentType
import io.ktor.http.contentType
import kotlinx.serialization.Serializable
import moe.lava.neon.api.ApiClient
import moe.lava.neon.api.endpoints.getExperiments
import moe.lava.neon.api.endpoints.login
import moe.lava.neon.core.AppSettings
@Serializable
private data class ExperimentResponse(
val fingerprint: String,
)
@Serializable
private data class LoginRequest(
val login: String,
val password: String,
val undelete: Boolean = false,
val loginSource: String? = null,
val giftCodeSkuId: String? = null,
)
@Serializable
private data class LoginResponse(
val userId: String,
val token: String,
val userSettings: UserSettings,
) {
@Serializable
data class UserSettings(val locale: String, val theme: String)
}
sealed class AuthResponse {
// TODO: Specify all possible error types here
data class Failed(val error: Throwable) : AuthResponse()
data class Success(val token: String) : AuthResponse()
// TODO
// data class MFARequested() : AuthResponse()
@ -57,24 +28,23 @@ class AuthRepository internal constructor(
email: String,
password: String,
): AuthResponse {
try {
if (fingerprint == null) {
fingerprint = api.client.get("experiments") {
parameter("with_guild_experiments", "true")
}.body<ExperimentResponse>().fingerprint
fingerprint = api.getExperiments().fingerprint
}
val res = api.client.post("auth/login") {
header("X-Fingerprint", fingerprint)
contentType(ContentType.Application.Json)
setBody(LoginRequest(
login = email,
val login = api.login(
email = email,
password = password,
))
fingerprint = fingerprint!!,
)
logger.i { "Login success $login" }
this.token = login.token
return AuthResponse.Success(login.token)
} catch (e: Throwable) {
return AuthResponse.Failed(e)
}
val body = res.body<LoginResponse>()
logger.i { "Login success $body" }
this.token = body.token
return AuthResponse.Success(body.token)
}
fun login(token: String): String {

View file

@ -122,13 +122,13 @@ class LoginViewModel(
}
suspend fun login(email: String, password: String): LoginResult {
return try {
when (val res = auth.login(email, password)) {
return when (val res = auth.login(email, password)) {
is AuthResponse.Success -> LoginResult.Success
}
} catch(e: Throwable) {
is AuthResponse.Failed -> {
val e = res.error
logger.e(e) { "Login failed" }
LoginResult.Failed(e.toString())
}
}
}
}