use crate::error::{CliError, Result};
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub server: ServerConfig,
pub output: OutputConfig,
pub log: LogConfig,
#[serde(default)]
pub accounts: Vec<Account>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerConfig {
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OutputConfig {
#[serde(default = "default_format")]
pub format: String,
#[serde(default = "default_color")]
pub color: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LogConfig {
#[serde(default = "default_log_level")]
pub level: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
pub name: String,
pub server: String,
pub username: String,
pub token: String,
#[serde(default)]
pub default: bool,
pub role: String,
}
fn default_format() -> String {
"table".to_string()
}
fn default_color() -> bool {
true
}
fn default_log_level() -> String {
"info".to_string()
}
impl Default for Config {
fn default() -> Self {
Self {
server: ServerConfig {
url: "http://localhost:38080".to_string(),
},
output: OutputConfig {
format: default_format(),
color: default_color(),
},
log: LogConfig {
level: default_log_level(),
},
accounts: Vec::new(),
}
}
}
impl Config {
pub fn config_dir() -> Result<PathBuf> {
ProjectDirs::from("com", "oauth-db", "oauth-db-cli")
.map(|dirs| dirs.config_dir().to_path_buf())
.ok_or_else(|| CliError::ConfigError("Failed to determine config directory".to_string()))
}
pub fn config_path() -> Result<PathBuf> {
Ok(Self::config_dir()?.join("config.toml"))
}
pub fn load() -> Result<Self> {
let path = Self::config_path()?;
if !path.exists() {
return Ok(Self::default());
}
let content = fs::read_to_string(&path)?;
let config: Config = toml::from_str(&content)?;
Ok(config)
}
pub fn save(&self) -> Result<()> {
let dir = Self::config_dir()?;
fs::create_dir_all(&dir)?;
let path = Self::config_path()?;
let content = toml::to_string_pretty(self)
.map_err(|e| CliError::ConfigError(format!("Failed to serialize config: {}", e)))?;
fs::write(&path, content)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&path)?.permissions();
perms.set_mode(0o600);
fs::set_permissions(&path, perms)?;
}
Ok(())
}
pub fn get_default_account(&self) -> Option<&Account> {
self.accounts.iter().find(|a| a.default)
}
pub fn get_account(&self, name: &str) -> Option<&Account> {
self.accounts.iter().find(|a| a.name == name)
}
pub fn add_account(&mut self, account: Account) {
if account.default {
for acc in &mut self.accounts {
acc.default = false;
}
}
self.accounts.push(account);
}
pub fn remove_account(&mut self, name: &str) -> bool {
if let Some(pos) = self.accounts.iter().position(|a| a.name == name) {
self.accounts.remove(pos);
true
} else {
false
}
}
pub fn set_default_account(&mut self, name: &str) -> bool {
let mut found = false;
for acc in &mut self.accounts {
if acc.name == name {
acc.default = true;
found = true;
} else {
acc.default = false;
}
}
found
}
}