use async_session::SessionStore; use axum_login::{AuthManagerLayer, AuthManagerLayerBuilder, AuthSession, AuthUser, AuthnBackend}; use julid::Julid; use sqlx::{ migrate::Migrator, sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}, SqlitePool, }; use tower_sessions::{ cookie::time::Duration, session_store::ExpiredDeletion, Expiry, Session, SessionManagerLayer, SqliteStore, }; use crate::User; const MAX_CONNS: u32 = 200; const MIN_CONNS: u32 = 5; const TIMEOUT: u64 = 11; const SESSION_TTL: Duration = Duration::new((365.2422 * 24. * 3600.0) as i64, 0); pub fn get_db_pool() -> SqlitePool { let db_filename = { std::env::var("DATABASE_FILE").unwrap_or_else(|_| { #[cfg(not(test))] { let home = std::env::var("HOME").expect("Could not determine $HOME for finding db file"); format!("{home}/.what2watch.db") } #[cfg(test)] { use rand::RngCore; let mut rng = rand::thread_rng(); let id = rng.next_u64(); // see https://www.sqlite.org/inmemorydb.html for meaning of the string; // it allows each separate test to have its own dedicated memory-backed db that // will live as long as the whole process format!("file:testdb-{id}?mode=memory&cache=shared") } }) }; tracing::info!("Connecting to DB at {db_filename}"); let conn_opts = SqliteConnectOptions::new() .foreign_keys(true) .auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental) .journal_mode(SqliteJournalMode::Wal) .synchronous(sqlx::sqlite::SqliteSynchronous::Normal) .filename(&db_filename) // need to build this out of band and put it in the project root; see instructions at // https://gitlab.com/nebkor/julid .extension("./libjulid") .busy_timeout(Duration::from_secs(TIMEOUT)) .create_if_missing(true); let pool = SqlitePoolOptions::new() .max_connections(MAX_CONNS) .min_connections(MIN_CONNS) .idle_timeout(Some(Duration::from_secs(3))) .max_lifetime(Some(Duration::from_secs(3600))) .connect_with(conn_opts); let pool = { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); rt.block_on(pool).unwrap() }; { let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); let m = Migrator::new(std::path::Path::new("./migrations")); let mut m = rt.block_on(m).unwrap(); let m = m.set_locking(false); rt.block_on(m.run(&pool)).unwrap(); tracing::info!("Ran migrations"); } pool } pub async fn session_layer(pool: SqlitePool) -> SessionManagerLayer { let store = SqliteStore::new(pool); store .migrate() .await .expect("Calling `migrate()` should be reliable, is the DB gone?"); SessionManagerLayer::new(store) .with_secure(true) .with_expiry(Expiry::OnInactivity(SESSION_TTL.into())) } pub async fn auth_layer( pool: SqlitePool, secret: &[u8], ) -> AuthManagerLayer> { todo!() } //-************************************************************************ // Tests for `db` module. //-************************************************************************ #[cfg(test)] mod tests { use tokio::runtime::Runtime; #[test] fn it_migrates_the_db() { let rt = Runtime::new().unwrap(); let db = super::get_db_pool(); rt.block_on(async { let r = sqlx::query("select count(*) from users") .fetch_one(&db) .await; assert!(r.is_ok()); }); } }