1use axum::{
22 extract::{Path, State},
23 http::StatusCode,
24 routing::get,
25 Json, Router,
26};
27use mlua_swarm::blueprint::store::{
28 blueprint_version, BlueprintId, BlueprintStore, CommitMetadata,
29};
30use mlua_swarm::blueprint::Blueprint;
31use mlua_swarm::enhance::{EnhanceSetting, EnhanceSettingInput};
32use mlua_swarm::store::enhance_setting::{
33 EnhanceSettingId, EnhanceSettingStore, EnhanceSettingStoreError,
34};
35use std::sync::Arc;
36
37#[derive(Clone)]
39pub struct EnhanceSettingsState {
40 pub setting_store: Arc<dyn EnhanceSettingStore>,
42 pub bp_store: Arc<dyn BlueprintStore>,
44}
45
46pub fn build_enhance_settings_router(
49 setting_store: Arc<dyn EnhanceSettingStore>,
50 bp_store: Arc<dyn BlueprintStore>,
51) -> Router {
52 let state = EnhanceSettingsState {
53 setting_store,
54 bp_store,
55 };
56 Router::new()
57 .route(
58 "/v1/enhance-settings",
59 get(list_settings).post(post_setting),
60 )
61 .route(
62 "/v1/enhance-settings/:id",
63 get(get_setting).put(put_setting).delete(delete_setting),
64 )
65 .with_state(state)
66}
67
68fn now_ms() -> i64 {
69 std::time::SystemTime::now()
70 .duration_since(std::time::UNIX_EPOCH)
71 .map(|d| d.as_millis() as i64)
72 .unwrap_or(0)
73}
74
75async fn commit_blueprint(
79 bp_store: &Arc<dyn BlueprintStore>,
80 blueprint: &Blueprint,
81 rationale: String,
82) -> Result<(), (StatusCode, String)> {
83 let bp_id = BlueprintId::new(blueprint.id.clone());
84 let v = blueprint_version(blueprint).map_err(|e| {
85 (
86 StatusCode::INTERNAL_SERVER_ERROR,
87 format!("bp version: {e}"),
88 )
89 })?;
90
91 if let Ok(traced) = bp_store.read_head(&bp_id).await {
93 if traced.trace.version == v {
94 return Ok(());
95 }
96 }
97
98 let mut meta = CommitMetadata::seed(bp_id.clone(), v, now_ms());
99 meta.rationale = rationale;
100 bp_store
101 .write_new(&bp_id, blueprint, &[], meta)
102 .await
103 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("bp commit: {e}")))?;
104 Ok(())
105}
106
107async fn list_settings(
108 State(state): State<EnhanceSettingsState>,
109) -> Result<Json<Vec<String>>, (StatusCode, String)> {
110 let ids = state
111 .setting_store
112 .list()
113 .await
114 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
115 Ok(Json(ids.into_iter().map(|id| id.0).collect()))
116}
117
118async fn post_setting(
119 State(state): State<EnhanceSettingsState>,
120 Json(input): Json<EnhanceSettingInput>,
121) -> Result<(StatusCode, Json<EnhanceSetting>), (StatusCode, String)> {
122 let (blueprint, setting) = input.into_ref();
123 let rationale = format!(
124 "enhance-setting POST id={} blueprint_id={}",
125 setting.id, setting.blueprint_id
126 );
127 commit_blueprint(&state.bp_store, &blueprint, rationale).await?;
128 state
129 .setting_store
130 .put(&EnhanceSettingId::new(setting.id.clone()), setting.clone())
131 .await
132 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
133 Ok((StatusCode::CREATED, Json(setting)))
134}
135
136async fn get_setting(
137 State(state): State<EnhanceSettingsState>,
138 Path(id): Path<String>,
139) -> Result<Json<EnhanceSetting>, (StatusCode, String)> {
140 let setting = state
141 .setting_store
142 .get(&EnhanceSettingId::new(id))
143 .await
144 .map_err(|e| match e {
145 EnhanceSettingStoreError::NotFound(id) => (
146 StatusCode::NOT_FOUND,
147 format!("enhance setting not found: {id}"),
148 ),
149 EnhanceSettingStoreError::Other(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
150 })?;
151 Ok(Json(setting))
152}
153
154async fn put_setting(
155 State(state): State<EnhanceSettingsState>,
156 Path(id): Path<String>,
157 Json(input): Json<EnhanceSettingInput>,
158) -> Result<Json<EnhanceSetting>, (StatusCode, String)> {
159 if input.id != id {
160 return Err((
161 StatusCode::BAD_REQUEST,
162 format!("path id {id:?} != body id {:?}", input.id),
163 ));
164 }
165 let (blueprint, setting) = input.into_ref();
166 let rationale = format!(
167 "enhance-setting PUT id={} blueprint_id={}",
168 setting.id, setting.blueprint_id
169 );
170 commit_blueprint(&state.bp_store, &blueprint, rationale).await?;
171 state
172 .setting_store
173 .put(&EnhanceSettingId::new(id), setting.clone())
174 .await
175 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
176 Ok(Json(setting))
177}
178
179async fn delete_setting(
180 State(state): State<EnhanceSettingsState>,
181 Path(id): Path<String>,
182) -> Result<StatusCode, (StatusCode, String)> {
183 state
184 .setting_store
185 .delete(&EnhanceSettingId::new(id))
186 .await
187 .map_err(|e| match e {
188 EnhanceSettingStoreError::NotFound(id) => (
189 StatusCode::NOT_FOUND,
190 format!("enhance setting not found: {id}"),
191 ),
192 EnhanceSettingStoreError::Other(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
193 })?;
194 Ok(StatusCode::NO_CONTENT)
195}