Migrate from Rocket to Axum (#17)
fixes #15 Co-authored-by: Felipe Contreras Salinas <felipe@bstr.cl> Reviewed-on: #17
This commit is contained in:
parent
20be3a1a1e
commit
ddd76dd84f
6 changed files with 643 additions and 954 deletions
1411
Cargo.lock
generated
1411
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
|
@ -8,13 +8,17 @@ links = "sqlite"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = {version = "0.5.0-rc.2", features = ["json"]}
|
||||
|
||||
[dependencies.rocket_db_pools]
|
||||
version = "0.1.0-rc.2"
|
||||
features = ["sqlx_sqlite"]
|
||||
axum = "0.6.12"
|
||||
tracing = "0.1.37"
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
tokio = { version = "1.27.0", features = ["macros", "signal"] }
|
||||
dotenvy = "0.15.7"
|
||||
tower-http = { version = "0.4.0", features = ["fs"] }
|
||||
serde = { version = "1.0.159", features = ["derive"] }
|
||||
serde_json = "1.0.95"
|
||||
futures = "0.3.28"
|
||||
|
||||
[dependencies.sqlx]
|
||||
version = "0.5.13"
|
||||
version = "0.6.3"
|
||||
default-features = false
|
||||
features = ["macros", "offline", "migrate", "sqlite"]
|
||||
features = ["runtime-tokio-native-tls", "macros", "offline", "migrate", "sqlite"]
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
[default.databases.db]
|
||||
url = "sqlite://db/huellas.db"
|
||||
[release]
|
||||
address = "0.0.0.0"
|
||||
54
src/main.rs
54
src/main.rs
|
|
@ -1,13 +1,51 @@
|
|||
#[macro_use]
|
||||
extern crate rocket;
|
||||
use rocket::fs::FileServer;
|
||||
use axum::Router;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod place;
|
||||
mod routes;
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.mount("/", FileServer::from("static"))
|
||||
.attach(routes::stage())
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
dotenvy::dotenv().unwrap_or_default();
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "huellas=debug".into()),
|
||||
)
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.init();
|
||||
|
||||
let db_url = dotenvy::var("DATABASE_URL").expect("DATABASE_URL not defined");
|
||||
let pool = SqlitePool::connect(&db_url)
|
||||
.await
|
||||
.expect("can't connect to database");
|
||||
|
||||
sqlx::migrate!()
|
||||
.run(&pool)
|
||||
.await
|
||||
.expect("couldn't run migrations");
|
||||
|
||||
let app = Router::new()
|
||||
.nest("/places", routes::places_routes(pool))
|
||||
.nest_service("/", ServeDir::new("static"));
|
||||
|
||||
let port = dotenvy::var("PORT").unwrap_or_default();
|
||||
let port = str::parse(&port).unwrap_or(3000);
|
||||
let address = SocketAddr::from(([0, 0, 0, 0], port));
|
||||
tracing::debug!("listening on {}", address);
|
||||
axum::Server::bind(&address)
|
||||
.serve(app.into_make_service())
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
tokio::signal::ctrl_c()
|
||||
.await
|
||||
.expect("Ctrl-C shutdown signal");
|
||||
println!("Received shutdown signal");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use rocket::serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct Place {
|
||||
pub id: Option<i64>,
|
||||
pub name: String,
|
||||
|
|
|
|||
107
src/routes.rs
107
src/routes.rs
|
|
@ -1,25 +1,28 @@
|
|||
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 axum::extract::{Json, Path, State};
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{delete, get};
|
||||
use axum::Router;
|
||||
use futures::TryStreamExt;
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
|
||||
use rocket::futures::stream::TryStreamExt;
|
||||
// use rocket::fairing::{self, AdHoc};
|
||||
|
||||
use crate::place::Place;
|
||||
type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
|
||||
type Result<T, E = (StatusCode, String)> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Database)]
|
||||
#[database("db")]
|
||||
struct Db(rocket_db_pools::sqlx::SqlitePool);
|
||||
fn internal_error<E>(err: E) -> (StatusCode, String)
|
||||
where
|
||||
E: std::error::Error,
|
||||
{
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, err.to_string())
|
||||
}
|
||||
|
||||
#[get("/places")]
|
||||
async fn get_places(mut db: Connection<Db>) -> Result<Json<Vec<Place>>> {
|
||||
let places = rocket_db_pools::sqlx::query!(
|
||||
async fn get_places(State(pool): State<SqlitePool>) -> Result<Json<Vec<Place>>> {
|
||||
let places = ::sqlx::query!(
|
||||
"SELECT id, name, address, open_hours, icon, description, url," +
|
||||
r#"longitude as "longitude: f64", latitude as "latitude: f64" FROM places WHERE active = TRUE"#
|
||||
)
|
||||
.fetch(&mut *db)
|
||||
.fetch(&pool)
|
||||
.map_ok(|p| Place {
|
||||
id: Some(p.id),
|
||||
name: p.name,
|
||||
|
|
@ -32,24 +35,19 @@ async fn get_places(mut db: Connection<Db>) -> Result<Json<Vec<Place>>> {
|
|||
url: p.url,
|
||||
})
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?;
|
||||
.await.map_err(internal_error)?;
|
||||
|
||||
Ok(Json(places))
|
||||
}
|
||||
|
||||
#[derive(Debug, Responder)]
|
||||
enum UpsertResponse {
|
||||
Created(Created<Json<Place>>),
|
||||
Accepted(Accepted<Json<Place>>),
|
||||
NotFound(NotFound<Json<Place>>),
|
||||
}
|
||||
|
||||
#[put("/places", format = "json", data = "<place>")]
|
||||
async fn upsert_place(db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||
async fn upsert_place(
|
||||
State(pool): State<SqlitePool>,
|
||||
Json(place): Json<Place>,
|
||||
) -> Result<Json<Place>> {
|
||||
if place.id.is_some() {
|
||||
update_place(db, place).await
|
||||
update_place(pool, place).await
|
||||
} else {
|
||||
insert_place(db, place).await
|
||||
insert_place(pool, place).await
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +55,7 @@ struct Id {
|
|||
id: i64,
|
||||
}
|
||||
|
||||
async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<UpsertResponse> {
|
||||
async fn insert_place(pool: SqlitePool, mut place: Place) -> Result<Json<Place>> {
|
||||
let i = ::sqlx::query_as!(
|
||||
Id,
|
||||
"INSERT INTO places (name, address, open_hours, icon, description, longitude, latitude, url)\
|
||||
|
|
@ -72,14 +70,15 @@ async fn insert_place(mut db: Connection<Db>, mut place: Json<Place>) -> Result<
|
|||
place.latitude,
|
||||
place.url
|
||||
)
|
||||
.fetch_one(&mut *db)
|
||||
.await?;
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.map_err(internal_error)?;
|
||||
|
||||
place.id = Some(i.id);
|
||||
Ok(UpsertResponse::Created(Created::new("/places").body(place)))
|
||||
Ok(Json(place))
|
||||
}
|
||||
|
||||
async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<UpsertResponse> {
|
||||
async fn update_place(pool: SqlitePool, place: Place) -> Result<Json<Place>> {
|
||||
let result = ::sqlx::query!(
|
||||
"UPDATE places SET (name, address, open_hours, icon, description, longitude, latitude, url) = (?, ?, ?, ?, ?, ?, ?, ?) WHERE id = ?",
|
||||
place.name,
|
||||
|
|
@ -92,43 +91,33 @@ async fn update_place(mut db: Connection<Db>, place: Json<Place>) -> Result<Upse
|
|||
place.url,
|
||||
place.id,
|
||||
)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(internal_error)?;
|
||||
|
||||
if result.rows_affected() == 1 {
|
||||
Ok(UpsertResponse::Accepted(Accepted(Some(place))))
|
||||
Ok(Json(place))
|
||||
} else {
|
||||
Ok(UpsertResponse::NotFound(NotFound(place)))
|
||||
Err((StatusCode::NOT_FOUND, "".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/places/<id>")]
|
||||
async fn delete_place(mut db: Connection<Db>, id: i64) -> Result<Option<()>> {
|
||||
async fn delete_place(State(pool): State<SqlitePool>, Path(id): Path<i64>) -> Result<()> {
|
||||
let result = ::sqlx::query!("UPDATE places SET active = FALSE WHERE id = ?", id)
|
||||
.execute(&mut *db)
|
||||
.await?;
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(internal_error)?;
|
||||
|
||||
Ok((result.rows_affected() == 1).then_some(()))
|
||||
}
|
||||
|
||||
async fn run_migrations(rocket: Rocket<Build>) -> 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),
|
||||
if result.rows_affected() == 1 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err((StatusCode::NOT_FOUND, "".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
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])
|
||||
})
|
||||
pub fn places_routes(pool: SqlitePool) -> Router {
|
||||
Router::new()
|
||||
.route("/", get(get_places).put(upsert_place))
|
||||
.route("/:id", delete(delete_place))
|
||||
.with_state(pool)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue