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}