mod captcha_solutions; mod config; mod errors; mod forms; mod logging; mod mailer; mod routes; mod states; mod templates; use anyhow::{Context, Result}; use axum::{ routing::{get, get_service, Router}, Server, }; use std::{env, net::SocketAddr, path::PathBuf, process}; use tower_http::services::ServeDir; use tracing::{error, info}; use crate::{config::Config, states::AppState}; const DATA_DIR_ENV_VAR: &str = "CF_DATA_DIR"; async fn init(logger_initialized: &mut bool) -> Result<()> { let data_dir = PathBuf::from( env::var(DATA_DIR_ENV_VAR) .with_context(|| format!("Environment variable {DATA_DIR_ENV_VAR} missing!"))?, ); let config = Config::build(&data_dir)?; logging::init_logger(&data_dir, &config.utc_offset)?; *logger_initialized = true; // The path prefix of all routes. let path_prefix = config.state_config.path_prefix.clone(); let socket_address = config .socket_address .parse::() .context("Failed to parse the socket address: {e:?}")?; let app_state = AppState::build(config).await?; // The service for serving the static files. let static_service = get_service(ServeDir::new("static")); let routes = Router::new() .route("/", get(routes::index).post(routes::submit)) .with_state(app_state) .nest_service("/static", static_service); let app = { if path_prefix.is_empty() { // No need to nest. routes } else { Router::new().nest(&path_prefix, routes) } }; info!("Starting server"); Server::bind(&socket_address) .serve(app.into_make_service()) .await?; Ok(()) } /// Single thread. #[tokio::main(flavor = "current_thread")] async fn main() { let mut logger_initialized = false; if let Err(e) = init(&mut logger_initialized).await { if logger_initialized { error!("{e:?}"); } else { eprintln!("{e:?}"); } process::exit(1); }; }