linkrs_server/web/storage/
sqlite.rs1use chrono::Utc;
4use sqlx::{sqlite::SqlitePoolOptions, Pool, Row, Sqlite};
5
6use crate::web::error::{WebError, WebResult};
7use crate::web::models::metadata::{FavoriteItem, HistoryItem};
8
9use super::MetadataStorage;
10
11pub struct SqliteStorage {
13 pool: Pool<Sqlite>,
14}
15
16impl SqliteStorage {
17 pub async fn new(database_path: &str) -> WebResult<Self> {
19 let pool = SqlitePoolOptions::new()
20 .max_connections(5)
21 .connect(database_path)
22 .await
23 .map_err(|e| WebError::Storage(format!("Failed to connect to SQLite: {}", e)))?;
24
25 let storage = Self { pool };
26 storage.init_tables().await?;
27
28 Ok(storage)
29 }
30
31 async fn init_tables(&self) -> WebResult<()> {
33 sqlx::query(
34 r#"
35 CREATE TABLE IF NOT EXISTS query_history (
36 id TEXT PRIMARY KEY,
37 session_id TEXT NOT NULL,
38 query TEXT NOT NULL,
39 executed_at TIMESTAMP NOT NULL,
40 execution_time_ms INTEGER NOT NULL,
41 rows_returned INTEGER NOT NULL,
42 success BOOLEAN NOT NULL,
43 error_message TEXT,
44 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
45 )
46 "#,
47 )
48 .execute(&self.pool)
49 .await
50 .map_err(|e| WebError::Storage(format!("Failed to create history table: {}", e)))?;
51
52 sqlx::query(
53 r#"
54 CREATE TABLE IF NOT EXISTS query_favorites (
55 id TEXT PRIMARY KEY,
56 session_id TEXT NOT NULL,
57 name TEXT NOT NULL,
58 query TEXT NOT NULL,
59 description TEXT,
60 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
61 updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
62 )
63 "#,
64 )
65 .execute(&self.pool)
66 .await
67 .map_err(|e| WebError::Storage(format!("Failed to create favorites table: {}", e)))?;
68
69 sqlx::query(
71 "CREATE INDEX IF NOT EXISTS idx_history_session ON query_history(session_id, executed_at DESC)"
72 )
73 .execute(&self.pool)
74 .await
75 .map_err(|e| WebError::Storage(format!("Failed to create history index: {}", e)))?;
76
77 sqlx::query(
78 "CREATE INDEX IF NOT EXISTS idx_favorites_session ON query_favorites(session_id, created_at DESC)"
79 )
80 .execute(&self.pool)
81 .await
82 .map_err(|e| WebError::Storage(format!("Failed to create favorites index: {}", e)))?;
83
84 Ok(())
85 }
86}
87
88#[async_trait::async_trait]
89impl MetadataStorage for SqliteStorage {
90 async fn add_history(&self, item: &HistoryItem) -> WebResult<()> {
91 sqlx::query(
92 r#"
93 INSERT INTO query_history (id, session_id, query, executed_at, execution_time_ms, rows_returned, success, error_message)
94 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
95 "#,
96 )
97 .bind(&item.id)
98 .bind(&item.session_id)
99 .bind(&item.query)
100 .bind(item.executed_at)
101 .bind(item.execution_time_ms)
102 .bind(item.rows_returned)
103 .bind(item.success)
104 .bind(&item.error_message)
105 .execute(&self.pool)
106 .await
107 .map_err(|e| WebError::Storage(format!("Failed to add history: {}", e)))?;
108
109 Ok(())
110 }
111
112 async fn get_history(
113 &self,
114 session_id: &str,
115 limit: usize,
116 offset: usize,
117 ) -> WebResult<(Vec<HistoryItem>, i64)> {
118 let items: Vec<HistoryItem> = sqlx::query_as(
119 r#"
120 SELECT id, session_id, query, executed_at, execution_time_ms, rows_returned, success, error_message
121 FROM query_history
122 WHERE session_id = ?1
123 ORDER BY executed_at DESC
124 LIMIT ?2 OFFSET ?3
125 "#,
126 )
127 .bind(session_id)
128 .bind(limit as i64)
129 .bind(offset as i64)
130 .fetch_all(&self.pool)
131 .await
132 .map_err(|e| WebError::Storage(format!("Failed to get history: {}", e)))?;
133
134 let total: i64 =
135 sqlx::query_scalar("SELECT COUNT(*) FROM query_history WHERE session_id = ?1")
136 .bind(session_id)
137 .fetch_one(&self.pool)
138 .await
139 .map_err(|e| WebError::Storage(format!("Failed to count history: {}", e)))?;
140
141 Ok((items, total))
142 }
143
144 async fn delete_history(&self, id: &str, session_id: &str) -> WebResult<()> {
145 let result = sqlx::query("DELETE FROM query_history WHERE id = ?1 AND session_id = ?2")
146 .bind(id)
147 .bind(session_id)
148 .execute(&self.pool)
149 .await
150 .map_err(|e| WebError::Storage(format!("Failed to delete history: {}", e)))?;
151
152 if result.rows_affected() == 0 {
153 return Err(WebError::NotFound("History item not found".to_string()));
154 }
155
156 Ok(())
157 }
158
159 async fn clear_history(&self, session_id: &str) -> WebResult<()> {
160 sqlx::query("DELETE FROM query_history WHERE session_id = ?1")
161 .bind(session_id)
162 .execute(&self.pool)
163 .await
164 .map_err(|e| WebError::Storage(format!("Failed to clear history: {}", e)))?;
165
166 Ok(())
167 }
168
169 async fn add_favorite(&self, item: &FavoriteItem) -> WebResult<()> {
170 sqlx::query(
171 r#"
172 INSERT INTO query_favorites (id, session_id, name, query, description, created_at, updated_at)
173 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
174 "#,
175 )
176 .bind(&item.id)
177 .bind(&item.session_id)
178 .bind(&item.name)
179 .bind(&item.query)
180 .bind(&item.description)
181 .bind(item.created_at)
182 .bind(item.created_at)
183 .execute(&self.pool)
184 .await
185 .map_err(|e| WebError::Storage(format!("Failed to add favorite: {}", e)))?;
186
187 Ok(())
188 }
189
190 async fn get_favorites(&self, session_id: &str) -> WebResult<Vec<FavoriteItem>> {
191 let items: Vec<FavoriteItem> = sqlx::query_as(
192 r#"
193 SELECT id, session_id, name, query, description, created_at
194 FROM query_favorites
195 WHERE session_id = ?1
196 ORDER BY created_at DESC
197 "#,
198 )
199 .bind(session_id)
200 .fetch_all(&self.pool)
201 .await
202 .map_err(|e| WebError::Storage(format!("Failed to get favorites: {}", e)))?;
203
204 Ok(items)
205 }
206
207 async fn get_favorite(&self, id: &str, session_id: &str) -> WebResult<FavoriteItem> {
208 let item: FavoriteItem = sqlx::query_as(
209 r#"
210 SELECT id, session_id, name, query, description, created_at
211 FROM query_favorites
212 WHERE id = ?1 AND session_id = ?2
213 "#,
214 )
215 .bind(id)
216 .bind(session_id)
217 .fetch_one(&self.pool)
218 .await
219 .map_err(|e| match e {
220 sqlx::Error::RowNotFound => WebError::NotFound("Favorite not found".to_string()),
221 _ => WebError::Storage(format!("Failed to get favorite: {}", e)),
222 })?;
223
224 Ok(item)
225 }
226
227 async fn update_favorite(
228 &self,
229 id: &str,
230 session_id: &str,
231 item: &FavoriteItem,
232 ) -> WebResult<()> {
233 let result = sqlx::query(
234 r#"
235 UPDATE query_favorites
236 SET name = ?1, query = ?2, description = ?3, updated_at = ?4
237 WHERE id = ?5 AND session_id = ?6
238 "#,
239 )
240 .bind(&item.name)
241 .bind(&item.query)
242 .bind(&item.description)
243 .bind(Utc::now())
244 .bind(id)
245 .bind(session_id)
246 .execute(&self.pool)
247 .await
248 .map_err(|e| WebError::Storage(format!("Failed to update favorite: {}", e)))?;
249
250 if result.rows_affected() == 0 {
251 return Err(WebError::NotFound("Favorite not found".to_string()));
252 }
253
254 Ok(())
255 }
256
257 async fn delete_favorite(&self, id: &str, session_id: &str) -> WebResult<()> {
258 let result = sqlx::query("DELETE FROM query_favorites WHERE id = ?1 AND session_id = ?2")
259 .bind(id)
260 .bind(session_id)
261 .execute(&self.pool)
262 .await
263 .map_err(|e| WebError::Storage(format!("Failed to delete favorite: {}", e)))?;
264
265 if result.rows_affected() == 0 {
266 return Err(WebError::NotFound("Favorite not found".to_string()));
267 }
268
269 Ok(())
270 }
271
272 async fn delete_all_favorites(&self, session_id: &str) -> WebResult<()> {
273 sqlx::query("DELETE FROM query_favorites WHERE session_id = ?1")
274 .bind(session_id)
275 .execute(&self.pool)
276 .await
277 .map_err(|e| WebError::Storage(format!("Failed to delete all favorites: {}", e)))?;
278
279 Ok(())
280 }
281}
282
283impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for HistoryItem {
285 fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
286 Ok(HistoryItem {
287 id: row.try_get("id")?,
288 session_id: row.try_get("session_id")?,
289 query: row.try_get("query")?,
290 executed_at: row.try_get("executed_at")?,
291 execution_time_ms: row.try_get("execution_time_ms")?,
292 rows_returned: row.try_get("rows_returned")?,
293 success: row.try_get("success")?,
294 error_message: row.try_get("error_message")?,
295 })
296 }
297}
298
299impl sqlx::FromRow<'_, sqlx::sqlite::SqliteRow> for FavoriteItem {
301 fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
302 Ok(FavoriteItem {
303 id: row.try_get("id")?,
304 session_id: row.try_get("session_id")?,
305 name: row.try_get("name")?,
306 query: row.try_get("query")?,
307 description: row.try_get("description")?,
308 created_at: row.try_get("created_at")?,
309 })
310 }
311}