use std::str::FromStr; use axum::{ body::Body, extract::{DefaultBodyLimit, FromRef, Path, State}, http::Request, response::IntoResponse, routing::get, Extension, Router, }; use leptos::*; use leptos_axum::{generate_route_list, handle_server_fns_with_context, LeptosRoutes}; use leptos_router::RouteListing; use sqlx::{ sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous}, ConnectOptions, SqlitePool, }; use tower::Layer; use tower_http::{ compression::CompressionLayer, trace::{ DefaultMakeSpan, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, TraceLayer, }, CompressionLevel, }; use tracing::Level; use self::fileserv::file_and_error_handler; use crate::{ app::*, server::middleware::{LoggingLayer, LoggingService}, }; pub mod backends; mod fileserv; mod middleware; pub fn pool() -> Result { use_context::().ok_or_else(|| ServerFnError::ServerError("Pool missing.".into())) } #[derive(FromRef, Debug, Clone)] pub struct AppState { pub leptos_options: LeptosOptions, pub pool: SqlitePool, pub routes: Vec, } pub async fn server_fn_handler( State(app_state): State, path: Path, request: Request, ) -> impl IntoResponse { tracing::debug!("handling server_fn {:?}", path); handle_server_fns_with_context( move || { provide_context(app_state.pool.clone()); }, request, ) .await } pub async fn leptos_routes_handler( State(app_state): State, req: Request, ) -> impl IntoResponse { tracing::debug!("handling leptos_route: {:?}", req); let handler = leptos_axum::render_route_with_context( app_state.leptos_options.clone(), app_state.routes.clone(), move || { provide_context(app_state.pool.clone()); }, App, ); let response = handler(req).await; tracing::debug!("leptos_route response: {:?}", response); response } pub async fn app(leptos_options: LeptosOptions) -> Router { async fn new_pool() -> Result> { // TODO Save this in xdg_config_dir /data dir let db_options = SqliteConnectOptions::from_str("sqlite:db.sqlite3")? .create_if_missing(true) .optimize_on_close(true, None) .journal_mode(SqliteJournalMode::Wal) .synchronous(SqliteSynchronous::Normal) .disable_statement_logging() .busy_timeout(std::time::Duration::from_secs(16)) .statement_cache_capacity(512) .to_owned(); tracing::debug!( msg = "Connectiong to db", conn_str = ?db_options.to_url_lossy() ); let pool = SqlitePoolOptions::new().connect_with(db_options).await?; sqlx::migrate!() .run(&pool) .await .expect("could not run SQLx migrations"); Ok(pool) } let routes = generate_route_list(App); let pool = new_pool().await.expect("pool err"); // // TODO move this out of server(pool has to be moved out too) // FIXME: Should proxy_man move here ? // let task = run_starter_task(pool.clone()); // tokio::task::spawn(task); let app_state = AppState { leptos_options, // stream_registry: Arc::default(), pool: pool.clone(), routes: routes.clone(), }; const MAX_BODY_LIMIT: usize = 16 * 1024 * 1024; // 16 MB tracing::debug!("routes: {:?}", routes); // build our application with a route Router::new() .route( "/api/*fn_name", get(server_fn_handler).post(server_fn_handler), ) .leptos_routes_with_handler(routes, get(leptos_routes_handler)) .fallback(file_and_error_handler) .with_state(app_state) .layer(Extension(pool)) .layer( // Todo Readd gzip & deflate and add compress_when which skips them if response is a stream CompressionLayer::new() .no_gzip() .no_deflate() .quality(CompressionLevel::Fastest), ) .layer( TraceLayer::new_for_http() .make_span_with(DefaultMakeSpan::new().include_headers(true)) .on_request(DefaultOnRequest::new().level(Level::INFO)) .on_response(DefaultOnResponse::new().level(Level::INFO)) .on_eos(DefaultOnEos::new().level(Level::DEBUG)) .on_failure(DefaultOnFailure::new().level(Level::ERROR)), ) .layer(DefaultBodyLimit::max(MAX_BODY_LIMIT)) .layer(LoggingLayer) }