use crate::config::DatabaseConfig;
use crate::utils::error::{GatewayError, Result};
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use std::time::Duration;
use tracing::{debug, info, warn};
#[derive(Debug, Clone)]
pub struct SqliteDatabase {
pool: SqlitePool,
}
impl SqliteDatabase {
pub async fn new(config: &DatabaseConfig) -> Result<Self> {
info!("Creating SQLite database connection pool");
debug!("Database URL: {}", Self::sanitize_url(&config.url));
if let Some(path) = config.url.strip_prefix("sqlite:") {
if let Some(parent) = std::path::Path::new(path).parent() {
std::fs::create_dir_all(parent)
.map_err(|e| GatewayError::Config(format!("Failed to create data directory: {}", e)))?;
}
}
let pool = SqlitePoolOptions::new()
.max_connections(config.max_connections)
.acquire_timeout(Duration::from_secs(config.connection_timeout))
.idle_timeout(Some(Duration::from_secs(600))) .max_lifetime(Some(Duration::from_secs(1800))) .connect(&config.url)
.await
.map_err(|e| {
warn!("Failed to connect to SQLite database: {}", e);
warn!("Database URL: {}", Self::sanitize_url(&config.url));
GatewayError::Database(e)
})?;
info!("SQLite database connection pool created successfully");
Ok(Self { pool })
}
fn sanitize_url(url: &str) -> String {
if url.starts_with("sqlite:") {
url.to_string() } else {
"***sanitized***".to_string()
}
}
pub fn pool(&self) -> &SqlitePool {
&self.pool
}
pub async fn execute(&self, query: &str) -> Result<u64> {
let result = sqlx::query(query)
.execute(&self.pool)
.await
.map_err(GatewayError::Database)?;
Ok(result.rows_affected())
}
pub async fn migrate(&self) -> Result<()> {
info!("Running SQLite database migrations");
self.execute(r#"
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
display_name TEXT,
password_hash TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'user',
status TEXT NOT NULL DEFAULT 'pending',
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
two_factor_enabled BOOLEAN NOT NULL DEFAULT FALSE,
last_login_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"#).await?;
self.execute(r#"
CREATE TABLE IF NOT EXISTS api_keys (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
key_hash TEXT UNIQUE NOT NULL,
permissions TEXT,
rate_limit_rpm INTEGER,
rate_limit_tpm INTEGER,
expires_at DATETIME,
last_used_at DATETIME,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
"#).await?;
self.execute(r#"
CREATE TABLE IF NOT EXISTS batches (
id TEXT PRIMARY KEY,
object TEXT NOT NULL DEFAULT 'batch',
endpoint TEXT NOT NULL,
input_file_id TEXT,
completion_window TEXT NOT NULL,
status TEXT NOT NULL,
output_file_id TEXT,
error_file_id TEXT,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
in_progress_at DATETIME,
expires_at DATETIME,
finalizing_at DATETIME,
completed_at DATETIME,
failed_at DATETIME,
expired_at DATETIME,
cancelling_at DATETIME,
cancelled_at DATETIME,
request_counts_total INTEGER NOT NULL DEFAULT 0,
request_counts_completed INTEGER NOT NULL DEFAULT 0,
request_counts_failed INTEGER NOT NULL DEFAULT 0,
metadata TEXT
)
"#).await?;
info!("SQLite database migrations completed successfully");
Ok(())
}
pub async fn health_check(&self) -> Result<()> {
debug!("Performing SQLite database health check");
sqlx::query("SELECT 1")
.execute(&self.pool)
.await
.map_err(GatewayError::Database)?;
debug!("SQLite database health check passed");
Ok(())
}
pub async fn close(&self) -> Result<()> {
info!("Closing SQLite database connection pool");
self.pool.close().await;
info!("SQLite database connection pool closed");
Ok(())
}
}