use dioxus::{ cli_config, fullstack::axum::{self, Router, middleware::from_fn}, prelude::{DioxusRouterExt, ServeConfig}, server::axum::Extension, }; use tokio::net::TcpListener; use tower_http::services::ServeFile; use crate::App; use crate::app::LOGO_ICO; use crate::server::{ auth::build_auth_layer, config::Config, database, key_val_store, require_auth_mw::require_auth_middleware, }; use crate::util::error::{Contextualize, Error, ErrorType, Result}; pub async fn main() -> Result { if let Err(e) = dotenvy::dotenv() { tracing::warn!("Error reading .env: {e}"); } tracing::debug!("Loading configuration..."); let config = Config::from_env() .map_err(|e| Error::message_here(e.to_string())) .err_context("Failed to load config")?; tracing::debug!("Loaded configuration: {config:#?}"); // Dioxus doesn't expose a way to configure the public path other than this environment // variable, and also doesn't provide a way to read what the public path is. As a workaround we // expose a config option and set this variable that Dioxus expects. // SAFETY: "This function is safe to call in a single-threaded program." unsafe { std::env::set_var("DIOXUS_PUBLIC_PATH", config.server.public_path.clone()); } let db_pool = database::setup(config.database.connection_uri()) .await .err_context("Failed database setup")?; let key_val_pool = key_val_store::setup(&config.key_val_store.connection_uri()) .await .err_context("Failed key-value store setup")?; let favicon_path = { // Resolve the favicon path let asset_path = LOGO_ICO.resolve(); // If the asset path starts with "/", strip it. Otherwise it behaves as an "absolute path" // and replaces the base path in the join operation. This is necessary because Dioxus will // produce a path like "/assets/" in the call to `resolve` let asset_path_rel = asset_path.strip_prefix("/").unwrap_or(&asset_path); config.server.public_path.join(asset_path_rel) }; tracing::debug!("Favicon path: {}", favicon_path.display()); let auth_layer = build_auth_layer(db_pool.clone(), key_val_pool, config.auth.cookies_secure); #[cfg(debug_assertions)] if let Some(dx_port) = cli_config::server_port() && dx_port != config.server.port { tracing::warn!( "Your configured server port ({}) doesn't match the one specified from environment \ variables set by the Dioxus CLI ({dx_port}). If you are intending to use the dx tool, \ please do not specify a port in the configuration.", config.server.port ); } let addr = config.server.serve_addr(); tracing::info!("Setup complete, building router..."); let router = Router::new() .serve_dioxus_application(ServeConfig::new(), App) .layer(from_fn(require_auth_middleware)) .layer(Extension(config)) .layer(Extension(db_pool)) .layer(auth_layer) .nest_service("/favicon.ico", ServeFile::new(favicon_path)); tracing::info!("Listening on {addr}..."); let listener = TcpListener::bind(addr) .await .map_err(|e| ErrorType::HttpServer(e.to_string())) .err_context(format!("Failed to bind to {addr}"))?; axum::serve(listener, router) .await .map_err(|e| ErrorType::HttpServer(e.to_string())) .err_context("HTTP server error")?; Err(Error::new_here(ErrorType::HttpServer( "axum::serve should never return".to_owned(), ))) }