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.plugins.websocket.WebSockets
import io.ktor.client.request.header import io.ktor.client.request.header
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.userAgent import io.ktor.http.userAgent
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.appendAll import io.ktor.util.appendAll
@ -28,7 +30,7 @@ class ApiClient {
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
val client = HttpClient { internal val client = HttpClient {
expectSuccess = true expectSuccess = true
install(ContentNegotiation) { install(ContentNegotiation) {
json(ApiConstants.json) json(ApiConstants.json)
@ -37,6 +39,7 @@ class ApiClient {
install(HttpCookies) install(HttpCookies)
defaultRequest { defaultRequest {
url("https://discord.com/api/v9/") url("https://discord.com/api/v9/")
contentType(ContentType.Application.Json)
userAgent(ApiConstants.userAgent) userAgent(ApiConstants.userAgent)
headers.appendAll(ApiConstants.baseHeaders) 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:gateway"))
implementation(project(":api:rest")) implementation(project(":api:rest"))
implementation(project(":common")) implementation(project(":common"))
implementation(libs.ktor.client.core)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(project.dependencies.platform(libs.koin.bom)) implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.core) implementation(libs.koin.core)

View file

@ -1,43 +1,14 @@
package moe.lava.neon.core.repository package moe.lava.neon.core.repository
import co.touchlab.kermit.Logger 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.ApiClient
import moe.lava.neon.api.endpoints.getExperiments
import moe.lava.neon.api.endpoints.login
import moe.lava.neon.core.AppSettings 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 { sealed class AuthResponse {
// TODO: Specify all possible error types here
data class Failed(val error: Throwable) : AuthResponse()
data class Success(val token: String) : AuthResponse() data class Success(val token: String) : AuthResponse()
// TODO // TODO
// data class MFARequested() : AuthResponse() // data class MFARequested() : AuthResponse()
@ -57,24 +28,23 @@ class AuthRepository internal constructor(
email: String, email: String,
password: String, password: String,
): AuthResponse { ): AuthResponse {
if (fingerprint == null) { try {
fingerprint = api.client.get("experiments") { if (fingerprint == null) {
parameter("with_guild_experiments", "true") fingerprint = api.getExperiments().fingerprint
}.body<ExperimentResponse>().fingerprint }
}
val res = api.client.post("auth/login") { val login = api.login(
header("X-Fingerprint", fingerprint) email = email,
contentType(ContentType.Application.Json)
setBody(LoginRequest(
login = email,
password = password, 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 { fun login(token: String): String {

View file

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