use anyhow::Result; use chrono::Utc; use sqlx::SqlitePool; use uuid::Uuid; #[derive(Debug, Clone, sqlx::FromRow)] pub struct AdminSessionRow { pub session_id: String, pub did: String, pub handle: String, pub oauth_session_id: String, pub created_at: String, pub expires_at: String, } /// Creates a new admin session and returns the generated session_id. pub async fn create_session( pool: &SqlitePool, did: &str, handle: &str, oauth_session_id: &str, ttl_hours: u64, ) -> Result { let session_id = Uuid::new_v4().to_string(); let now = Utc::now(); let created_at = now.to_rfc3339(); let expires_at = (now + chrono::Duration::hours(ttl_hours as i64)).to_rfc3339(); sqlx::query( "INSERT INTO admin_sessions (session_id, did, handle, oauth_session_id, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?)", ) .bind(&session_id) .bind(did) .bind(handle) .bind(oauth_session_id) .bind(&created_at) .bind(&expires_at) .execute(pool) .await?; Ok(session_id) } /// Looks up a session by ID. Returns None if the session does not exist or has expired. pub async fn get_session(pool: &SqlitePool, session_id: &str) -> Result> { let now = Utc::now().to_rfc3339(); let row = sqlx::query_as::<_, AdminSessionRow>( "SELECT session_id, did, handle, oauth_session_id, created_at, expires_at FROM admin_sessions WHERE session_id = ? AND expires_at > ?", ) .bind(session_id) .bind(&now) .fetch_optional(pool) .await?; Ok(row) } /// Deletes a session by ID. pub async fn delete_session(pool: &SqlitePool, session_id: &str) -> Result<()> { sqlx::query("DELETE FROM admin_sessions WHERE session_id = ?") .bind(session_id) .execute(pool) .await?; Ok(()) } /// Deletes all expired sessions and returns the number of rows removed. pub async fn cleanup_expired_sessions(pool: &SqlitePool) -> Result { let now = Utc::now().to_rfc3339(); let result = sqlx::query("DELETE FROM admin_sessions WHERE expires_at <= ?") .bind(&now) .execute(pool) .await?; Ok(result.rows_affected()) }