use anyhow::Result;
use serde::{Deserialize, Serialize};
const TABLE: &str = "settings";
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TeamSettings {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_base_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub embed_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enrich: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub flows: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub guide: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub embed: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_enrich: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_flows: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_concurrency: Option<usize>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_price_in: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub llm_price_out: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub scip: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_index: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_index_llm: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub auto_index_minutes: Option<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub repos: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct Stored {
pub settings: TeamSettings,
pub version: Option<u64>,
pub changed_by: Option<String>,
pub changed_at: Option<String>,
}
fn uri(root: &str) -> String {
format!("{}/_team", root.trim_end_matches('/'))
}
pub async fn load(root: &str) -> Result<Stored> {
let s = crate::docstore::load::<TeamSettings>(&uri(root), TABLE).await?;
Ok(Stored {
settings: s.doc,
version: s.version,
changed_by: s.changed_by,
changed_at: s.changed_at,
})
}
pub async fn save_as(
root: &str,
settings: &TeamSettings,
expected: Option<u64>,
by: &str,
) -> Result<u64> {
crate::docstore::save(&uri(root), TABLE, settings, expected, by)
.await
.map_err(|e| match format!("{e}").contains("changed meanwhile") {
true => anyhow::anyhow!(
"the team settings were changed by someone else; reload and try again"
),
false => e,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn save_load_and_refuse_a_stale_write() {
let dir = std::env::temp_dir().join(format!("comindly-team-{}", std::process::id()));
let root = dir.to_str().unwrap();
let first = TeamSettings {
llm_model: Some("m1".into()),
..Default::default()
};
let v1 = save_as(root, &first, None, "t").await.unwrap();
assert_eq!(load(root).await.unwrap().settings, first);
let second = TeamSettings {
llm_model: Some("m2".into()),
..Default::default()
};
save_as(root, &second, Some(v1), "t").await.unwrap();
assert!(
save_as(root, &first, Some(v1), "t").await.is_err(),
"stale version"
);
assert_eq!(
load(root).await.unwrap().settings.llm_model.as_deref(),
Some("m2")
);
std::fs::remove_dir_all(&dir).ok();
}
}