use anyhow::Result; use clap::Clap; use dotenv::dotenv; use http::StatusCode; use hyper::server::Server; use listenfd::ListenFd; use serde::{de::DeserializeOwned, Serialize}; use sqlx::postgres::PgPool; use std::convert::Infallible; use std::env; use tracing::info; use tracing_subscriber::fmt::format::FmtSpan; use url::Url; use warp::Filter; mod caches; mod db; mod handlers; #[macro_use] mod macros; mod models; mod problem; use caches::Caches; use models::interior_ref_list::InteriorRefList; use models::merchandise_list::MerchandiseList; use models::owner::Owner; use models::shop::Shop; use models::ListParams; #[derive(Clap)] #[clap(version = "0.1.0", author = "Tyler Hallada ")] struct Opts { #[clap(short, long)] migrate: bool, } #[derive(Debug, Clone)] pub struct Environment { pub db: PgPool, pub caches: Caches, pub api_url: Url, } impl Environment { async fn new(api_url: Url) -> Result { Ok(Environment { db: PgPool::builder() .max_size(5) .build(&env::var("DATABASE_URL")?) .await?, caches: Caches::initialize(), api_url, }) } } #[derive(Serialize)] struct ErrorMessage { code: u16, message: String, } fn with_env(env: Environment) -> impl Filter + Clone { warp::any().map(move || env.clone()) } fn json_body() -> impl Filter + Clone where T: Send + DeserializeOwned, { warp::body::content_length_limit(1024 * 64).and(warp::body::json()) } #[tokio::main] async fn main() -> Result<()> { dotenv().ok(); let env_log_filter = env::var("RUST_LOG").unwrap_or_else(|_| "warp=info,bazaar_realm_api=info".to_owned()); tracing_subscriber::fmt() .with_env_filter(env_log_filter) .with_span_events(FmtSpan::CLOSE) .init(); let opts: Opts = Opts::parse(); if opts.migrate { info!("going to migrate now!"); db::migrate().await; return Ok(()); } let host = env::var("HOST").expect("`HOST` environment variable not defined"); let host_url = Url::parse(&host).expect("Cannot parse URL from `HOST` environment variable"); let api_url = host_url.join("/v1/")?; let env = Environment::new(api_url).await?; let status_handler = warp::path::path("status") .and(warp::path::end()) .and(warp::get()) .map(|| StatusCode::OK); // TODO: return what api versions this server supports instead let get_owner_handler = warp::path("owners").and( warp::path::param() .and(warp::path::end()) .and(warp::get()) .and(with_env(env.clone())) .and_then(handlers::get_owner), ); let create_owner_handler = warp::path("owners").and( warp::path::end() .and(warp::post()) .and(json_body::()) .and(warp::addr::remote()) .and(warp::header::optional("api-key")) .and(warp::header::optional("x-real-ip")) .and(with_env(env.clone())) .and_then(handlers::create_owner), ); let delete_owner_handler = warp::path("owners").and( warp::path::param() .and(warp::path::end()) .and(warp::delete()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::delete_owner), ); let update_owner_handler = warp::path("owners").and( warp::path::param() .and(warp::path::end()) .and(warp::patch()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::update_owner), ); let list_owners_handler = warp::path("owners").and( warp::path::end() .and(warp::get()) .and(warp::query::()) .and(with_env(env.clone())) .and_then(handlers::list_owners), ); let get_shop_handler = warp::path("shops").and( warp::path::param() .and(warp::path::end()) .and(warp::get()) .and(with_env(env.clone())) .and_then(handlers::get_shop), ); let create_shop_handler = warp::path("shops").and( warp::path::end() .and(warp::post()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::create_shop), ); let delete_shop_handler = warp::path("shops").and( warp::path::param() .and(warp::path::end()) .and(warp::delete()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::delete_shop), ); let update_shop_handler = warp::path("shops").and( warp::path::param() .and(warp::patch()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::update_shop), ); let list_shops_handler = warp::path("shops").and( warp::path::end() .and(warp::get()) .and(warp::query::()) .and(with_env(env.clone())) .and_then(handlers::list_shops), ); let get_interior_ref_list_handler = warp::path("interior_ref_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::get()) .and(with_env(env.clone())) .and_then(handlers::get_interior_ref_list), ); let create_interior_ref_list_handler = warp::path("interior_ref_lists").and( warp::path::end() .and(warp::post()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::create_interior_ref_list), ); let delete_interior_ref_list_handler = warp::path("interior_ref_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::delete()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::delete_interior_ref_list), ); let update_interior_ref_list_handler = warp::path("interior_ref_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::patch()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::update_interior_ref_list), ); let list_interior_ref_lists_handler = warp::path("interior_ref_lists").and( warp::path::end() .and(warp::get()) .and(warp::query::()) .and(with_env(env.clone())) .and_then(handlers::list_interior_ref_lists), ); let get_interior_ref_list_by_shop_id_handler = warp::path("shops").and( warp::path::param() .and(warp::path("interior_ref_list")) .and(warp::path::end()) .and(warp::get()) .and(with_env(env.clone())) .and_then(handlers::get_interior_ref_list_by_shop_id), ); let get_merchandise_list_handler = warp::path("merchandise_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::get()) .and(with_env(env.clone())) .and_then(handlers::get_merchandise_list), ); let create_merchandise_list_handler = warp::path("merchandise_lists").and( warp::path::end() .and(warp::post()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::create_merchandise_list), ); let delete_merchandise_list_handler = warp::path("merchandise_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::delete()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::delete_merchandise_list), ); let update_merchandise_list_handler = warp::path("merchandise_lists").and( warp::path::param() .and(warp::path::end()) .and(warp::patch()) .and(json_body::()) .and(warp::header::optional("api-key")) .and(with_env(env.clone())) .and_then(handlers::update_merchandise_list), ); let list_merchandise_lists_handler = warp::path("merchandise_lists").and( warp::path::end() .and(warp::get()) .and(warp::query::()) .and(with_env(env.clone())) .and_then(handlers::list_merchandise_lists), ); let routes = warp::path("v1") .and(balanced_or_tree!( status_handler, get_owner_handler, delete_owner_handler, update_owner_handler, create_owner_handler, list_owners_handler, get_shop_handler, delete_shop_handler, update_shop_handler, create_shop_handler, list_shops_handler, get_interior_ref_list_by_shop_id_handler, get_interior_ref_list_handler, delete_interior_ref_list_handler, update_interior_ref_list_handler, create_interior_ref_list_handler, list_interior_ref_lists_handler, get_merchandise_list_handler, delete_merchandise_list_handler, update_merchandise_list_handler, create_merchandise_list_handler, list_merchandise_lists_handler, // warp::any().map(|| StatusCode::NOT_FOUND), )) .recover(problem::unpack_problem) .with(warp::compression::gzip()) .with(warp::trace::request()); let svc = warp::service(routes); let make_svc = hyper::service::make_service_fn(|_: _| { let svc = svc.clone(); async move { Ok::<_, Infallible>(svc) } }); let mut listenfd = ListenFd::from_env(); let server = if let Some(l) = listenfd.take_tcp_listener(0)? { Server::from_tcp(l)? } else { Server::bind(&([127, 0, 0, 1], 3030).into()) }; // warp::serve(routes).run(([127, 0, 0, 1], 3030)).await; server.serve(make_svc).await?; Ok(()) }