Skip to main content

plexus_comms/activations/discord/
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 DiscordStorageConfig {
7    pub db_path: PathBuf,
8}
9
10impl Default for DiscordStorageConfig {
11    fn default() -> Self {
12        Self {
13            db_path: PathBuf::from("discord_accounts.db"),
14        }
15    }
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
19pub struct DiscordAccountConfig {
20    pub bot_token: String,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct DiscordAccount {
25    pub name: String,
26    pub bot_token: String,
27    pub created_at: i64,
28    pub updated_at: i64,
29}
30
31pub struct DiscordStorage {
32    pool: SqlitePool,
33}
34
35impl DiscordStorage {
36    pub async fn new(config: DiscordStorageConfig) -> Result<Self, String> {
37        let db_url = format!("sqlite:{}?mode=rwc", config.db_path.display());
38        let connect_options: SqliteConnectOptions = db_url
39            .parse()
40            .map_err(|e| format!("Failed to parse database URL: {}", e))?;
41        let connect_options = connect_options.disable_statement_logging();
42
43        let pool = SqlitePool::connect_with(connect_options)
44            .await
45            .map_err(|e| format!("Failed to connect to database: {}", e))?;
46
47        let storage = Self { pool };
48        storage.run_migrations().await?;
49
50        Ok(storage)
51    }
52
53    async fn run_migrations(&self) -> Result<(), String> {
54        sqlx::query(
55            r#"
56            CREATE TABLE IF NOT EXISTS discord_accounts (
57                name TEXT PRIMARY KEY,
58                bot_token TEXT NOT NULL,
59                created_at INTEGER NOT NULL,
60                updated_at INTEGER NOT NULL
61            );
62            "#,
63        )
64        .execute(&self.pool)
65        .await
66        .map_err(|e| format!("Failed to run migrations: {}", e))?;
67
68        Ok(())
69    }
70
71    pub async fn register_account(&self, account: DiscordAccount) -> Result<(), String> {
72        sqlx::query(
73            r#"
74            INSERT INTO discord_accounts (name, bot_token, created_at, updated_at)
75            VALUES (?, ?, ?, ?)
76            ON CONFLICT(name) DO UPDATE SET
77                bot_token = excluded.bot_token,
78                updated_at = excluded.updated_at
79            "#,
80        )
81        .bind(&account.name)
82        .bind(&account.bot_token)
83        .bind(account.created_at)
84        .bind(account.updated_at)
85        .execute(&self.pool)
86        .await
87        .map_err(|e| format!("Failed to register account: {}", e))?;
88
89        Ok(())
90    }
91
92    pub async fn get_account(&self, name: &str) -> Result<Option<DiscordAccount>, String> {
93        let row = sqlx::query(
94            r#"
95            SELECT name, bot_token, created_at, updated_at
96            FROM discord_accounts
97            WHERE name = ?
98            "#,
99        )
100        .bind(name)
101        .fetch_optional(&self.pool)
102        .await
103        .map_err(|e| format!("Failed to get account: {}", e))?;
104
105        match row {
106            Some(row) => Ok(Some(DiscordAccount {
107                name: row.get("name"),
108                bot_token: row.get("bot_token"),
109                created_at: row.get("created_at"),
110                updated_at: row.get("updated_at"),
111            })),
112            None => Ok(None),
113        }
114    }
115
116    pub async fn list_accounts(&self) -> Result<Vec<DiscordAccount>, String> {
117        let rows = sqlx::query(
118            r#"
119            SELECT name, bot_token, created_at, updated_at
120            FROM discord_accounts
121            ORDER BY name
122            "#,
123        )
124        .fetch_all(&self.pool)
125        .await
126        .map_err(|e| format!("Failed to list accounts: {}", e))?;
127
128        let mut accounts = Vec::new();
129        for row in rows {
130            accounts.push(DiscordAccount {
131                name: row.get("name"),
132                bot_token: row.get("bot_token"),
133                created_at: row.get("created_at"),
134                updated_at: row.get("updated_at"),
135            });
136        }
137
138        Ok(accounts)
139    }
140
141    pub async fn remove_account(&self, name: &str) -> Result<bool, String> {
142        let result = sqlx::query(
143            r#"
144            DELETE FROM discord_accounts WHERE name = ?
145            "#,
146        )
147        .bind(name)
148        .execute(&self.pool)
149        .await
150        .map_err(|e| format!("Failed to remove account: {}", e))?;
151
152        Ok(result.rows_affected() > 0)
153    }
154}