refactor(backend): abstract database logic into generic Store

This commit introduces a generic `Store` struct to handle all common database operations, such as connection management and schema initialization. All existing manager structs (`QuicklinkManager`, `SnippetManager`, `FrecencyManager`, `ClipboardHistoryManager`, `AiUsageManager`) have been refactored to use this new abstraction.
This commit is contained in:
ByteAtATime 2025-06-29 15:48:03 -07:00
parent 23c222a956
commit 7156827585
No known key found for this signature in database
7 changed files with 186 additions and 223 deletions

View file

@ -1,70 +1,64 @@
use crate::error::AppError;
use crate::snippets::types::Snippet;
use crate::store::Store;
use chrono::{DateTime, Utc};
use rusqlite::{params, Connection, Result as RusqliteResult};
use std::sync::{Arc, Mutex};
use tauri::{AppHandle, Manager};
use rusqlite::params;
use std::sync::Arc;
use tauri::AppHandle;
const SNIPPETS_SCHEMA: &str = "CREATE TABLE IF NOT EXISTS snippets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
keyword TEXT NOT NULL UNIQUE,
content TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)";
#[derive(Clone)]
pub struct SnippetManager {
db: Arc<Mutex<Connection>>,
store: Arc<Store>,
}
impl SnippetManager {
pub fn new(app_handle: AppHandle) -> Result<Self, AppError> {
let data_dir = app_handle
.path()
.app_local_data_dir()
.map_err(|_| AppError::DirectoryNotFound)?;
let db_path = data_dir.join("snippets.sqlite");
let db = Connection::open(db_path)?;
pub fn new(app_handle: &AppHandle) -> Result<Self, AppError> {
let store = Store::new(app_handle, "snippets.sqlite")?;
store.init_table(SNIPPETS_SCHEMA)?;
// Handle simple schema migrations in a block to drop the lock
{
let db = store.conn();
let mut stmt = db.prepare("PRAGMA table_info(snippets)")?;
let columns: Vec<String> = stmt
.query_map([], |row| row.get(1))?
.collect::<Result<Vec<_>, _>>()?;
if !columns.contains(&"times_used".to_string()) {
db.execute(
"ALTER TABLE snippets ADD COLUMN times_used INTEGER NOT NULL DEFAULT 0",
[],
)?;
}
if !columns.contains(&"last_used_at".to_string()) {
db.execute(
"ALTER TABLE snippets ADD COLUMN last_used_at INTEGER NOT NULL DEFAULT 0",
[],
)?;
}
}
Ok(Self {
db: Arc::new(Mutex::new(db)),
store: Arc::new(store),
})
}
pub fn init_db(&self) -> RusqliteResult<()> {
let db = self.db.lock().unwrap();
db.execute(
"CREATE TABLE IF NOT EXISTS snippets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
keyword TEXT NOT NULL UNIQUE,
content TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)",
[],
)?;
let mut stmt = db.prepare("PRAGMA table_info(snippets)")?;
let columns: Vec<String> = stmt
.query_map([], |row| row.get(1))?
.collect::<Result<Vec<_>, _>>()?;
if !columns.contains(&"times_used".to_string()) {
db.execute(
"ALTER TABLE snippets ADD COLUMN times_used INTEGER NOT NULL DEFAULT 0",
[],
)?;
}
if !columns.contains(&"last_used_at".to_string()) {
db.execute(
"ALTER TABLE snippets ADD COLUMN last_used_at INTEGER NOT NULL DEFAULT 0",
[],
)?;
}
Ok(())
}
pub fn create_snippet(
&self,
name: String,
keyword: String,
content: String,
) -> Result<i64, AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let now = Utc::now().timestamp();
db.execute(
"INSERT INTO snippets (name, keyword, content, created_at, updated_at, times_used, last_used_at)
@ -75,7 +69,7 @@ impl SnippetManager {
}
pub fn list_snippets(&self, search_term: Option<String>) -> Result<Vec<Snippet>, AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let mut query = "SELECT id, name, keyword, content, created_at, updated_at, times_used, last_used_at FROM snippets".to_string();
let mut params_vec: Vec<Box<dyn rusqlite::ToSql>> = vec![];
@ -119,7 +113,7 @@ impl SnippetManager {
keyword: String,
content: String,
) -> Result<(), AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let now = Utc::now().timestamp();
db.execute(
"UPDATE snippets SET name = ?1, keyword = ?2, content = ?3, updated_at = ?4 WHERE id = ?5",
@ -129,13 +123,13 @@ impl SnippetManager {
}
pub fn delete_snippet(&self, id: i64) -> Result<(), AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
db.execute("DELETE FROM snippets WHERE id = ?1", params![id])?;
Ok(())
}
pub fn snippet_was_used(&self, id: i64) -> Result<(), AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let now = Utc::now().timestamp();
db.execute(
"UPDATE snippets SET times_used = times_used + 1, last_used_at = ?1 WHERE id = ?2",
@ -145,7 +139,7 @@ impl SnippetManager {
}
pub fn find_snippet_by_keyword(&self, keyword: &str) -> Result<Option<Snippet>, AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let mut stmt = db.prepare("SELECT id, name, keyword, content, created_at, updated_at, times_used, last_used_at FROM snippets WHERE keyword = ?1")?;
let mut rows = stmt.query_map(params![keyword], |row| {
let created_at_ts: i64 = row.get(4)?;
@ -171,7 +165,7 @@ impl SnippetManager {
}
pub fn find_snippet_by_name(&self, name: &str) -> Result<Option<Snippet>, AppError> {
let db = self.db.lock().unwrap();
let db = self.store.conn();
let mut stmt = db.prepare("SELECT id, name, keyword, content, created_at, updated_at, times_used, last_used_at FROM snippets WHERE name = ?1 ORDER BY updated_at DESC LIMIT 1")?;
let mut rows = stmt.query_map(params![name], |row| {
let created_at_ts: i64 = row.get(4)?;