//! Database-agnostic authentication repository implementation //! //! This module provides a unified interface for user authentication operations //! that works with both PostgreSQL and SQLite databases. use crate::database::DatabaseType; use crate::database::connection::{DatabaseConnection, DatabaseParam}; use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Database-specific user representation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DatabaseUser { pub id: Uuid, pub email: String, pub username: Option, pub display_name: Option, pub password_hash: String, pub avatar_url: Option, pub roles: Vec, pub is_active: bool, pub is_verified: bool, pub email_verified: bool, pub created_at: DateTime, pub updated_at: DateTime, pub last_login: Option>, pub two_factor_enabled: bool, pub two_factor_secret: Option, pub backup_codes: Vec, } /// Request struct for creating a new user #[derive(Debug, Clone)] pub struct CreateUserRequest { pub email: String, pub password_hash: String, pub display_name: Option, pub username: Option, pub is_verified: bool, pub is_active: bool, } /// Request struct for OAuth user creation #[derive(Debug, Clone)] #[allow(dead_code)] pub struct OAuthUserRequest { pub email: String, pub display_name: Option, pub username: Option, pub provider: String, pub provider_id: String, pub provider_data: serde_json::Value, } /// Request struct for creating a session #[derive(Debug, Clone)] pub struct CreateSessionRequest { pub user_id: Uuid, pub token: String, pub expires_at: DateTime, pub user_agent: Option, pub ip_address: Option, } /// User session representation #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UserSession { pub id: Uuid, pub user_id: Uuid, pub token: String, pub expires_at: DateTime, pub created_at: DateTime, pub last_used_at: Option>, pub user_agent: Option, pub ip_address: Option, pub is_active: bool, } /// Database-agnostic authentication repository trait #[async_trait] #[allow(dead_code)] pub trait AuthRepositoryTrait: Send + Sync + Clone + 'static { /// Initialize database tables async fn init_tables(&self) -> Result<()>; /// Create a new user async fn create_user(&self, user: &CreateUserRequest) -> Result; /// Find user by email async fn find_user_by_email(&self, email: &str) -> Result>; /// Find user by ID async fn find_user_by_id(&self, id: &Uuid) -> Result>; /// Update user information async fn update_user(&self, user: &DatabaseUser) -> Result<()>; /// Delete user async fn delete_user(&self, id: &Uuid) -> Result<()>; /// Verify user password async fn verify_password(&self, email: &str, password_hash: &str) -> Result; /// Update user password async fn update_password(&self, id: &Uuid, password_hash: &str) -> Result<()>; /// Get user roles async fn get_user_roles(&self, id: &Uuid) -> Result>; /// Add role to user async fn add_user_role(&self, id: &Uuid, role: &str) -> Result<()>; /// Remove role from user async fn remove_user_role(&self, id: &Uuid, role: &str) -> Result<()>; /// Create OAuth user async fn create_oauth_user(&self, user: &OAuthUserRequest) -> Result; /// Find user by OAuth provider async fn find_user_by_oauth( &self, provider: &str, provider_id: &str, ) -> Result>; /// Get user profile async fn get_user_profile(&self, id: &Uuid) -> Result>; /// Update user profile async fn update_user_profile(&self, user: &DatabaseUser) -> Result<()>; /// Get user sessions async fn get_user_sessions(&self, user_id: &Uuid) -> Result>; /// Create session async fn create_session(&self, session: &CreateSessionRequest) -> Result; /// Get session by token async fn get_session_by_token(&self, token: &str) -> Result>; /// Update session async fn update_session(&self, session: &UserSession) -> Result<()>; /// Delete session async fn delete_session(&self, token: &str) -> Result<()>; /// Cleanup expired sessions async fn cleanup_expired_sessions(&self) -> Result; /// Check if email exists async fn email_exists(&self, email: &str) -> Result; /// Check if username exists async fn username_exists(&self, username: &str) -> Result; /// Find session by ID async fn find_session(&self, session_id: &str) -> Result>; /// Update session last accessed time async fn update_session_accessed(&self, session_id: &str) -> Result<()>; /// Update last login time async fn update_last_login(&self, user_id: Uuid) -> Result<()>; /// Invalidate all user sessions async fn invalidate_all_user_sessions(&self, user_id: Uuid) -> Result<()>; /// Create OAuth account link async fn create_oauth_account( &self, user_id: Uuid, provider: &str, provider_id: &str, ) -> Result<()>; /// Find user by OAuth account async fn find_user_by_oauth_account( &self, provider: &str, provider_id: &str, ) -> Result>; /// Create token for password reset, verification, etc. async fn create_token( &self, user_id: Uuid, token_type: &str, token_hash: &str, expires_at: chrono::DateTime, ) -> Result<()>; /// Find token by hash and type async fn find_token( &self, token_hash: &str, token_type: &str, ) -> Result)>>; /// Mark token as used async fn use_token(&self, token_hash: &str, token_type: &str) -> Result<()>; /// Verify user email address async fn verify_email(&self, user_id: Uuid) -> Result<()>; /// Cleanup expired tokens async fn cleanup_expired_tokens(&self) -> Result; } /// Database-agnostic authentication repository implementation #[derive(Debug, Clone)] pub struct AuthRepository { database: DatabaseConnection, } impl AuthRepository { /// Create a new auth repository with a database connection pub fn new(database: DatabaseConnection) -> Self { Self { database } } /// Get the database type pub fn database_type(&self) -> DatabaseType { self.database.database_type() } /// Create from database pool (convenience method) pub fn from_pool(pool: &crate::database::DatabasePool) -> Self { let connection = DatabaseConnection::from_pool(pool); Self::new(connection) } /// Get a reference to the database connection (for compatibility) pub fn pool(&self) -> &DatabaseConnection { &self.database } } #[async_trait] impl AuthRepositoryTrait for AuthRepository { async fn init_tables(&self) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.init_postgres_tables().await, DatabaseType::SQLite => self.init_sqlite_tables().await, } } async fn create_user(&self, user: &CreateUserRequest) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.create_user_postgres(user).await, DatabaseType::SQLite => self.create_user_sqlite(user).await, } } async fn find_user_by_email(&self, email: &str) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.find_user_by_email_postgres(email).await, DatabaseType::SQLite => self.find_user_by_email_sqlite(email).await, } } async fn find_user_by_id(&self, id: &Uuid) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.find_user_by_id_postgres(id).await, DatabaseType::SQLite => self.find_user_by_id_sqlite(id).await, } } async fn update_user(&self, user: &DatabaseUser) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_user_postgres(user).await, DatabaseType::SQLite => self.update_user_sqlite(user).await, } } async fn delete_user(&self, id: &Uuid) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.delete_user_postgres(id).await, DatabaseType::SQLite => self.delete_user_sqlite(id).await, } } async fn verify_password(&self, email: &str, password_hash: &str) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.verify_password_postgres(email, password_hash).await, DatabaseType::SQLite => self.verify_password_sqlite(email, password_hash).await, } } async fn update_password(&self, id: &Uuid, password_hash: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_password_postgres(id, password_hash).await, DatabaseType::SQLite => self.update_password_sqlite(id, password_hash).await, } } async fn get_user_roles(&self, id: &Uuid) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.get_user_roles_postgres(id).await, DatabaseType::SQLite => self.get_user_roles_sqlite(id).await, } } async fn add_user_role(&self, id: &Uuid, role: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.add_user_role_postgres(id, role).await, DatabaseType::SQLite => self.add_user_role_sqlite(id, role).await, } } async fn remove_user_role(&self, id: &Uuid, role: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.remove_user_role_postgres(id, role).await, DatabaseType::SQLite => self.remove_user_role_sqlite(id, role).await, } } async fn create_oauth_user(&self, user: &OAuthUserRequest) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.create_oauth_user_postgres(user).await, DatabaseType::SQLite => self.create_oauth_user_sqlite(user).await, } } async fn find_user_by_oauth( &self, provider: &str, provider_id: &str, ) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => { self.find_user_by_oauth_postgres(provider, provider_id) .await } DatabaseType::SQLite => self.find_user_by_oauth_sqlite(provider, provider_id).await, } } async fn get_user_profile(&self, id: &Uuid) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.get_user_profile_postgres(id).await, DatabaseType::SQLite => self.get_user_profile_sqlite(id).await, } } async fn update_user_profile(&self, user: &DatabaseUser) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_user_profile_postgres(user).await, DatabaseType::SQLite => self.update_user_profile_sqlite(user).await, } } async fn get_user_sessions(&self, user_id: &Uuid) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.get_user_sessions_postgres(user_id).await, DatabaseType::SQLite => self.get_user_sessions_sqlite(user_id).await, } } async fn create_session(&self, session: &CreateSessionRequest) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.create_session_postgres(session).await, DatabaseType::SQLite => self.create_session_sqlite(session).await, } } async fn get_session_by_token(&self, token: &str) -> Result> { match self.database.database_type() { DatabaseType::PostgreSQL => self.get_session_by_token_postgres(token).await, DatabaseType::SQLite => self.get_session_by_token_sqlite(token).await, } } async fn update_session(&self, session: &UserSession) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_session_postgres(session).await, DatabaseType::SQLite => self.update_session_sqlite(session).await, } } async fn delete_session(&self, token: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.delete_session_postgres(token).await, DatabaseType::SQLite => self.delete_session_sqlite(token).await, } } async fn cleanup_expired_sessions(&self) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.cleanup_expired_sessions_postgres().await, DatabaseType::SQLite => self.cleanup_expired_sessions_sqlite().await, } } async fn email_exists(&self, email: &str) -> Result { match self.find_user_by_email(email).await { Ok(Some(_)) => Ok(true), Ok(None) => Ok(false), Err(e) => Err(e), } } async fn username_exists(&self, username: &str) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.username_exists_postgres(username).await, DatabaseType::SQLite => self.username_exists_sqlite(username).await, } } async fn find_session(&self, session_id: &str) -> Result> { self.get_session_by_token(session_id).await } async fn update_session_accessed(&self, session_id: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_session_accessed_postgres(session_id).await, DatabaseType::SQLite => self.update_session_accessed_sqlite(session_id).await, } } async fn update_last_login(&self, user_id: Uuid) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.update_last_login_postgres(user_id).await, DatabaseType::SQLite => self.update_last_login_sqlite(user_id).await, } } async fn invalidate_all_user_sessions(&self, user_id: Uuid) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.invalidate_all_user_sessions_postgres(user_id).await, DatabaseType::SQLite => self.invalidate_all_user_sessions_sqlite(user_id).await, } } async fn create_oauth_account( &self, user_id: Uuid, provider: &str, provider_id: &str, ) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => { self.create_oauth_account_postgres(user_id, provider, provider_id) .await } DatabaseType::SQLite => { self.create_oauth_account_sqlite(user_id, provider, provider_id) .await } } } async fn find_user_by_oauth_account( &self, provider: &str, provider_id: &str, ) -> Result> { self.find_user_by_oauth(provider, provider_id).await } async fn create_token( &self, user_id: Uuid, token_type: &str, token_hash: &str, expires_at: chrono::DateTime, ) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => { self.create_token_postgres(user_id, token_type, token_hash, expires_at) .await } DatabaseType::SQLite => { self.create_token_sqlite(user_id, token_type, token_hash, expires_at) .await } } } async fn find_token( &self, token_hash: &str, token_type: &str, ) -> Result)>> { match self.database.database_type() { DatabaseType::PostgreSQL => self.find_token_postgres(token_hash, token_type).await, DatabaseType::SQLite => self.find_token_sqlite(token_hash, token_type).await, } } async fn use_token(&self, token_hash: &str, token_type: &str) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.use_token_postgres(token_hash, token_type).await, DatabaseType::SQLite => self.use_token_sqlite(token_hash, token_type).await, } } async fn verify_email(&self, user_id: Uuid) -> Result<()> { match self.database.database_type() { DatabaseType::PostgreSQL => self.verify_email_postgres(user_id).await, DatabaseType::SQLite => self.verify_email_sqlite(user_id).await, } } async fn cleanup_expired_tokens(&self) -> Result { match self.database.database_type() { DatabaseType::PostgreSQL => self.cleanup_expired_tokens_postgres().await, DatabaseType::SQLite => self.cleanup_expired_tokens_sqlite().await, } } } // PostgreSQL implementations impl AuthRepository { async fn init_postgres_tables(&self) -> Result<()> { // Create users table let create_users_table = r#" CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email VARCHAR(255) UNIQUE NOT NULL, username VARCHAR(255) UNIQUE, display_name VARCHAR(255), password_hash VARCHAR(255) NOT NULL, avatar_url TEXT, roles TEXT[] DEFAULT ARRAY[]::TEXT[], is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, email_verified BOOLEAN DEFAULT FALSE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), last_login TIMESTAMPTZ, two_factor_enabled BOOLEAN DEFAULT FALSE, two_factor_secret VARCHAR(255), backup_codes TEXT[] DEFAULT ARRAY[]::TEXT[] ) "#; // Create sessions table let create_sessions_table = r#" CREATE TABLE IF NOT EXISTS user_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) UNIQUE NOT NULL, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), last_used_at TIMESTAMPTZ, user_agent TEXT, ip_address INET, is_active BOOLEAN DEFAULT TRUE ) "#; // Create OAuth providers table let create_oauth_table = r#" CREATE TABLE IF NOT EXISTS oauth_providers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider VARCHAR(50) NOT NULL, provider_id VARCHAR(255) NOT NULL, provider_data JSONB, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(provider, provider_id) ) "#; // Create indexes let create_indexes = vec![ "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)", "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)", "CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(token)", "CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON user_sessions(user_id)", "CREATE INDEX IF NOT EXISTS idx_oauth_provider ON oauth_providers(provider, provider_id)", ]; self.database.execute(create_users_table, &[]).await?; self.database.execute(create_sessions_table, &[]).await?; self.database.execute(create_oauth_table, &[]).await?; for index in create_indexes { self.database.execute(index, &[]).await?; } Ok(()) } async fn create_user_postgres(&self, user: &CreateUserRequest) -> Result { let now = Utc::now(); let id = Uuid::new_v4(); let roles = serde_json::to_string(&vec!["user".to_string()])?; let query = r#" INSERT INTO users (id, email, username, display_name, password_hash, roles, is_active, is_verified, email_verified, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id, email, username, display_name, password_hash, avatar_url, roles, is_active, is_verified, email_verified, created_at, updated_at, last_login, two_factor_enabled, two_factor_secret, backup_codes "#; let params = vec![ DatabaseParam::Uuid(id), DatabaseParam::String(user.email.clone()), DatabaseParam::OptionalString(user.username.clone()), DatabaseParam::OptionalString(user.display_name.clone()), DatabaseParam::String(user.password_hash.clone()), DatabaseParam::String(roles), DatabaseParam::Bool(user.is_active), DatabaseParam::Bool(user.is_verified), DatabaseParam::Bool(user.is_verified), DatabaseParam::DateTime(now), DatabaseParam::DateTime(now), ]; let row = self.database.fetch_one(query, ¶ms).await?; Ok(DatabaseUser { id: row.get_uuid("id")?, email: row.get_string("email")?, username: row.get_optional_string("username")?, display_name: row.get_optional_string("display_name")?, password_hash: row.get_string("password_hash")?, avatar_url: row.get_optional_string("avatar_url")?, roles: serde_json::from_str(&row.get_string("roles")?).unwrap_or_default(), is_active: row.get_bool("is_active")?, is_verified: row.get_bool("is_verified")?, email_verified: row.get_bool("email_verified")?, created_at: row.get_datetime("created_at")?, updated_at: row.get_datetime("updated_at")?, last_login: row.get_optional_datetime("last_login")?, two_factor_enabled: row.get_bool("two_factor_enabled")?, two_factor_secret: row.get_optional_string("two_factor_secret")?, backup_codes: serde_json::from_str( &row.get_optional_string("backup_codes")?.unwrap_or_default(), ) .unwrap_or_default(), }) } async fn find_user_by_email_postgres(&self, email: &str) -> Result> { let query = r#" SELECT id, email, username, display_name, password_hash, avatar_url, roles, is_active, is_verified, email_verified, created_at, updated_at, last_login, two_factor_enabled, two_factor_secret, backup_codes FROM users WHERE email = $1 "#; let params = vec![DatabaseParam::String(email.to_string())]; let row = self.database.fetch_optional(query, ¶ms).await?; Ok(row.map(|r| DatabaseUser { id: r.get_uuid("id").unwrap(), email: r.get_string("email").unwrap(), username: r.get_optional_string("username").unwrap(), display_name: r.get_optional_string("display_name").unwrap(), password_hash: r.get_string("password_hash").unwrap(), avatar_url: r.get_optional_string("avatar_url").unwrap(), roles: serde_json::from_str(&r.get_string("roles").unwrap()).unwrap_or_default(), is_active: r.get_bool("is_active").unwrap(), is_verified: r.get_bool("is_verified").unwrap(), email_verified: r.get_bool("email_verified").unwrap(), created_at: r.get_datetime("created_at").unwrap(), updated_at: r.get_datetime("updated_at").unwrap(), last_login: r.get_optional_datetime("last_login").unwrap(), two_factor_enabled: r.get_bool("two_factor_enabled").unwrap(), two_factor_secret: r.get_optional_string("two_factor_secret").unwrap(), backup_codes: serde_json::from_str( &r.get_optional_string("backup_codes") .unwrap() .unwrap_or_default(), ) .unwrap_or_default(), })) } async fn find_user_by_id_postgres(&self, id: &Uuid) -> Result> { let query = r#" SELECT id, email, username, display_name, password_hash, avatar_url, roles, is_active, is_verified, email_verified, created_at, updated_at, last_login, two_factor_enabled, two_factor_secret, backup_codes FROM users WHERE id = $1 "#; let params = vec![DatabaseParam::Uuid(*id)]; let row = self.database.fetch_optional(query, ¶ms).await?; Ok(row.map(|r| DatabaseUser { id: r.get_uuid("id").unwrap(), email: r.get_string("email").unwrap(), username: r.get_optional_string("username").unwrap(), display_name: r.get_optional_string("display_name").unwrap(), password_hash: r.get_string("password_hash").unwrap(), avatar_url: r.get_optional_string("avatar_url").unwrap(), roles: serde_json::from_str(&r.get_string("roles").unwrap()).unwrap_or_default(), is_active: r.get_bool("is_active").unwrap(), is_verified: r.get_bool("is_verified").unwrap(), email_verified: r.get_bool("email_verified").unwrap(), created_at: r.get_datetime("created_at").unwrap(), updated_at: r.get_datetime("updated_at").unwrap(), last_login: r.get_optional_datetime("last_login").unwrap(), two_factor_enabled: r.get_bool("two_factor_enabled").unwrap(), two_factor_secret: r.get_optional_string("two_factor_secret").unwrap(), backup_codes: serde_json::from_str( &r.get_optional_string("backup_codes") .unwrap() .unwrap_or_default(), ) .unwrap_or_default(), })) } // SQLite implementations async fn init_sqlite_tables(&self) -> Result<()> { // Create users table let create_users_table = r#" CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, username TEXT UNIQUE, display_name TEXT, password_hash TEXT NOT NULL, avatar_url TEXT, roles TEXT DEFAULT '[]', is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, email_verified BOOLEAN DEFAULT FALSE, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, last_login TEXT, two_factor_enabled BOOLEAN DEFAULT FALSE, two_factor_secret TEXT, backup_codes TEXT DEFAULT '[]' ) "#; // Create sessions table let create_sessions_table = r#" CREATE TABLE IF NOT EXISTS user_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, token TEXT UNIQUE NOT NULL, expires_at TEXT NOT NULL, created_at TEXT NOT NULL, last_used_at TEXT, user_agent TEXT, ip_address TEXT, is_active BOOLEAN DEFAULT TRUE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) "#; // Create OAuth providers table let create_oauth_table = r#" CREATE TABLE IF NOT EXISTS oauth_providers ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, provider TEXT NOT NULL, provider_id TEXT NOT NULL, provider_data TEXT, created_at TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE(provider, provider_id) ) "#; // Create indexes let create_indexes = vec![ "CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)", "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)", "CREATE INDEX IF NOT EXISTS idx_sessions_token ON user_sessions(token)", "CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON user_sessions(user_id)", "CREATE INDEX IF NOT EXISTS idx_oauth_provider ON oauth_providers(provider, provider_id)", ]; self.database.execute(create_users_table, &[]).await?; self.database.execute(create_sessions_table, &[]).await?; self.database.execute(create_oauth_table, &[]).await?; for index in create_indexes { self.database.execute(index, &[]).await?; } Ok(()) } async fn create_user_sqlite(&self, user: &CreateUserRequest) -> Result { let now = Utc::now(); let id = Uuid::new_v4(); let roles = serde_json::to_string(&vec!["user".to_string()])?; let query = r#" INSERT INTO users (id, email, username, display_name, password_hash, roles, is_active, is_verified, email_verified, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11) "#; let params = vec![ DatabaseParam::String(id.to_string()), DatabaseParam::String(user.email.clone()), DatabaseParam::OptionalString(user.username.clone()), DatabaseParam::OptionalString(user.display_name.clone()), DatabaseParam::String(user.password_hash.clone()), DatabaseParam::String(roles), DatabaseParam::Bool(user.is_active), DatabaseParam::Bool(user.is_verified), DatabaseParam::Bool(user.is_verified), DatabaseParam::String(now.to_rfc3339()), DatabaseParam::String(now.to_rfc3339()), ]; self.database.execute(query, ¶ms).await?; Ok(DatabaseUser { id, email: user.email.clone(), username: user.username.clone(), display_name: user.display_name.clone(), password_hash: user.password_hash.clone(), avatar_url: None, roles: vec!["user".to_string()], is_active: user.is_active, is_verified: user.is_verified, email_verified: user.is_verified, created_at: now, updated_at: now, last_login: None, two_factor_enabled: false, two_factor_secret: None, backup_codes: Vec::new(), }) } async fn find_user_by_email_sqlite(&self, email: &str) -> Result> { let query = r#" SELECT id, email, username, display_name, password_hash, avatar_url, roles, is_active, is_verified, email_verified, created_at, updated_at, last_login, two_factor_enabled, two_factor_secret, backup_codes FROM users WHERE email = ?1 "#; let params = vec![DatabaseParam::String(email.to_string())]; let row = self.database.fetch_optional(query, ¶ms).await?; Ok(row.map(|r| DatabaseUser { id: Uuid::parse_str(&r.get_string("id").unwrap()).unwrap(), email: r.get_string("email").unwrap(), username: r.get_optional_string("username").unwrap(), display_name: r.get_optional_string("display_name").unwrap(), password_hash: r.get_string("password_hash").unwrap(), avatar_url: r.get_optional_string("avatar_url").unwrap(), roles: serde_json::from_str(&r.get_string("roles").unwrap()).unwrap_or_default(), is_active: r.get_bool("is_active").unwrap(), is_verified: r.get_bool("is_verified").unwrap(), email_verified: r.get_bool("email_verified").unwrap(), created_at: DateTime::parse_from_rfc3339(&r.get_string("created_at").unwrap()) .unwrap() .with_timezone(&Utc), updated_at: DateTime::parse_from_rfc3339(&r.get_string("updated_at").unwrap()) .unwrap() .with_timezone(&Utc), last_login: r.get_optional_string("last_login").unwrap().map(|s| { DateTime::parse_from_rfc3339(&s) .unwrap() .with_timezone(&Utc) }), two_factor_enabled: r.get_bool("two_factor_enabled").unwrap(), two_factor_secret: r.get_optional_string("two_factor_secret").unwrap(), backup_codes: serde_json::from_str( &r.get_optional_string("backup_codes") .unwrap() .unwrap_or_default(), ) .unwrap_or_default(), })) } async fn find_user_by_id_sqlite(&self, id: &Uuid) -> Result> { let query = r#" SELECT id, email, username, display_name, password_hash, avatar_url, roles, is_active, is_verified, email_verified, created_at, updated_at, last_login, two_factor_enabled, two_factor_secret, backup_codes FROM users WHERE id = ?1 "#; let params = vec![DatabaseParam::String(id.to_string())]; let row = self.database.fetch_optional(query, ¶ms).await?; Ok(row.map(|r| DatabaseUser { id: Uuid::parse_str(&r.get_string("id").unwrap()).unwrap(), email: r.get_string("email").unwrap(), username: r.get_optional_string("username").unwrap(), display_name: r.get_optional_string("display_name").unwrap(), password_hash: r.get_string("password_hash").unwrap(), avatar_url: r.get_optional_string("avatar_url").unwrap(), roles: serde_json::from_str(&r.get_string("roles").unwrap()).unwrap_or_default(), is_active: r.get_bool("is_active").unwrap(), is_verified: r.get_bool("is_verified").unwrap(), email_verified: r.get_bool("email_verified").unwrap(), created_at: DateTime::parse_from_rfc3339(&r.get_string("created_at").unwrap()) .unwrap() .with_timezone(&Utc), updated_at: DateTime::parse_from_rfc3339(&r.get_string("updated_at").unwrap()) .unwrap() .with_timezone(&Utc), last_login: r.get_optional_string("last_login").unwrap().map(|s| { DateTime::parse_from_rfc3339(&s) .unwrap() .with_timezone(&Utc) }), two_factor_enabled: r.get_bool("two_factor_enabled").unwrap(), two_factor_secret: r.get_optional_string("two_factor_secret").unwrap(), backup_codes: serde_json::from_str( &r.get_optional_string("backup_codes") .unwrap() .unwrap_or_default(), ) .unwrap_or_default(), })) } // Stub implementations for remaining methods async fn update_user_postgres(&self, _user: &DatabaseUser) -> Result<()> { // TODO: Implement user update for PostgreSQL Ok(()) } async fn update_user_sqlite(&self, _user: &DatabaseUser) -> Result<()> { // TODO: Implement user update for SQLite Ok(()) } async fn delete_user_postgres(&self, _id: &Uuid) -> Result<()> { // TODO: Implement user deletion for PostgreSQL Ok(()) } async fn delete_user_sqlite(&self, _id: &Uuid) -> Result<()> { // TODO: Implement user deletion for SQLite Ok(()) } async fn verify_password_postgres(&self, email: &str, password_hash: &str) -> Result { let query = "SELECT password_hash FROM users WHERE email = $1 AND is_active = true LIMIT 1"; let params = vec![DatabaseParam::String(email.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => { let stored_hash = row.get_string("password_hash")?; Ok(stored_hash == password_hash) } None => Ok(false), } } async fn verify_password_sqlite(&self, email: &str, password_hash: &str) -> Result { let query = "SELECT password_hash FROM users WHERE email = ? AND is_active = 1 LIMIT 1"; let params = vec![DatabaseParam::String(email.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => { let stored_hash = row.get_string("password_hash")?; Ok(stored_hash == password_hash) } None => Ok(false), } } async fn update_password_postgres(&self, id: &Uuid, password_hash: &str) -> Result<()> { let query = "UPDATE users SET password_hash = $1, updated_at = NOW() WHERE id = $2"; let params = vec![ DatabaseParam::String(password_hash.to_string()), DatabaseParam::Uuid(*id), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn update_password_sqlite(&self, id: &Uuid, password_hash: &str) -> Result<()> { let query = "UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?"; let params = vec![ DatabaseParam::String(password_hash.to_string()), DatabaseParam::String(id.to_string()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn get_user_roles_postgres(&self, _id: &Uuid) -> Result> { // TODO: Implement get user roles for PostgreSQL Ok(vec![]) } async fn get_user_roles_sqlite(&self, _id: &Uuid) -> Result> { // TODO: Implement get user roles for SQLite Ok(vec![]) } async fn add_user_role_postgres(&self, _id: &Uuid, _role: &str) -> Result<()> { // TODO: Implement add user role for PostgreSQL Ok(()) } async fn add_user_role_sqlite(&self, _id: &Uuid, _role: &str) -> Result<()> { // TODO: Implement add user role for SQLite Ok(()) } async fn remove_user_role_postgres(&self, _id: &Uuid, _role: &str) -> Result<()> { // TODO: Implement remove user role for PostgreSQL Ok(()) } async fn remove_user_role_sqlite(&self, _id: &Uuid, _role: &str) -> Result<()> { // TODO: Implement remove user role for SQLite Ok(()) } async fn create_oauth_user_postgres(&self, _user: &OAuthUserRequest) -> Result { // TODO: Implement OAuth user creation for PostgreSQL Err(anyhow::anyhow!("Not implemented")) } async fn create_oauth_user_sqlite(&self, _user: &OAuthUserRequest) -> Result { // TODO: Implement OAuth user creation for SQLite Err(anyhow::anyhow!("Not implemented")) } async fn find_user_by_oauth_postgres( &self, provider: &str, provider_id: &str, ) -> Result> { let query = r#" SELECT u.id, u.email, u.username, u.display_name, u.password_hash, u.avatar_url, u.roles, u.is_active, u.is_verified, u.email_verified, u.created_at, u.updated_at, u.last_login, u.two_factor_enabled, u.two_factor_secret, u.backup_codes FROM users u JOIN oauth_providers o ON u.id = o.user_id WHERE o.provider = $1 AND o.provider_id = $2 AND u.is_active = true LIMIT 1 "#; let params = vec![ DatabaseParam::String(provider.to_string()), DatabaseParam::String(provider_id.to_string()), ]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => Ok(Some(self.row_to_database_user(row)?)), None => Ok(None), } } async fn find_user_by_oauth_sqlite( &self, provider: &str, provider_id: &str, ) -> Result> { let query = r#" SELECT u.id, u.email, u.username, u.display_name, u.password_hash, u.avatar_url, u.roles, u.is_active, u.is_verified, u.email_verified, u.created_at, u.updated_at, u.last_login, u.two_factor_enabled, u.two_factor_secret, u.backup_codes FROM users u JOIN oauth_providers o ON u.id = o.user_id WHERE o.provider = ? AND o.provider_id = ? AND u.is_active = 1 LIMIT 1 "#; let params = vec![ DatabaseParam::String(provider.to_string()), DatabaseParam::String(provider_id.to_string()), ]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => Ok(Some(self.row_to_database_user(row)?)), None => Ok(None), } } async fn get_user_profile_postgres(&self, _id: &Uuid) -> Result> { // TODO: Implement get user profile for PostgreSQL Ok(None) } async fn get_user_profile_sqlite(&self, _id: &Uuid) -> Result> { // TODO: Implement get user profile for SQLite Ok(None) } async fn update_user_profile_postgres(&self, _user: &DatabaseUser) -> Result<()> { // TODO: Implement update user profile for PostgreSQL Ok(()) } async fn update_user_profile_sqlite(&self, _user: &DatabaseUser) -> Result<()> { // TODO: Implement update user profile for SQLite Ok(()) } async fn get_user_sessions_postgres(&self, _user_id: &Uuid) -> Result> { // TODO: Implement get user sessions for PostgreSQL Ok(vec![]) } async fn get_user_sessions_sqlite(&self, _user_id: &Uuid) -> Result> { // TODO: Implement get user sessions for SQLite Ok(vec![]) } async fn create_session_postgres(&self, session: &CreateSessionRequest) -> Result { let id = Uuid::new_v4(); let now = Utc::now(); let query = r#" INSERT INTO user_sessions (id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active "#; let params = vec![ DatabaseParam::Uuid(id), DatabaseParam::Uuid(session.user_id), DatabaseParam::String(session.token.clone()), DatabaseParam::DateTime(session.expires_at), DatabaseParam::DateTime(now), DatabaseParam::DateTime(now), DatabaseParam::OptionalString(session.user_agent.clone()), DatabaseParam::OptionalString(session.ip_address.clone()), DatabaseParam::Bool(true), ]; let row = self.database.fetch_one(query, ¶ms).await?; Ok(UserSession { id: row.get_uuid("id")?, user_id: row.get_uuid("user_id")?, token: row.get_string("token")?, expires_at: row.get_datetime("expires_at")?, created_at: row.get_datetime("created_at")?, last_used_at: row.get_optional_datetime("last_used_at")?, user_agent: row.get_optional_string("user_agent")?, ip_address: row.get_optional_string("ip_address")?, is_active: row.get_bool("is_active")?, }) } async fn create_session_sqlite(&self, session: &CreateSessionRequest) -> Result { let id = Uuid::new_v4(); let now = Utc::now(); let query = r#" INSERT INTO user_sessions (id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active "#; let params = vec![ DatabaseParam::String(id.to_string()), DatabaseParam::String(session.user_id.to_string()), DatabaseParam::String(session.token.clone()), DatabaseParam::String(session.expires_at.to_rfc3339()), DatabaseParam::String(now.to_rfc3339()), DatabaseParam::String(now.to_rfc3339()), DatabaseParam::OptionalString(session.user_agent.clone()), DatabaseParam::OptionalString(session.ip_address.clone()), DatabaseParam::Bool(true), ]; let row = self.database.fetch_one(query, ¶ms).await?; Ok(UserSession { id: row.get_uuid("id")?, user_id: row.get_uuid("user_id")?, token: row.get_string("token")?, expires_at: row.get_datetime("expires_at")?, created_at: row.get_datetime("created_at")?, last_used_at: row.get_optional_datetime("last_used_at")?, user_agent: row.get_optional_string("user_agent")?, ip_address: row.get_optional_string("ip_address")?, is_active: row.get_bool("is_active")?, }) } async fn get_session_by_token_postgres(&self, token: &str) -> Result> { let query = r#" SELECT id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active FROM user_sessions WHERE token = $1 AND is_active = true AND expires_at > NOW() LIMIT 1 "#; let params = vec![DatabaseParam::String(token.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => Ok(Some(UserSession { id: row.get_uuid("id")?, user_id: row.get_uuid("user_id")?, token: row.get_string("token")?, expires_at: row.get_datetime("expires_at")?, created_at: row.get_datetime("created_at")?, last_used_at: row.get_optional_datetime("last_used_at")?, user_agent: row.get_optional_string("user_agent")?, ip_address: row.get_optional_string("ip_address")?, is_active: row.get_bool("is_active")?, })), None => Ok(None), } } async fn get_session_by_token_sqlite(&self, token: &str) -> Result> { let query = r#" SELECT id, user_id, token, expires_at, created_at, last_used_at, user_agent, ip_address, is_active FROM user_sessions WHERE token = ? AND is_active = 1 AND expires_at > datetime('now') LIMIT 1 "#; let params = vec![DatabaseParam::String(token.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => Ok(Some(UserSession { id: row.get_uuid("id")?, user_id: row.get_uuid("user_id")?, token: row.get_string("token")?, expires_at: row.get_datetime("expires_at")?, created_at: row.get_datetime("created_at")?, last_used_at: row.get_optional_datetime("last_used_at")?, user_agent: row.get_optional_string("user_agent")?, ip_address: row.get_optional_string("ip_address")?, is_active: row.get_bool("is_active")?, })), None => Ok(None), } } async fn update_session_postgres(&self, _session: &UserSession) -> Result<()> { // TODO: Implement update session for PostgreSQL Ok(()) } async fn update_session_sqlite(&self, _session: &UserSession) -> Result<()> { // TODO: Implement update session for SQLite Ok(()) } async fn delete_session_postgres(&self, token: &str) -> Result<()> { let query = "DELETE FROM user_sessions WHERE token = $1"; let params = vec![DatabaseParam::String(token.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn delete_session_sqlite(&self, token: &str) -> Result<()> { let query = "DELETE FROM user_sessions WHERE token = ?"; let params = vec![DatabaseParam::String(token.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn cleanup_expired_sessions_postgres(&self) -> Result { let query = "DELETE FROM user_sessions WHERE expires_at <= NOW() OR is_active = false"; self.database.execute(query, &[]).await?; // Note: In a real implementation, you'd want to return the actual count // This would require a different approach or counting before deletion Ok(0) } async fn cleanup_expired_sessions_sqlite(&self) -> Result { let query = "DELETE FROM user_sessions WHERE expires_at <= datetime('now') OR is_active = 0"; self.database.execute(query, &[]).await?; // Note: In a real implementation, you'd want to return the actual count // This would require a different approach or counting before deletion Ok(0) } async fn username_exists_postgres(&self, username: &str) -> Result { let query = "SELECT 1 FROM users WHERE username = $1 LIMIT 1"; let params = vec![DatabaseParam::String(username.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(_) => Ok(true), None => Ok(false), } } async fn username_exists_sqlite(&self, username: &str) -> Result { let query = "SELECT 1 FROM users WHERE username = ? LIMIT 1"; let params = vec![DatabaseParam::String(username.to_string())]; match self.database.fetch_optional(query, ¶ms).await? { Some(_) => Ok(true), None => Ok(false), } } async fn update_session_accessed_postgres(&self, session_id: &str) -> Result<()> { let query = "UPDATE user_sessions SET last_used_at = NOW() WHERE token = $1 AND is_active = true"; let params = vec![DatabaseParam::String(session_id.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn update_session_accessed_sqlite(&self, session_id: &str) -> Result<()> { let query = "UPDATE user_sessions SET last_used_at = datetime('now') WHERE token = ? AND is_active = 1"; let params = vec![DatabaseParam::String(session_id.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn update_last_login_postgres(&self, user_id: Uuid) -> Result<()> { let query = "UPDATE users SET last_login = NOW(), updated_at = NOW() WHERE id = $1"; let params = vec![DatabaseParam::Uuid(user_id)]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn update_last_login_sqlite(&self, user_id: Uuid) -> Result<()> { let query = "UPDATE users SET last_login = datetime('now'), updated_at = datetime('now') WHERE id = ?"; let params = vec![DatabaseParam::String(user_id.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn invalidate_all_user_sessions_postgres(&self, user_id: Uuid) -> Result<()> { let query = "UPDATE user_sessions SET is_active = false WHERE user_id = $1"; let params = vec![DatabaseParam::Uuid(user_id)]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn invalidate_all_user_sessions_sqlite(&self, user_id: Uuid) -> Result<()> { let query = "UPDATE user_sessions SET is_active = 0 WHERE user_id = ?"; let params = vec![DatabaseParam::String(user_id.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn create_oauth_account_postgres( &self, user_id: Uuid, provider: &str, provider_id: &str, ) -> Result<()> { let query = r#" INSERT INTO oauth_providers (user_id, provider, provider_id, created_at) VALUES ($1, $2, $3, NOW()) ON CONFLICT (provider, provider_id) DO NOTHING "#; let params = vec![ DatabaseParam::Uuid(user_id), DatabaseParam::String(provider.to_string()), DatabaseParam::String(provider_id.to_string()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn create_oauth_account_sqlite( &self, user_id: Uuid, provider: &str, provider_id: &str, ) -> Result<()> { let query = r#" INSERT OR IGNORE INTO oauth_providers (user_id, provider, provider_id, created_at) VALUES (?, ?, ?, datetime('now')) "#; let params = vec![ DatabaseParam::String(user_id.to_string()), DatabaseParam::String(provider.to_string()), DatabaseParam::String(provider_id.to_string()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn create_token_postgres( &self, user_id: Uuid, token_type: &str, token_hash: &str, expires_at: chrono::DateTime, ) -> Result<()> { // First, ensure tokens table exists let create_table = r#" CREATE TABLE IF NOT EXISTS user_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(255) NOT NULL, token_type VARCHAR(50) NOT NULL, expires_at TIMESTAMPTZ NOT NULL, used_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(token_hash, token_type) ) "#; self.database.execute(create_table, &[]).await?; let query = r#" INSERT INTO user_tokens (user_id, token_hash, token_type, expires_at) VALUES ($1, $2, $3, $4) ON CONFLICT (token_hash, token_type) DO UPDATE SET expires_at = EXCLUDED.expires_at, used_at = NULL "#; let params = vec![ DatabaseParam::Uuid(user_id), DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), DatabaseParam::DateTime(expires_at), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn create_token_sqlite( &self, user_id: Uuid, token_type: &str, token_hash: &str, expires_at: chrono::DateTime, ) -> Result<()> { // First, ensure tokens table exists let create_table = r#" CREATE TABLE IF NOT EXISTS user_tokens ( id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), user_id TEXT NOT NULL, token_hash TEXT NOT NULL, token_type TEXT NOT NULL, expires_at TEXT NOT NULL, used_at TEXT, created_at TEXT DEFAULT (datetime('now')), UNIQUE(token_hash, token_type), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) "#; self.database.execute(create_table, &[]).await?; let query = r#" INSERT OR REPLACE INTO user_tokens (user_id, token_hash, token_type, expires_at) VALUES (?, ?, ?, ?) "#; let params = vec![ DatabaseParam::String(user_id.to_string()), DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), DatabaseParam::String(expires_at.to_rfc3339()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn find_token_postgres( &self, token_hash: &str, token_type: &str, ) -> Result)>> { let query = r#" SELECT user_id, expires_at FROM user_tokens WHERE token_hash = $1 AND token_type = $2 AND expires_at > NOW() AND used_at IS NULL LIMIT 1 "#; let params = vec![ DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), ]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => { let user_id = row.get_uuid("user_id")?; let expires_at = row.get_datetime("expires_at")?; Ok(Some((user_id, expires_at))) } None => Ok(None), } } async fn find_token_sqlite( &self, token_hash: &str, token_type: &str, ) -> Result)>> { let query = r#" SELECT user_id, expires_at FROM user_tokens WHERE token_hash = ? AND token_type = ? AND expires_at > datetime('now') AND used_at IS NULL LIMIT 1 "#; let params = vec![ DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), ]; match self.database.fetch_optional(query, ¶ms).await? { Some(row) => { let user_id = row.get_uuid("user_id")?; let expires_at = row.get_datetime("expires_at")?; Ok(Some((user_id, expires_at))) } None => Ok(None), } } async fn use_token_postgres(&self, token_hash: &str, token_type: &str) -> Result<()> { let query = r#" UPDATE user_tokens SET used_at = NOW() WHERE token_hash = $1 AND token_type = $2 AND used_at IS NULL "#; let params = vec![ DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn use_token_sqlite(&self, token_hash: &str, token_type: &str) -> Result<()> { let query = r#" UPDATE user_tokens SET used_at = datetime('now') WHERE token_hash = ? AND token_type = ? AND used_at IS NULL "#; let params = vec![ DatabaseParam::String(token_hash.to_string()), DatabaseParam::String(token_type.to_string()), ]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn verify_email_postgres(&self, user_id: Uuid) -> Result<()> { let query = r#" UPDATE users SET email_verified = true, is_verified = true, updated_at = NOW() WHERE id = $1 "#; let params = vec![DatabaseParam::Uuid(user_id)]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn verify_email_sqlite(&self, user_id: Uuid) -> Result<()> { let query = r#" UPDATE users SET email_verified = 1, is_verified = 1, updated_at = datetime('now') WHERE id = ? "#; let params = vec![DatabaseParam::String(user_id.to_string())]; self.database.execute(query, ¶ms).await?; Ok(()) } async fn cleanup_expired_tokens_postgres(&self) -> Result { let query = "DELETE FROM user_tokens WHERE expires_at <= NOW()"; self.database.execute(query, &[]).await?; // Note: In a real implementation, you'd want to return the actual count // This would require a different approach or counting before deletion Ok(0) } async fn cleanup_expired_tokens_sqlite(&self) -> Result { let query = "DELETE FROM user_tokens WHERE expires_at <= datetime('now')"; self.database.execute(query, &[]).await?; // Note: In a real implementation, you'd want to return the actual count // This would require a different approach or counting before deletion Ok(0) } // Helper method to convert database row to DatabaseUser fn row_to_database_user( &self, row: crate::database::connection::DatabaseRow, ) -> Result { let roles_json = row.get_string("roles")?; let roles: Vec = serde_json::from_str(&roles_json) .map_err(|e| anyhow::anyhow!("Failed to parse roles JSON: {}", e))?; let backup_codes_json = row.get_optional_string("backup_codes")?; let backup_codes: Vec = backup_codes_json .map(|json| serde_json::from_str(&json).unwrap_or_default()) .unwrap_or_default(); Ok(DatabaseUser { id: row.get_uuid("id")?, email: row.get_string("email")?, username: row.get_optional_string("username")?, display_name: row.get_optional_string("display_name")?, password_hash: row.get_string("password_hash")?, avatar_url: row.get_optional_string("avatar_url")?, roles, is_active: row.get_bool("is_active")?, is_verified: row.get_bool("is_verified")?, email_verified: row.get_bool("email_verified")?, created_at: row.get_datetime("created_at")?, updated_at: row.get_datetime("updated_at")?, last_login: row.get_optional_datetime("last_login")?, two_factor_enabled: row.get_bool("two_factor_enabled")?, two_factor_secret: row.get_optional_string("two_factor_secret")?, backup_codes, }) } } impl From for shared::auth::User { fn from(db_user: DatabaseUser) -> Self { Self { id: db_user.id, email: db_user.email, username: db_user.username.unwrap_or_default(), display_name: db_user.display_name, avatar_url: db_user.avatar_url, roles: db_user .roles .into_iter() .map(|r| match r.as_str() { "admin" => shared::auth::Role::Admin, "moderator" => shared::auth::Role::Moderator, "user" => shared::auth::Role::User, "guest" => shared::auth::Role::Guest, _ => shared::auth::Role::Custom(r), }) .collect(), is_active: db_user.is_active, email_verified: db_user.email_verified, created_at: db_user.created_at, updated_at: db_user.updated_at, last_login: db_user.last_login, profile: shared::auth::UserProfile::default(), two_factor_enabled: db_user.two_factor_enabled, } } } impl From for DatabaseUser { fn from(user: shared::auth::User) -> Self { Self { id: user.id, email: user.email, username: Some(user.username), display_name: user.display_name, password_hash: String::new(), // This should be handled separately avatar_url: user.avatar_url, roles: user .roles .into_iter() .map(|r| match r { shared::auth::Role::Admin => "admin".to_string(), shared::auth::Role::Moderator => "moderator".to_string(), shared::auth::Role::User => "user".to_string(), shared::auth::Role::Guest => "guest".to_string(), shared::auth::Role::Custom(name) => name, }) .collect(), is_active: user.is_active, is_verified: user.email_verified, email_verified: user.email_verified, created_at: user.created_at, updated_at: user.updated_at, last_login: user.last_login, two_factor_enabled: user.two_factor_enabled, two_factor_secret: None, backup_codes: Vec::new(), } } }