Skip to main content

mvsep_api_tester/db/
user_config.rs

1use anyhow::Result;
2use rusqlite::{params, Connection};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct UserConfigEntry {
7    pub key: String,
8    pub value: String,
9    pub value_type: String,
10    pub updated_at: String,
11}
12
13pub struct UserConfigDB {
14    conn: Connection,
15}
16
17impl UserConfigDB {
18    pub fn new(path: &str) -> Result<Self> {
19        let conn = Connection::open(path)?;
20        let db = Self { conn };
21        db.init()?;
22        Ok(db)
23    }
24
25    #[allow(clippy::should_implement_trait)]
26    pub fn default() -> Result<Self> {
27        let path = format!(
28            "{}/user_config.db",
29            std::env::var("HOME").unwrap_or_else(|_| ".".to_string())
30        );
31        Self::new(&path)
32    }
33
34    fn init(&self) -> Result<()> {
35        self.conn.execute_batch(
36            "
37            CREATE TABLE IF NOT EXISTS user_config (
38                key TEXT PRIMARY KEY NOT NULL,
39                value TEXT NOT NULL DEFAULT '',
40                value_type TEXT NOT NULL DEFAULT 'string',
41                updated_at TEXT DEFAULT (datetime('now')),
42                created_at TEXT DEFAULT (datetime('now'))
43            );
44
45            CREATE INDEX IF NOT EXISTS idx_user_config_key ON user_config(key);
46            CREATE INDEX IF NOT EXISTS idx_user_config_type ON user_config(value_type);
47
48            CREATE TABLE IF NOT EXISTS config_groups (
49                id INTEGER PRIMARY KEY AUTOINCREMENT,
50                name TEXT NOT NULL UNIQUE,
51                description TEXT,
52                created_at TEXT DEFAULT (datetime('now'))
53            );
54            ",
55        )?;
56        Ok(())
57    }
58
59    pub fn set_string(&self, key: &str, value: &str) -> Result<()> {
60        self.conn.execute(
61            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'string', datetime('now'))",
62            params![key, value],
63        )?;
64        Ok(())
65    }
66
67    pub fn get_string(&self, key: &str) -> Result<Option<String>> {
68        let mut stmt = self
69            .conn
70            .prepare("SELECT value FROM user_config WHERE key = ?1")?;
71        let mut rows = stmt.query_map(params![key], |row| row.get(0))?;
72        match rows.next() {
73            Some(result) => Ok(Some(result?)),
74            None => Ok(None),
75        }
76    }
77
78    pub fn set_bool(&self, key: &str, value: bool) -> Result<()> {
79        self.conn.execute(
80            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'bool', datetime('now'))",
81            params![key, if value { "true" } else { "false" }],
82        )?;
83        Ok(())
84    }
85
86    pub fn get_bool(&self, key: &str) -> Result<Option<bool>> {
87        match self.get_string(key)? {
88            Some(value) => Ok(Some(value == "true")),
89            None => Ok(None),
90        }
91    }
92
93    pub fn set_int(&self, key: &str, value: i64) -> Result<()> {
94        self.conn.execute(
95            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'int', datetime('now'))",
96            params![key, value.to_string()],
97        )?;
98        Ok(())
99    }
100
101    pub fn get_int(&self, key: &str) -> Result<Option<i64>> {
102        match self.get_string(key)? {
103            Some(value) => match value.parse::<i64>() {
104                Ok(v) => Ok(Some(v)),
105                Err(_) => Ok(None),
106            },
107            None => Ok(None),
108        }
109    }
110
111    pub fn set_float(&self, key: &str, value: f64) -> Result<()> {
112        self.conn.execute(
113            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'float', datetime('now'))",
114            params![key, value.to_string()],
115        )?;
116        Ok(())
117    }
118
119    pub fn get_float(&self, key: &str) -> Result<Option<f64>> {
120        match self.get_string(key)? {
121            Some(value) => match value.parse::<f64>() {
122                Ok(v) => Ok(Some(v)),
123                Err(_) => Ok(None),
124            },
125            None => Ok(None),
126        }
127    }
128
129    pub fn set_json<T: Serialize>(&self, key: &str, value: &T) -> Result<()> {
130        let json_str = serde_json::to_string(value)?;
131        self.conn.execute(
132            "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, 'json', datetime('now'))",
133            params![key, json_str],
134        )?;
135        Ok(())
136    }
137
138    pub fn get_json<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Result<Option<T>> {
139        match self.get_string(key)? {
140            Some(value) => match serde_json::from_str(&value) {
141                Ok(v) => Ok(Some(v)),
142                Err(_) => Ok(None),
143            },
144            None => Ok(None),
145        }
146    }
147
148    pub fn delete(&self, key: &str) -> Result<bool> {
149        let rows_affected = self
150            .conn
151            .execute("DELETE FROM user_config WHERE key = ?1", params![key])?;
152        Ok(rows_affected > 0)
153    }
154
155    pub fn exists(&self, key: &str) -> Result<bool> {
156        let mut stmt = self
157            .conn
158            .prepare("SELECT COUNT(*) FROM user_config WHERE key = ?1")?;
159        let count: i64 = stmt.query_row(params![key], |row| row.get(0))?;
160        Ok(count > 0)
161    }
162
163    pub fn get_all_by_prefix(&self, prefix: &str) -> Result<Vec<UserConfigEntry>> {
164        let mut stmt = self.conn.prepare(
165            "SELECT key, value, value_type, updated_at FROM user_config WHERE key LIKE ?1 || '%' ORDER BY key"
166        )?;
167        let rows = stmt.query_map(params![prefix], |row| {
168            Ok(UserConfigEntry {
169                key: row.get(0)?,
170                value: row.get(1)?,
171                value_type: row.get(2)?,
172                updated_at: row.get(3)?,
173            })
174        })?;
175        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
176    }
177
178    pub fn get_all_by_type(&self, value_type: &str) -> Result<Vec<UserConfigEntry>> {
179        let mut stmt = self.conn.prepare(
180            "SELECT key, value, value_type, updated_at FROM user_config WHERE value_type = ?1 ORDER BY key"
181        )?;
182        let rows = stmt.query_map(params![value_type], |row| {
183            Ok(UserConfigEntry {
184                key: row.get(0)?,
185                value: row.get(1)?,
186                value_type: row.get(2)?,
187                updated_at: row.get(3)?,
188            })
189        })?;
190        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
191    }
192
193    pub fn get_all(&self) -> Result<Vec<UserConfigEntry>> {
194        let mut stmt = self
195            .conn
196            .prepare("SELECT key, value, value_type, updated_at FROM user_config ORDER BY key")?;
197        let rows = stmt.query_map([], |row| {
198            Ok(UserConfigEntry {
199                key: row.get(0)?,
200                value: row.get(1)?,
201                value_type: row.get(2)?,
202                updated_at: row.get(3)?,
203            })
204        })?;
205        rows.collect::<Result<Vec<_>, _>>().map_err(|e| e.into())
206    }
207
208    pub fn count(&self) -> Result<i64> {
209        let mut stmt = self.conn.prepare("SELECT COUNT(*) FROM user_config")?;
210        let count: i64 = stmt.query_row([], |row| row.get(0))?;
211        Ok(count)
212    }
213
214    pub fn clear_all(&self) -> Result<u64> {
215        let affected = self.conn.execute("DELETE FROM user_config", [])?;
216        Ok(affected as u64)
217    }
218
219    pub fn export_to_json(&self) -> Result<String> {
220        let entries = self.get_all()?;
221        let json = serde_json::to_string_pretty(&entries)?;
222        Ok(json)
223    }
224
225    pub fn import_from_json(&self, json_str: &str) -> Result<u64> {
226        let entries: Vec<UserConfigEntry> = serde_json::from_str(json_str)?;
227        let mut count = 0u64;
228
229        for entry in entries {
230            self.conn.execute(
231                "INSERT OR REPLACE INTO user_config (key, value, value_type, updated_at) VALUES (?1, ?2, ?3, ?4)",
232                params![entry.key, entry.value, entry.value_type, entry.updated_at],
233            )?;
234            count += 1;
235        }
236
237        Ok(count)
238    }
239}