refactor: move db stuff into own module
							parent
							
								
									412f4cd2c7
								
							
						
					
					
						commit
						f96bf4d193
					
				|  | @ -1 +1,2 @@ | |||
| /target | ||||
| db.sqlite | ||||
|  |  | |||
|  | @ -0,0 +1,97 @@ | |||
| use std::{error::Error, fmt}; | ||||
| 
 | ||||
| use r2d2_sqlite::{ | ||||
|     rusqlite::{self, Row}, | ||||
|     SqliteConnectionManager, | ||||
| }; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| pub type DbPool = r2d2::Pool<SqliteConnectionManager>; | ||||
| 
 | ||||
| #[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<r2d2::Error> for DbError { | ||||
|     fn from(value: r2d2::Error) -> Self { | ||||
|         Self::Pool(value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl From<rusqlite::Error> for DbError { | ||||
|     fn from(value: rusqlite::Error) -> Self { | ||||
|         Self::Db(value) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Serialize)] | ||||
| pub struct Plant { | ||||
|     id: i32, | ||||
|     name: String, | ||||
|     species: String, | ||||
|     description: String, | ||||
| } | ||||
| 
 | ||||
| impl Plant { | ||||
|     pub fn from_row(row: &Row<'_>) -> Result<Self, rusqlite::Error> { | ||||
|         Ok(Self { | ||||
|             id: row.get(0)?, | ||||
|             name: row.get(1)?, | ||||
|             species: row.get(2)?, | ||||
|             description: row.get(3)?, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| pub struct NewPlant { | ||||
|     name: String, | ||||
|     species: String, | ||||
|     description: String, | ||||
| } | ||||
| 
 | ||||
| pub fn list_plants(pool: &DbPool) -> Result<Vec<Plant>, DbError> { | ||||
|     let conn = pool.get()?; | ||||
|     let mut stmt = conn.prepare("select * from plants")?; | ||||
| 
 | ||||
|     let mut plants = Vec::new(); | ||||
| 
 | ||||
|     for plant in stmt.query_map((), |row| Plant::from_row(row))? { | ||||
|         plants.push(plant?); | ||||
|     } | ||||
| 
 | ||||
|     Ok(plants) | ||||
| } | ||||
| 
 | ||||
| pub fn insert_plant(pool: &DbPool, plant: &NewPlant) -> Result<Plant, DbError> { | ||||
|     let conn = pool.get()?; | ||||
| 
 | ||||
|     let mut stmt = conn.prepare( | ||||
|         "insert into plants (name, species, description) values ($1, $2, $3) returning *", | ||||
|     )?; | ||||
|     Ok( | ||||
|         stmt.query_row((&plant.name, &plant.species, &plant.description), |row| { | ||||
|             Plant::from_row(row) | ||||
|         })?, | ||||
|     ) | ||||
| } | ||||
|  | @ -1,3 +1,4 @@ | |||
| mod db; | ||||
| mod server; | ||||
| 
 | ||||
| use std::sync::Arc; | ||||
|  | @ -6,8 +7,6 @@ use r2d2_sqlite::{rusqlite, SqliteConnectionManager}; | |||
| use tera::Tera; | ||||
| use tower_http::compression::CompressionLayer; | ||||
| 
 | ||||
| pub type DbPool = r2d2::Pool<SqliteConnectionManager>; | ||||
| 
 | ||||
| const MIGRATIONS: [&str; 2] = [ | ||||
|     include_str!("migrations/000_initial.sql"), | ||||
|     include_str!("migrations/001_plants.sql"), | ||||
|  | @ -19,7 +18,7 @@ const STATIC_FILES: [(&str, &'static str); 1] = [( | |||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct Context { | ||||
|     pool: crate::DbPool, | ||||
|     pool: db::DbPool, | ||||
|     tera: Arc<Tera>, | ||||
| } | ||||
| 
 | ||||
|  | @ -48,7 +47,7 @@ async fn main() { | |||
|         .unwrap(); | ||||
| } | ||||
| 
 | ||||
| fn run_migrations(pool: &DbPool) -> rusqlite::Result<()> { | ||||
| fn run_migrations(pool: &db::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
 | ||||
|  |  | |||
|  | @ -4,30 +4,9 @@ use axum::{ | |||
|     routing::{get, post}, | ||||
|     Form, Router, | ||||
| }; | ||||
| use r2d2_sqlite::rusqlite::{self, Row}; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use tera::Context; | ||||
| 
 | ||||
| #[derive(Serialize)] | ||||
| struct Plant { | ||||
|     id: i32, | ||||
|     name: String, | ||||
|     species: String, | ||||
|     description: String, | ||||
| } | ||||
| 
 | ||||
| impl TryFrom<&Row<'_>> for Plant { | ||||
|     type Error = rusqlite::Error; | ||||
| 
 | ||||
|     fn try_from(row: &Row<'_>) -> Result<Self, Self::Error> { | ||||
|         Ok(Self { | ||||
|             id: row.get(0)?, | ||||
|             name: row.get(1)?, | ||||
|             species: row.get(2)?, | ||||
|             description: row.get(3)?, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| use crate::db; | ||||
| 
 | ||||
| pub fn app(ctx: crate::Context) -> axum::Router { | ||||
|     let mut router = Router::new() | ||||
|  | @ -42,19 +21,9 @@ pub fn app(ctx: crate::Context) -> axum::Router { | |||
| } | ||||
| 
 | ||||
| async fn get_index(State(ctx): State<crate::Context>) -> Html<String> { | ||||
|     let plants = tokio::task::spawn_blocking(move || { | ||||
|         let conn = ctx.pool.get().unwrap(); | ||||
| 
 | ||||
|         let mut stmt = conn.prepare("select * from plants").unwrap(); | ||||
|         let mut plants = Vec::new(); | ||||
| 
 | ||||
|         for plant in stmt.query_map((), |row| Plant::try_from(row)).unwrap() { | ||||
|             plants.push(plant.unwrap()); | ||||
|         } | ||||
| 
 | ||||
|         plants | ||||
|     }) | ||||
|     let plants = tokio::task::spawn_blocking(move || db::list_plants(&ctx.pool)) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     let mut context = Context::new(); | ||||
|  | @ -62,34 +31,13 @@ async fn get_index(State(ctx): State<crate::Context>) -> Html<String> { | |||
|     Html(ctx.tera.render("index.html", &context).unwrap()) | ||||
| } | ||||
| 
 | ||||
| #[derive(Deserialize)] | ||||
| struct NewPlant { | ||||
|     name: String, | ||||
|     species: String, | ||||
|     description: String, | ||||
| } | ||||
| 
 | ||||
| async fn post_plants( | ||||
|     State(ctx): State<crate::Context>, | ||||
|     Form(plant): Form<NewPlant>, | ||||
|     Form(plant): Form<db::NewPlant>, | ||||
| ) -> Html<String> { | ||||
|     let plant = tokio::task::spawn_blocking(move || { | ||||
|         let conn = ctx.pool.get().unwrap(); | ||||
| 
 | ||||
|         let mut stmt = conn | ||||
|             .prepare( | ||||
|                 "insert into plants (name, species, description) values ($1, $2, $3) returning *", | ||||
|             ) | ||||
|             .unwrap(); | ||||
|         let plant = stmt | ||||
|             .query_row((&plant.name, &plant.species, &plant.description), |row| { | ||||
|                 Plant::try_from(row) | ||||
|             }) | ||||
|             .unwrap(); | ||||
| 
 | ||||
|         plant | ||||
|     }) | ||||
|     let plant = tokio::task::spawn_blocking(move || db::insert_plant(&ctx.pool, &plant)) | ||||
|         .await | ||||
|         .unwrap() | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     let mut context = Context::new(); | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue