plexus_comms/activations/discord/
storage.rs1use 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}