plexus_comms/activations/email/
storage.rs1use serde::{Deserialize, Serialize};
2use sqlx::{sqlite::SqlitePool, sqlite::SqliteConnectOptions, ConnectOptions, Row};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone)]
6pub struct EmailStorageConfig {
7 pub db_path: PathBuf,
8}
9
10impl Default for EmailStorageConfig {
11 fn default() -> Self {
12 Self {
13 db_path: PathBuf::from("email_accounts.db"),
14 }
15 }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct EmailAccount {
20 pub name: String,
21 pub smtp: Option<SmtpAccountConfig>,
22 pub imap: Option<ImapAccountConfig>,
23 pub created_at: i64,
24 pub updated_at: i64,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
28pub struct SmtpAccountConfig {
29 pub host: String,
30 pub port: u16,
31 pub username: String,
32 pub password: String,
33 pub from_email: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
37pub struct ImapAccountConfig {
38 pub host: String,
39 pub port: u16,
40 pub username: String,
41 pub password: String,
42}
43
44pub struct EmailStorage {
45 pool: SqlitePool,
46}
47
48impl EmailStorage {
49 pub async fn new(config: EmailStorageConfig) -> Result<Self, String> {
50 let db_url = format!("sqlite:{}?mode=rwc", config.db_path.display());
51 let connect_options: SqliteConnectOptions = db_url
52 .parse()
53 .map_err(|e| format!("Failed to parse database URL: {}", e))?;
54 let connect_options = connect_options.disable_statement_logging();
55
56 let pool = SqlitePool::connect_with(connect_options)
57 .await
58 .map_err(|e| format!("Failed to connect to database: {}", e))?;
59
60 let storage = Self { pool };
61 storage.run_migrations().await?;
62
63 Ok(storage)
64 }
65
66 async fn run_migrations(&self) -> Result<(), String> {
67 sqlx::query(
68 r#"
69 CREATE TABLE IF NOT EXISTS email_accounts (
70 name TEXT PRIMARY KEY,
71 smtp_config TEXT,
72 imap_config TEXT,
73 created_at INTEGER NOT NULL,
74 updated_at INTEGER NOT NULL
75 );
76 "#,
77 )
78 .execute(&self.pool)
79 .await
80 .map_err(|e| format!("Failed to run migrations: {}", e))?;
81
82 Ok(())
83 }
84
85 pub async fn register_account(&self, account: EmailAccount) -> Result<(), String> {
86 let smtp_json = account
87 .smtp
88 .as_ref()
89 .map(|s| serde_json::to_string(s).unwrap());
90 let imap_json = account
91 .imap
92 .as_ref()
93 .map(|i| serde_json::to_string(i).unwrap());
94
95 sqlx::query(
96 r#"
97 INSERT INTO email_accounts (name, smtp_config, imap_config, created_at, updated_at)
98 VALUES (?, ?, ?, ?, ?)
99 ON CONFLICT(name) DO UPDATE SET
100 smtp_config = excluded.smtp_config,
101 imap_config = excluded.imap_config,
102 updated_at = excluded.updated_at
103 "#,
104 )
105 .bind(&account.name)
106 .bind(smtp_json)
107 .bind(imap_json)
108 .bind(account.created_at)
109 .bind(account.updated_at)
110 .execute(&self.pool)
111 .await
112 .map_err(|e| format!("Failed to register account: {}", e))?;
113
114 Ok(())
115 }
116
117 pub async fn get_account(&self, name: &str) -> Result<Option<EmailAccount>, String> {
118 let row = sqlx::query(
119 r#"
120 SELECT name, smtp_config, imap_config, created_at, updated_at
121 FROM email_accounts
122 WHERE name = ?
123 "#,
124 )
125 .bind(name)
126 .fetch_optional(&self.pool)
127 .await
128 .map_err(|e| format!("Failed to get account: {}", e))?;
129
130 match row {
131 Some(row) => {
132 let smtp_json: Option<String> = row.get("smtp_config");
133 let imap_json: Option<String> = row.get("imap_config");
134
135 let smtp = smtp_json
136 .and_then(|s| serde_json::from_str(&s).ok());
137 let imap = imap_json
138 .and_then(|i| serde_json::from_str(&i).ok());
139
140 Ok(Some(EmailAccount {
141 name: row.get("name"),
142 smtp,
143 imap,
144 created_at: row.get("created_at"),
145 updated_at: row.get("updated_at"),
146 }))
147 }
148 None => Ok(None),
149 }
150 }
151
152 pub async fn list_accounts(&self) -> Result<Vec<EmailAccount>, String> {
153 let rows = sqlx::query(
154 r#"
155 SELECT name, smtp_config, imap_config, created_at, updated_at
156 FROM email_accounts
157 ORDER BY name
158 "#,
159 )
160 .fetch_all(&self.pool)
161 .await
162 .map_err(|e| format!("Failed to list accounts: {}", e))?;
163
164 let mut accounts = Vec::new();
165 for row in rows {
166 let smtp_json: Option<String> = row.get("smtp_config");
167 let imap_json: Option<String> = row.get("imap_config");
168
169 let smtp = smtp_json.and_then(|s| serde_json::from_str(&s).ok());
170 let imap = imap_json.and_then(|i| serde_json::from_str(&i).ok());
171
172 accounts.push(EmailAccount {
173 name: row.get("name"),
174 smtp,
175 imap,
176 created_at: row.get("created_at"),
177 updated_at: row.get("updated_at"),
178 });
179 }
180
181 Ok(accounts)
182 }
183
184 pub async fn remove_account(&self, name: &str) -> Result<bool, String> {
185 let result = sqlx::query(
186 r#"
187 DELETE FROM email_accounts WHERE name = ?
188 "#,
189 )
190 .bind(name)
191 .execute(&self.pool)
192 .await
193 .map_err(|e| format!("Failed to remove account: {}", e))?;
194
195 Ok(result.rows_affected() > 0)
196 }
197}