use axum::Json;
use axum::extract::State;
use axum::http::StatusCode;
use kanade_shared::kv::{BUCKET_FLEET_CONFIG, KEY_FREEZE};
use kanade_shared::manifest::Freeze;
use tracing::info;
use crate::api::AppState;
use crate::audit;
use crate::audit::Caller;
pub async fn get(State(s): State<AppState>) -> Result<Json<Option<Freeze>>, (StatusCode, String)> {
let kv = s
.jetstream
.get_key_value(BUCKET_FLEET_CONFIG)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("fleet_config bucket unreachable: {e}"),
)
})?;
match kv.get(KEY_FREEZE).await {
Ok(Some(bytes)) => match serde_json::from_slice::<Freeze>(&bytes) {
Ok(freeze) => Ok(Json(Some(freeze))),
Err(e) => Err((
StatusCode::INTERNAL_SERVER_ERROR,
format!("stored freeze is corrupt: {e}"),
)),
},
Ok(None) => Ok(Json(None)),
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, format!("KV get: {e}"))),
}
}
pub async fn set(
State(s): State<AppState>,
caller: Caller,
Json(freeze): Json<Freeze>,
) -> Result<Json<Freeze>, (StatusCode, String)> {
if let Err(e) = freeze.validate() {
return Err((StatusCode::BAD_REQUEST, format!("invalid freeze: {e}")));
}
let kv = s
.jetstream
.get_key_value(BUCKET_FLEET_CONFIG)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("fleet_config bucket unreachable: {e}"),
)
})?;
let body = serde_json::to_vec(&freeze)
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("serialize: {e}")))?;
kv.put(KEY_FREEZE, body.into())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("KV put: {e}")))?;
info!(
from = ?freeze.from,
until = ?freeze.until,
reason = ?freeze.reason,
"fleet change-freeze set",
);
audit::record(
&s.nats,
"operator",
"freeze_set",
Some(KEY_FREEZE),
Some(&caller),
serde_json::json!({
"from": freeze.from,
"until": freeze.until,
"reason": freeze.reason,
}),
)
.await;
Ok(Json(freeze))
}
pub async fn clear(
State(s): State<AppState>,
caller: Caller,
) -> Result<StatusCode, (StatusCode, String)> {
let kv = s
.jetstream
.get_key_value(BUCKET_FLEET_CONFIG)
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("fleet_config bucket unreachable: {e}"),
)
})?;
kv.delete(KEY_FREEZE)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("KV delete: {e}")))?;
info!("fleet change-freeze cleared");
audit::record(
&s.nats,
"operator",
"freeze_clear",
Some(KEY_FREEZE),
Some(&caller),
serde_json::json!({}),
)
.await;
Ok(StatusCode::NO_CONTENT)
}