use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use kanade_shared::kv::{BUCKET_SERVER_SETTINGS, KEY_SERVER_SETTINGS};
use kanade_shared::wire::{MAX_AGENT_PRUNE_DAYS, ServerSettings};
use tracing::{info, warn};
use crate::api::AppState;
use crate::audit;
use crate::audit::Caller;
pub async fn get(State(s): State<AppState>) -> Result<Json<ServerSettings>, (StatusCode, String)> {
let kv = open_bucket(&s).await?;
match kv.get(KEY_SERVER_SETTINGS).await {
Ok(Some(bytes)) => serde_json::from_slice::<ServerSettings>(&bytes)
.map(Json)
.map_err(|e| {
warn!(error = %e, "decode server_settings");
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("stored server_settings is corrupt: {e}"),
)
}),
Ok(None) => Ok(Json(ServerSettings::default())),
Err(e) => {
warn!(error = %e, "read server_settings");
Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("read server_settings: {e}"),
))
}
}
}
pub async fn defaults() -> Json<ServerSettings> {
Json(ServerSettings::defaults())
}
pub async fn put(
State(s): State<AppState>,
caller: Caller,
Json(settings): Json<ServerSettings>,
) -> Result<Json<ServerSettings>, (StatusCode, String)> {
if let Some(days) = settings.agent_prune_days {
if days == 0 {
return Err((
StatusCode::UNPROCESSABLE_ENTITY,
"agent_prune_days must be >= 1; omit it or send null to disable pruning"
.to_string(),
));
}
if days > MAX_AGENT_PRUNE_DAYS {
return Err((
StatusCode::UNPROCESSABLE_ENTITY,
format!("agent_prune_days must be <= {MAX_AGENT_PRUNE_DAYS} (100 years)"),
));
}
}
let kv = open_bucket(&s).await?;
let body = serde_json::to_vec(&settings).map_err(|e| {
warn!(error = %e, "encode server_settings");
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("encode server_settings: {e}"),
)
})?;
kv.put(KEY_SERVER_SETTINGS, body.into())
.await
.map_err(|e| {
warn!(error = %e, "write server_settings");
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("write server_settings: {e}"),
)
})?;
info!(
agent_prune_days = ?settings.agent_prune_days,
"server_settings replaced",
);
audit::record(
&s.nats,
"operator",
"server_settings_set",
Some(KEY_SERVER_SETTINGS),
Some(&caller),
serde_json::to_value(&settings).unwrap_or(serde_json::Value::Null),
)
.await;
Ok(Json(settings))
}
async fn open_bucket(
s: &AppState,
) -> Result<async_nats::jetstream::kv::Store, (StatusCode, String)> {
s.jetstream
.get_key_value(BUCKET_SERVER_SETTINGS)
.await
.map_err(|e| {
warn!(error = %e, bucket = BUCKET_SERVER_SETTINGS, "open server_settings KV bucket");
(
StatusCode::SERVICE_UNAVAILABLE,
format!("server_settings KV bucket unavailable: {e}"),
)
})
}