mod api_key;
mod qos_operator_key;
pub use api_key::{KeyCurve, StoredApiKey};
pub use qos_operator_key::StoredQosOperatorKey;
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
const CONFIG_DIR: &str = ".config/turnkey";
const CONFIG_FILE: &str = "tvc.config.toml";
const ORGS_DIR: &str = "orgs";
const API_KEY_FILE: &str = "api_key.json";
const OPERATOR_KEY_FILE: &str = "operator.json";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Config {
pub active_org: Option<String>,
#[serde(default)]
pub orgs: HashMap<String, OrgConfig>,
#[serde(default)]
pub last_created_app_id: HashMap<String, String>,
#[serde(default)]
pub last_operator_ids: HashMap<String, Vec<String>>,
}
pub const API_BASE_URL_PROD: &str = "https://api.turnkey.com";
pub const API_BASE_URL_PREPROD: &str = "https://api.preprod.turnkey.engineering";
pub const API_BASE_URL_DEV: &str = "https://api.dev.turnkey.engineering";
pub const API_BASE_URL_LOCAL: &str = "http://localhost:8081";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrgConfig {
pub id: String,
pub api_key_path: PathBuf,
pub operator_key_path: PathBuf,
#[serde(default = "default_api_base_url")]
pub api_base_url: String,
}
fn default_api_base_url() -> String {
API_BASE_URL_PROD.to_string()
}
pub fn config_dir() -> Result<PathBuf> {
let home = std::env::var("HOME").context("HOME environment variable not set")?;
Ok(PathBuf::from(home).join(CONFIG_DIR))
}
pub fn config_file_path() -> Result<PathBuf> {
Ok(config_dir()?.join(CONFIG_FILE))
}
pub fn orgs_dir() -> Result<PathBuf> {
Ok(config_dir()?.join(ORGS_DIR))
}
pub fn default_org_dir(alias: &str) -> Result<PathBuf> {
Ok(orgs_dir()?.join(alias))
}
pub fn default_api_key_path(alias: &str) -> Result<PathBuf> {
Ok(default_org_dir(alias)?.join(API_KEY_FILE))
}
pub fn default_operator_key_path(alias: &str) -> Result<PathBuf> {
Ok(default_org_dir(alias)?.join(OPERATOR_KEY_FILE))
}
impl Config {
pub async fn load() -> Result<Self> {
let path = config_file_path()?;
if !path.exists() {
return Ok(Config::default());
}
let content = tokio::fs::read_to_string(&path)
.await
.with_context(|| format!("failed to read config file: {}", path.display()))?;
let config: Config = toml::from_str(&content)
.with_context(|| format!("failed to parse config file: {}", path.display()))?;
Ok(config)
}
pub async fn save(&self) -> Result<()> {
let path = config_file_path()?;
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent).await.with_context(|| {
format!("failed to create config directory: {}", parent.display())
})?;
}
let content = toml::to_string_pretty(self).context("failed to serialize config")?;
tokio::fs::write(&path, content)
.await
.with_context(|| format!("failed to write config file: {}", path.display()))?;
Ok(())
}
pub fn active_org_config(&self) -> Option<(&String, &OrgConfig)> {
let alias = self.active_org.as_ref()?;
self.orgs.get(alias).map(|config| (alias, config))
}
pub fn add_org(&mut self, alias: &str, org_id: String, api_base_url: String) -> Result<()> {
let org_config = OrgConfig {
id: org_id,
api_key_path: default_api_key_path(alias)?,
operator_key_path: default_operator_key_path(alias)?,
api_base_url,
};
self.orgs.insert(alias.to_string(), org_config);
Ok(())
}
pub fn set_active_org(&mut self, alias: &str) -> Result<()> {
if !self.orgs.contains_key(alias) {
bail!("organization '{}' not found in config", alias);
}
self.active_org = Some(alias.to_string());
Ok(())
}
pub fn org_aliases(&self) -> Vec<&String> {
self.orgs.keys().collect()
}
pub fn set_last_app_id(&mut self, app_id: &str) -> Result<()> {
let alias = self
.active_org
.as_ref()
.context("no active organization set")?;
self.last_created_app_id
.insert(alias.clone(), app_id.to_string());
Ok(())
}
pub fn get_last_app_id(&self) -> Option<String> {
let alias = self.active_org.as_ref()?;
self.last_created_app_id.get(alias).cloned()
}
pub fn set_last_operator_ids(&mut self, operator_ids: &[String]) -> Result<()> {
let alias = self
.active_org
.as_ref()
.context("no active organization set")?;
self.last_operator_ids
.insert(alias.clone(), operator_ids.to_vec());
Ok(())
}
pub fn get_last_operator_ids(&self) -> Option<Vec<String>> {
let alias = self.active_org.as_ref()?;
self.last_operator_ids.get(alias).cloned()
}
}