mod server; use std::sync::Arc; use r2d2_sqlite::{rusqlite, SqliteConnectionManager}; use tera::Tera; pub type DbPool = r2d2::Pool; const MIGRATIONS: [&str; 2] = [ include_str!("migrations/000_initial.sql"), include_str!("migrations/001_plants.sql"), ]; #[derive(Clone)] pub struct Context { pool: crate::DbPool, tera: Arc, } #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); let manager = SqliteConnectionManager::file("db.sqlite"); let pool = r2d2::Pool::new(manager).unwrap(); run_migrations(&pool).unwrap(); let tera = load_templates(); let ctx = Context { pool, tera: Arc::new(tera), }; let app = server::app(ctx); let address = "0.0.0.0:8000"; tracing::info!("Starting server on {address}"); let listener = tokio::net::TcpListener::bind(address).await.unwrap(); axum::serve(listener, app.into_make_service()) .await .unwrap(); } fn run_migrations(pool: &DbPool) -> 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(()) } fn load_templates() -> Tera { let mut tera = Tera::default(); tera.add_raw_templates(vec![ ("index.html", include_str!("templates/index.html")), ("plant_li.html", include_str!("templates/plant_li.html")), ]) .unwrap(); tera }