use rocket::fairing::{self, AdHoc}; use rocket::response::status::{Accepted, Created, NotFound}; use rocket::serde::json::Json; use rocket::{Build, Rocket}; use rocket_db_pools::{Connection, Database}; use rocket::futures::stream::TryStreamExt; use crate::place::Place; type Result> = std::result::Result; #[derive(Database)] #[database("db")] struct Db(rocket_db_pools::sqlx::SqlitePool); #[get("/places")] async fn get_places(mut db: Connection) -> Result>> { let places = rocket_db_pools::sqlx::query!( "SELECT id, name, address, open_hours, icon, description," + r#"longitude as "longitude: f64", latitude as "latitude: f64" FROM places WHERE active = TRUE"# ) .fetch(&mut *db) .map_ok(|p| Place { id: Some(p.id), name: p.name, address: p.address, open_hours: p.open_hours, icon: p.icon, description: p.description, latitude: p.latitude, longitude: p.longitude, }) .try_collect::>() .await?; Ok(Json(places)) } #[derive(Debug, Responder)] enum UpsertResponse { Created(Created>), Accepted(Accepted>), NotFound(NotFound>), } #[put("/places", format = "json", data = "")] async fn upsert_place(db: Connection, place: Json) -> Result { if place.id.is_some() { update_place(db, place).await } else { insert_place(db, place).await } } struct Id { id: i64, } async fn insert_place(mut db: Connection, mut place: Json) -> Result { let i = ::sqlx::query_as!( Id, "INSERT INTO places (name, address, open_hours, icon, description, longitude, latitude)\ VALUES (?, ?, ?, ?, ?, ?, ?)\ RETURNING id", place.name, place.address, place.open_hours, place.icon, place.description, place.longitude, place.latitude ) .fetch_one(&mut *db) .await?; place.id = Some(i.id); Ok(UpsertResponse::Created(Created::new("/places").body(place))) } async fn update_place(mut db: Connection, place: Json) -> Result { let result = ::sqlx::query!( "UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude) = (?, ?, ?, ?, ?, ?, ?) WHERE id = ?", place.name, place.address, place.open_hours, place.icon, place.description, place.longitude, place.latitude, place.id, ) .execute(&mut *db) .await?; if result.rows_affected() == 1 { Ok(UpsertResponse::Accepted(Accepted(Some(place)))) } else { Ok(UpsertResponse::NotFound(NotFound(place))) } } #[delete("/places/")] async fn delete_place(mut db: Connection, id: i64) -> Result> { let result = ::sqlx::query!("UPDATE places SET active = FALSE WHERE id = ?", id) .execute(&mut *db) .await?; Ok((result.rows_affected() == 1).then(|| ())) } async fn run_migrations(rocket: Rocket) -> fairing::Result { match Db::fetch(&rocket) { Some(db) => match ::sqlx::migrate!("./migrations").run(&**db).await { Ok(_) => Ok(rocket), Err(e) => { error!("Failed to initialize SQLx database: {}", e); Err(rocket) } }, None => Err(rocket), } } pub fn stage() -> AdHoc { AdHoc::on_ignite("SQLx Stage", |rocket| async { rocket .attach(Db::init()) .attach(AdHoc::try_on_ignite("SQLx Migrations", run_migrations)) .mount("/", routes![upsert_place, get_places, delete_place]) }) }