mod comment; mod event; mod plant; mod session; mod user; use r2d2_sqlite::{rusqlite, SqliteConnectionManager}; use std::{error::Error, fmt}; pub use comment::{Comment, NewComment}; pub use event::{Event, EventType, NewEvent, EVENT_TYPES}; pub use plant::{NewPlant, Plant}; pub use session::Session; pub use user::{NewUser, User}; pub type DbPool = r2d2::Pool; #[derive(Debug)] pub enum DbError { Pool(r2d2::Error), Db(rusqlite::Error), } impl fmt::Display for DbError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Pool(_) => write!(f, "failed to acquire connection from pool"), Self::Db(_) => write!(f, "error while accessing the database"), } } } impl Error for DbError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { Self::Pool(err) => Some(err), Self::Db(err) => Some(err), } } } impl From for DbError { fn from(value: r2d2::Error) -> Self { Self::Pool(value) } } impl From for DbError { fn from(value: rusqlite::Error) -> Self { Self::Db(value) } } pub fn run_migrations(pool: &DbPool, migrations: &[&str]) -> rusqlite::Result<()> { let mut conn = pool.get().unwrap(); // If the migration version query fails, we assume it's because the table isn't there yet so we // try to run the first migration let mut next_version = conn .query_row("select max(version) from migration_version", (), |row| { row.get::<_, usize>(0).map(|n| n + 1) }) .unwrap_or(0); while next_version < migrations.len() { let tx = conn.transaction()?; tx.execute(migrations[next_version], ())?; let cur_time = chrono::Local::now().timestamp(); tx.execute( "insert into migration_version values ($1, $2)", (next_version, cur_time), )?; tx.commit()?; tracing::info!("Applied migration {next_version}"); next_version += 1; } Ok(()) }