use std::{ env::VarError, io::Write, net::{Ipv4Addr, SocketAddr}, }; use axum::{ routing::{get, MethodRouter}, Router, }; use tokio::net::TcpListener; use tower_http::services::ServeDir; use tower_sessions::{Expiry, SessionManagerLayer}; use tower_sessions_sqlx_store::{sqlx::SqlitePool, SqliteStore}; #[macro_use] extern crate justerror; #[macro_use] extern crate lazy_static; mod handlers; mod templates; mod user; #[tokio::main] async fn main() { use handlers::handlers::{get_signup, payment_success, post_signup}; init(); // for javascript and css // TODO: figure out how to intern these contents let assets_dir = std::env::current_dir().unwrap().join("assets"); let assets_svc = ServeDir::new(assets_dir.as_path()); // just for signups let pool = SqlitePool::connect("sqlite:memory:").await.unwrap(); let session_store = SqliteStore::new(pool); session_store.migrate().await.unwrap(); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_same_site(tower_sessions::cookie::SameSite::Lax) .with_expiry(Expiry::OnInactivity(time::Duration::hours(2))); // the core application, defining the routes and handlers let app = Router::new() .nest_service("/assets", assets_svc) .stripped_clone("/signup/", get(get_signup).post(post_signup)) .stripped_clone("/payment_success/", get(payment_success)) .route("/payment_success/:receipt", get(payment_success)) .layer(session_layer) .into_make_service(); let listener = mklistener().await; axum::serve(listener, app).await.unwrap(); } //-************************************************************************ // li'l helpers //-************************************************************************ fn init() { dotenvy::dotenv().expect("Could not read .env file."); env_logger::builder() .format(|buf, record| { let ts = buf.timestamp(); writeln!(buf, "{}: {}", ts, record.args()) }) .init(); } async fn mklistener() -> TcpListener { let ip = std::env::var("LISTENING_ADDR").expect("Could not find $LISTENING_ADDR in environment"); let ip: Ipv4Addr = ip .parse() .unwrap_or_else(|_| panic!("Could not parse {ip} as an IP address")); let port: u16 = std::env::var("LISTENING_PORT") .and_then(|p| p.parse().map_err(|_| VarError::NotPresent)) .unwrap_or_else(|_| { panic!("Could not find LISTENING_PORT in env or parse if present"); }); let addr = SocketAddr::from((ip, port)); TcpListener::bind(&addr).await.unwrap() } /// Adds both routes, with and without a trailing slash. trait RouterPathStrip where S: Clone + Send + Sync + 'static, { fn stripped_clone(self, path: &str, method_router: MethodRouter) -> Self; } impl RouterPathStrip for Router where S: Clone + Send + Sync + 'static, { fn stripped_clone(self, path: &str, method_router: MethodRouter) -> Self { assert!(path.ends_with('/')); self.route(path, method_router.clone()) .route(path.trim_end_matches('/'), method_router) } }