#[macro_use] extern crate rust_i18n; #[cfg(test)] #[macro_export] macro_rules! testdb { () => {{ let pool = SqlitePool::connect(":memory:").await.unwrap(); sqlx::query_file!("./migration.sql") .execute(&pool) .await .unwrap(); pool }}; } i18n!("locales", fallback = "de-AT"); use admin::station::Station; use auth::Backend; use axum::{body::Body, extract::FromRef, response::Response, routing::get, Router}; use axum_login::AuthManagerLayerBuilder; use partials::page; use sqlx::SqlitePool; use std::sync::Arc; use tokio::net::TcpListener; use tower_sessions::{cookie::time::Duration, Expiry, SessionManagerLayer}; use tower_sessions_sqlx_store_chrono::SqliteStore; pub(crate) mod admin; mod auth; pub(crate) mod models; mod partials; pub(crate) mod station; pub(crate) fn pl(amount: usize, single: &str, append: &str) -> String { if amount == 1 { single.into() } else { format!("{single}{append}") } } #[macro_export] macro_rules! err { ($session:expr, $fmt:expr $(, $arg:expr)*) => { $session .insert( "err", &format!($fmt $(, $arg)*) ) .await .unwrap() }; } #[macro_export] macro_rules! succ { ($session:expr, $fmt:expr $(, $arg:expr)*) => { $session .insert( "succ", &format!($fmt $(, $arg)*) ) .await .unwrap() }; } #[macro_export] macro_rules! suc { ($session:expr, $message:expr) => { $session.insert("succ", &$message).await.unwrap() }; } #[macro_export] macro_rules! er { ($session:expr, $message:expr) => { $session.insert("err", &$message).await.unwrap() }; } const PICO_CSS: &str = include_str!("../assets/pico.min.css"); const MY_CSS: &str = include_str!("../assets/style.css"); const LEAFLET_CSS: &str = include_str!("../assets/leaflet.css"); const LEAFLET_JS: &str = include_str!("../assets/leaflet.js"); const MARKER_PNG: &[u8] = include_bytes!("../assets/marker-icon.png"); async fn serve_pico_css() -> Response { Response::builder() .header("Content-Type", "text/css") .body(Body::from(PICO_CSS)) .unwrap() } async fn serve_my_css() -> Response { Response::builder() .header("Content-Type", "text/css") .body(Body::from(MY_CSS)) .unwrap() } async fn serve_leaflet_css() -> Response { Response::builder() .header("Content-Type", "text/css") .body(Body::from(LEAFLET_CSS)) .unwrap() } async fn serve_leaflet_js() -> Response { Response::builder() .header("Content-Type", "application/javascript") .body(Body::from(LEAFLET_JS)) .unwrap() } async fn serve_marker_png() -> Response { Response::builder() .header("Content-Type", "image/png") .body(Body::from(MARKER_PNG)) .unwrap() } #[derive(Clone)] struct AppState { db: Arc, } impl FromRef for Arc { fn from_ref(state: &AppState) -> Self { state.db.clone() } } fn router(db: SqlitePool) -> Router { let session_store = SqliteStore::new(db.clone()); let session_layer = SessionManagerLayer::new(session_store) .with_secure(false) .with_expiry(Expiry::OnInactivity(Duration::weeks(2))); let backend = Backend::new(db.clone()); let auth_layer = AuthManagerLayerBuilder::new(backend, session_layer).build(); let state = AppState { db: Arc::new(db) }; Router::new() .nest("/s/{id}/{code}", station::routes()) // TODO: maybe switch to "/" .nest("/admin", admin::routes()) .nest("/auth", auth::routes()) .route("/pico.css", get(serve_pico_css)) .route("/style.css", get(serve_my_css)) .route("/leaflet.css", get(serve_leaflet_css)) .route("/leaflet.js", get(serve_leaflet_js)) .route("/marker.png", get(serve_marker_png)) .with_state(state) .layer(auth_layer) } /// Starts the main application. pub async fn start(listener: TcpListener, db: SqlitePool) { let app = router(db); axum::serve(listener, app).await.unwrap(); }