use crate::consts::ZELLIJ_PROJ_DIR; use crate::shared::set_permissions; use rusqlite::Connection; use std::path::PathBuf; #[derive(Debug)] pub enum TokenError { Database(rusqlite::Error), Io(std::io::Error), InvalidPath, } impl std::fmt::Display for TokenError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { TokenError::Database(e) => write!(f, "Database error: {}", e), TokenError::Io(e) => write!(f, "IO error: {}", e), TokenError::InvalidPath => write!(f, "Invalid path"), } } } impl std::error::Error for TokenError {} impl From for TokenError { fn from(error: rusqlite::Error) -> Self { TokenError::Database(error) } } impl From for TokenError { fn from(error: std::io::Error) -> Self { TokenError::Io(error) } } type Result = std::result::Result; fn get_db_path() -> Result { let data_dir = ZELLIJ_PROJ_DIR.data_dir(); std::fs::create_dir_all(data_dir)?; let db_path = data_dir.join("remote_sessions.db"); Ok(db_path) } fn init_db(conn: &Connection) -> Result<()> { conn.execute_batch("PRAGMA busy_timeout = 5000")?; conn.execute( "CREATE TABLE IF NOT EXISTS remote_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, server_url TEXT UNIQUE NOT NULL, session_token TEXT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, last_used_at DATETIME DEFAULT CURRENT_TIMESTAMP )", [], )?; Ok(()) } /// Save session token for a server (upsert) pub fn save_session_token(server_url: &str, session_token: &str) -> Result<()> { let db_path = get_db_path()?; // Set file permissions to 0600 if creating new file let is_new = !db_path.exists(); let conn = Connection::open(&db_path)?; init_db(&conn)?; if is_new { set_permissions(&db_path, 0o600)?; } conn.execute( "INSERT OR REPLACE INTO remote_sessions (server_url, session_token, last_used_at) VALUES (?1, ?2, CURRENT_TIMESTAMP)", [server_url, session_token], )?; Ok(()) } /// Get session token for a server, update last_used_at pub fn get_session_token(server_url: &str) -> Result> { let db_path = get_db_path()?; if !db_path.exists() { return Ok(None); } let conn = Connection::open(db_path)?; init_db(&conn)?; let token = match conn.query_row( "SELECT session_token FROM remote_sessions WHERE server_url = ?1", [server_url], |row| row.get::<_, String>(0), ) { Ok(token) => Some(token), Err(rusqlite::Error::QueryReturnedNoRows) => None, Err(e) => return Err(TokenError::Database(e)), }; if token.is_some() { // Update last_used_at conn.execute( "UPDATE remote_sessions SET last_used_at = CURRENT_TIMESTAMP WHERE server_url = ?1", [server_url], )?; } Ok(token) } /// Delete session token for a server pub fn delete_session_token(server_url: &str) -> Result { let db_path = get_db_path()?; if !db_path.exists() { return Ok(false); } let conn = Connection::open(db_path)?; init_db(&conn)?; let rows_affected = conn.execute( "DELETE FROM remote_sessions WHERE server_url = ?1", [server_url], )?; Ok(rows_affected > 0) }