feat: add :backend:config module with ConfigApiClient (Step 3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kohei 2026-02-26 20:41:22 +09:00
parent 0ede5c690b
commit ea460f7ef0
5 changed files with 129 additions and 0 deletions

View file

@ -64,6 +64,8 @@ dependencies {
implementation(project(":core:domain"))
implementation(project(":core:data"))
implementation(project(":core:ui"))
implementation(project(":backend:api"))
implementation(project(":backend:config"))
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)

View file

@ -0,0 +1,45 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.serialization)
}
android {
namespace = "io.clawdroid.backend.config"
compileSdk = libs.versions.android.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.android.minSdk.get().toInt()
}
buildFeatures {
compose = true
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}
dependencies {
implementation(project(":backend:api"))
implementation(project(":core:ui"))
implementation(libs.ktor.client.okhttp)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.json)
implementation(libs.serialization.json)
implementation(platform(libs.compose.bom))
implementation(libs.compose.ui)
implementation(libs.compose.material3)
implementation(libs.navigation.compose)
implementation(libs.koin.android)
implementation(libs.koin.compose)
implementation(libs.coroutines.android)
debugImplementation(libs.compose.ui.tooling)
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View file

@ -0,0 +1,79 @@
package io.clawdroid.backend.config
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.request.put
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.http.ContentType
import io.ktor.http.contentType
import io.ktor.http.isSuccess
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import java.io.Closeable
import java.io.IOException
@Serializable
data class ConfigSchema(val sections: List<SchemaSection>)
@Serializable
data class SchemaSection(val key: String, val label: String, val fields: List<SchemaField>)
@Serializable
data class SchemaField(
val key: String,
val label: String,
val type: String,
val secret: Boolean = false,
val default: JsonElement = Json.parseToJsonElement("null"),
)
@Serializable
data class SaveConfigResult(
val status: String? = null,
val restart: Boolean = false,
val error: String? = null,
)
class ConfigApiClient : Closeable {
private val baseUrl: String get() = "http://127.0.0.1:18790"
private val client = HttpClient(OkHttp) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true; isLenient = true })
}
}
suspend fun getSchema(): ConfigSchema {
return client.get("$baseUrl/api/config/schema").ensureSuccess().body()
}
suspend fun getConfig(): JsonObject {
return client.get("$baseUrl/api/config").ensureSuccess().body()
}
suspend fun saveConfig(config: JsonObject): SaveConfigResult {
return client.put("$baseUrl/api/config") {
contentType(ContentType.Application.Json)
setBody(config)
}.ensureSuccess().body()
}
override fun close() {
client.close()
}
private suspend fun HttpResponse.ensureSuccess(): HttpResponse {
if (!status.isSuccess()) {
val error = runCatching { body<SaveConfigResult>().error }.getOrNull()
throw IOException("HTTP ${status.value}: ${error ?: "request failed"}")
}
return this
}
}

View file

@ -19,3 +19,4 @@ include(":core:domain")
include(":core:data")
include(":core:ui")
include(":backend:api")
include(":backend:config")