Skip to main content

oauth_db_cli/
config.rs

1use crate::error::{CliError, Result};
2use directories::ProjectDirs;
3use serde::{Deserialize, Serialize};
4use std::fs;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Config {
9    pub server: ServerConfig,
10    pub output: OutputConfig,
11    pub log: LogConfig,
12    #[serde(default)]
13    pub accounts: Vec<Account>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ServerConfig {
18    pub url: String,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct OutputConfig {
23    #[serde(default = "default_format")]
24    pub format: String,
25    #[serde(default = "default_color")]
26    pub color: bool,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct LogConfig {
31    #[serde(default = "default_log_level")]
32    pub level: String,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Account {
37    pub name: String,
38    pub server: String,
39    pub username: String,
40    pub token: String,
41    #[serde(default)]
42    pub default: bool,
43    pub role: String,
44}
45
46fn default_format() -> String {
47    "table".to_string()
48}
49
50fn default_color() -> bool {
51    true
52}
53
54fn default_log_level() -> String {
55    "info".to_string()
56}
57
58impl Default for Config {
59    fn default() -> Self {
60        Self {
61            server: ServerConfig {
62                url: "http://localhost:38080".to_string(),
63            },
64            output: OutputConfig {
65                format: default_format(),
66                color: default_color(),
67            },
68            log: LogConfig {
69                level: default_log_level(),
70            },
71            accounts: Vec::new(),
72        }
73    }
74}
75
76impl Config {
77    pub fn config_dir() -> Result<PathBuf> {
78        ProjectDirs::from("com", "oauth-db", "oauth-db-cli")
79            .map(|dirs| dirs.config_dir().to_path_buf())
80            .ok_or_else(|| CliError::ConfigError("Failed to determine config directory".to_string()))
81    }
82
83    pub fn config_path() -> Result<PathBuf> {
84        Ok(Self::config_dir()?.join("config.toml"))
85    }
86
87    pub fn load() -> Result<Self> {
88        let path = Self::config_path()?;
89        if !path.exists() {
90            return Ok(Self::default());
91        }
92
93        let content = fs::read_to_string(&path)?;
94        let config: Config = toml::from_str(&content)?;
95        Ok(config)
96    }
97
98    pub fn save(&self) -> Result<()> {
99        let dir = Self::config_dir()?;
100        fs::create_dir_all(&dir)?;
101
102        let path = Self::config_path()?;
103        let content = toml::to_string_pretty(self)
104            .map_err(|e| CliError::ConfigError(format!("Failed to serialize config: {}", e)))?;
105
106        fs::write(&path, content)?;
107
108        #[cfg(unix)]
109        {
110            use std::os::unix::fs::PermissionsExt;
111            let mut perms = fs::metadata(&path)?.permissions();
112            perms.set_mode(0o600);
113            fs::set_permissions(&path, perms)?;
114        }
115
116        Ok(())
117    }
118
119    pub fn get_default_account(&self) -> Option<&Account> {
120        self.accounts.iter().find(|a| a.default)
121    }
122
123    pub fn get_account(&self, name: &str) -> Option<&Account> {
124        self.accounts.iter().find(|a| a.name == name)
125    }
126
127    pub fn add_account(&mut self, account: Account) {
128        if account.default {
129            for acc in &mut self.accounts {
130                acc.default = false;
131            }
132        }
133        self.accounts.push(account);
134    }
135
136    pub fn remove_account(&mut self, name: &str) -> bool {
137        if let Some(pos) = self.accounts.iter().position(|a| a.name == name) {
138            self.accounts.remove(pos);
139            true
140        } else {
141            false
142        }
143    }
144
145    pub fn set_default_account(&mut self, name: &str) -> bool {
146        let mut found = false;
147        for acc in &mut self.accounts {
148            if acc.name == name {
149                acc.default = true;
150                found = true;
151            } else {
152                acc.default = false;
153            }
154        }
155        found
156    }
157}