stationslauf/src/lib.rs

165 lines
4.2 KiB
Rust

#[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<Body> {
Response::builder()
.header("Content-Type", "text/css")
.body(Body::from(PICO_CSS))
.unwrap()
}
async fn serve_my_css() -> Response<Body> {
Response::builder()
.header("Content-Type", "text/css")
.body(Body::from(MY_CSS))
.unwrap()
}
async fn serve_leaflet_css() -> Response<Body> {
Response::builder()
.header("Content-Type", "text/css")
.body(Body::from(LEAFLET_CSS))
.unwrap()
}
async fn serve_leaflet_js() -> Response<Body> {
Response::builder()
.header("Content-Type", "application/javascript")
.body(Body::from(LEAFLET_JS))
.unwrap()
}
async fn serve_marker_png() -> Response<Body> {
Response::builder()
.header("Content-Type", "image/png")
.body(Body::from(MARKER_PNG))
.unwrap()
}
#[derive(Clone)]
struct AppState {
db: Arc<SqlitePool>,
}
impl FromRef<AppState> for Arc<SqlitePool> {
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();
}