use std::sync::Arc; use axum::Router; use clap::Command; use database::get_connection; use sea_orm::ConnectOptions; use tracing::{debug, info}; use tracing_subscriber::fmt::format::{DefaultFields, Format}; use crate::{ cmd::CliCommand, configs::{ProgramSettings, get_program_settings, logging::LoggingSettings}, log, routes::{self, AppService, AppState}, services::{ auth::{ authentication::{AuthenticationServiceImpl, strategies::password::PasswordStrategy}, user::UserServiceImpl, }, server_state::ServerStateService, settings::SettingsService, }, tasks, }; pub fn get_cli_command() -> CliCommand { CliCommand { command: command(), action, } } fn command() -> Command { Command::new("start").about("Start the server") } fn action( _matches: &clap::ArgMatches, ) -> std::pin::Pin + Send>> { Box::pin(async move {}) } pub async fn start_server() { let settings = tracing::subscriber::with_default( log::make_temporary_subscriber(), || -> ProgramSettings { debug!("Temporary subscriber installed."); info!("Reading configuration..."); let settings = get_program_settings(); info!("Configuration read successfully."); debug!("Resetting global subscriber..."); let subscriber = get_global_tracing_subscriber_builder(&settings.logging).finish(); tracing::subscriber::set_global_default(subscriber) .expect("Failed to set global default subscriber"); debug!( "Global subscriber set with logging level: {:?}", settings.logging.level ); settings }, ); tasks::startup::run_startup_tasks(&settings) .await .inspect_err(|err| { tracing::error!("Failed to run startup tasks: {}", err); }) .expect("Failed to run startup tasks"); // setup database connection pool info!("Establishing database connection..."); debug!("Database URL: {}", settings.database.url); let db_options = |options: &mut ConnectOptions| { options.max_connections(settings.database.max_connections); }; let db_connection = Arc::new( get_connection(&settings.database.url, Some(db_options)) .await .expect("Failed to establish database connection"), ); info!("Database connection established."); // build the axum app and run the server... info!("Starting application..."); let mut app: Router = routes::get_root_router( Arc::new(get_app_state(&db_connection, &settings)), Arc::new(settings.server.cors.clone()), ); if settings.server.serve_openapi { info!("Enabling OpenAPI documentation endpoint at /openapi.json"); app = app.route( "/openapi.json", axum::routing::get(|| async { use utoipa::OpenApi; let doc = routes::ApiDoc::openapi(); doc.to_pretty_json() .expect("Failed to serialize OpenAPI doc to JSON") }), ); } let address = format!("{}:{}", settings.server.address, settings.server.port); info!("Starting server at http://{}", address); let listener = tokio::net::TcpListener::bind(address) .await .expect("Failed to bind to address"); axum::serve(listener, app) .await .expect("Failed to run the server"); } fn get_global_tracing_subscriber_builder( settings: &LoggingSettings, ) -> tracing_subscriber::fmt::SubscriberBuilder< DefaultFields, Format, > { // After configuration is read, install the global subscriber let builder = tracing_subscriber::fmt() .with_max_level(settings.level) .with_target(false) .with_level(true); if settings.utc { builder.with_timer(BoxedTimer(Box::new( tracing_subscriber::fmt::time::UtcTime::rfc_3339(), ))) } else { builder.with_timer(BoxedTimer(Box::new( tracing_subscriber::fmt::time::ChronoLocal::rfc_3339(), ))) } } fn get_app_state( db_connection: &Arc, settings: &ProgramSettings, ) -> AppState { AppState { database_connection: db_connection.clone(), config: Arc::new(settings.clone()), service: Arc::new(AppService { server_state: Arc::new(ServerStateService::new(db_connection.clone())), settings: Arc::new(SettingsService::new(db_connection.clone())), auth_state: routes::AuthState { strategy: routes::AuthStrategy { password: Arc::new(PasswordStrategy::new(db_connection.clone())), }, authentication: Arc::new(AuthenticationServiceImpl::new( settings.auth.jwt_secret.clone(), )), }, user: Arc::new(UserServiceImpl::new(db_connection.clone())), }), } } // A small wrapper that holds a boxed `FormatTime` trait object and itself // implements `FormatTime`, allowing us to use it as a concrete type with // `builder.with_timer` while still picking the concrete timer implementation // at runtime. // wrapper type to hold boxed timers and implement the `FormatTime` trait for // a concrete type so `with_timer` may be called once outside the conditional. struct BoxedTimer(Box); impl tracing_subscriber::fmt::time::FormatTime for BoxedTimer { fn format_time( &self, w: &mut tracing_subscriber::fmt::format::Writer<'_>, ) -> std::result::Result<(), std::fmt::Error> { self.0.format_time(w) } }