use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite}; use std::path::Path; use std::time::Duration; use crate::error::Result; #[derive(Clone)] pub struct DatabasePool { pool: Pool, } impl DatabasePool { pub async fn new(db_path: &Path) -> Result { if let Some(parent) = db_path.parent() { tokio::fs::create_dir_all(parent).await?; } let db_url = format!("sqlite:{}?mode=rwc", db_path.display()); let pool = SqlitePoolOptions::new() .max_connections(4) .acquire_timeout(Duration::from_secs(5)) .idle_timeout(Duration::from_secs(300)) .connect(&db_url) .await?; Ok(Self { pool }) } pub async fn init_schema(&self) -> Result<()> { self.create_config_table().await?; self.create_users_table().await?; self.create_user_totp_credentials_table().await?; self.create_api_tokens_table().await?; self.create_wol_history_table().await?; Ok(()) } async fn create_config_table(&self) -> Result<()> { sqlx::query( r#" CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at TEXT NOT NULL DEFAULT (datetime('now')) ) "#, ) .execute(&self.pool) .await?; Ok(()) } async fn create_users_table(&self) -> Result<()> { sqlx::query( r#" CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ) "#, ) .execute(&self.pool) .await?; Ok(()) } async fn create_api_tokens_table(&self) -> Result<()> { sqlx::query( r#" CREATE TABLE IF NOT EXISTS api_tokens ( id TEXT PRIMARY KEY, name TEXT NOT NULL, token_hash TEXT NOT NULL, permissions TEXT NOT NULL, expires_at TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now')), last_used TEXT ) "#, ) .execute(&self.pool) .await?; Ok(()) } async fn create_user_totp_credentials_table(&self) -> Result<()> { sqlx::query( r#" CREATE TABLE IF NOT EXISTS user_totp_credentials ( user_id TEXT PRIMARY KEY, secret TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) "#, ) .execute(&self.pool) .await?; Ok(()) } async fn create_wol_history_table(&self) -> Result<()> { sqlx::query( r#" CREATE TABLE IF NOT EXISTS wol_history ( mac_address TEXT PRIMARY KEY, updated_at INTEGER NOT NULL ) "#, ) .execute(&self.pool) .await?; sqlx::query( r#" CREATE INDEX IF NOT EXISTS idx_wol_history_updated_at ON wol_history(updated_at DESC) "#, ) .execute(&self.pool) .await?; Ok(()) } pub fn pool(&self) -> &Pool { &self.pool } pub fn clone_pool(&self) -> Pool { self.pool.clone() } }