Skip to main content

plexus_comms/activations/email/
storage.rs

1use 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}