use axum::{
extract::{Path, State},
http::StatusCode,
routing::get,
Json, Router,
};
use mlua_swarm::blueprint::store::{
blueprint_version, BlueprintId, BlueprintStore, CommitMetadata,
};
use mlua_swarm::blueprint::Blueprint;
use mlua_swarm::enhance::{EnhanceSetting, EnhanceSettingInput};
use mlua_swarm::store::enhance_setting::{
EnhanceSettingId, EnhanceSettingStore, EnhanceSettingStoreError,
};
use std::sync::Arc;
#[derive(Clone)]
pub struct EnhanceSettingsState {
pub setting_store: Arc<dyn EnhanceSettingStore>,
pub bp_store: Arc<dyn BlueprintStore>,
}
pub fn build_enhance_settings_router(
setting_store: Arc<dyn EnhanceSettingStore>,
bp_store: Arc<dyn BlueprintStore>,
) -> Router {
let state = EnhanceSettingsState {
setting_store,
bp_store,
};
Router::new()
.route(
"/v1/enhance-settings",
get(list_settings).post(post_setting),
)
.route(
"/v1/enhance-settings/:id",
get(get_setting).put(put_setting).delete(delete_setting),
)
.with_state(state)
}
fn now_ms() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_millis() as i64)
.unwrap_or(0)
}
async fn commit_blueprint(
bp_store: &Arc<dyn BlueprintStore>,
blueprint: &Blueprint,
rationale: String,
) -> Result<(), (StatusCode, String)> {
let bp_id = BlueprintId::new(blueprint.id.clone());
let v = blueprint_version(blueprint).map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("bp version: {e}"),
)
})?;
if let Ok(traced) = bp_store.read_head(&bp_id).await {
if traced.trace.version == v {
return Ok(());
}
}
let mut meta = CommitMetadata::seed(bp_id.clone(), v, now_ms());
meta.rationale = rationale;
bp_store
.write_new(&bp_id, blueprint, &[], meta)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("bp commit: {e}")))?;
Ok(())
}
async fn list_settings(
State(state): State<EnhanceSettingsState>,
) -> Result<Json<Vec<String>>, (StatusCode, String)> {
let ids = state
.setting_store
.list()
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(ids.into_iter().map(|id| id.0).collect()))
}
async fn post_setting(
State(state): State<EnhanceSettingsState>,
Json(input): Json<EnhanceSettingInput>,
) -> Result<(StatusCode, Json<EnhanceSetting>), (StatusCode, String)> {
let (blueprint, setting) = input.into_ref();
let rationale = format!(
"enhance-setting POST id={} blueprint_id={}",
setting.id, setting.blueprint_id
);
commit_blueprint(&state.bp_store, &blueprint, rationale).await?;
state
.setting_store
.put(&EnhanceSettingId::new(setting.id.clone()), setting.clone())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok((StatusCode::CREATED, Json(setting)))
}
async fn get_setting(
State(state): State<EnhanceSettingsState>,
Path(id): Path<String>,
) -> Result<Json<EnhanceSetting>, (StatusCode, String)> {
let setting = state
.setting_store
.get(&EnhanceSettingId::new(id))
.await
.map_err(|e| match e {
EnhanceSettingStoreError::NotFound(id) => (
StatusCode::NOT_FOUND,
format!("enhance setting not found: {id}"),
),
})?;
Ok(Json(setting))
}
async fn put_setting(
State(state): State<EnhanceSettingsState>,
Path(id): Path<String>,
Json(input): Json<EnhanceSettingInput>,
) -> Result<Json<EnhanceSetting>, (StatusCode, String)> {
if input.id != id {
return Err((
StatusCode::BAD_REQUEST,
format!("path id {id:?} != body id {:?}", input.id),
));
}
let (blueprint, setting) = input.into_ref();
let rationale = format!(
"enhance-setting PUT id={} blueprint_id={}",
setting.id, setting.blueprint_id
);
commit_blueprint(&state.bp_store, &blueprint, rationale).await?;
state
.setting_store
.put(&EnhanceSettingId::new(id), setting.clone())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(setting))
}
async fn delete_setting(
State(state): State<EnhanceSettingsState>,
Path(id): Path<String>,
) -> Result<StatusCode, (StatusCode, String)> {
state
.setting_store
.delete(&EnhanceSettingId::new(id))
.await
.map_err(|e| match e {
EnhanceSettingStoreError::NotFound(id) => (
StatusCode::NOT_FOUND,
format!("enhance setting not found: {id}"),
),
})?;
Ok(StatusCode::NO_CONTENT)
}