mod account; mod auth; mod budget; mod user; use std::{sync::mpsc::Sender, time::Duration}; use axum::{ Router, extract::{MatchedPath, Request}, response::Response, }; use humantime::format_duration; use tokio::net::TcpListener; use tower_http::trace::TraceLayer; use tracing::{Span, error, info, info_span, warn}; use uuid::Uuid; use crate::{ db::DbPool, models::AppError, services::{CachePool, UserConfirmationMessage, config::Config}, }; #[derive(Clone)] pub struct AppState { db_pool: DbPool, cache_pool: CachePool, config: Config, mail_sender: Sender, } impl AppState { pub fn new( db_pool: DbPool, cache_pool: CachePool, config: Config, mail_sender: Sender, ) -> Self { Self { db_pool, cache_pool, config, mail_sender, } } pub fn db_pool(&self) -> &DbPool { &self.db_pool } pub fn cache_pool(&self) -> &CachePool { &self.cache_pool } pub fn config(&self) -> &Config { &self.config } pub fn mail_sender(&self) -> &Sender { &self.mail_sender } } pub async fn start_app( db_pool: DbPool, cache_pool: CachePool, config: Config, mail_sender: Sender, ) -> Result<(), AppError> { let connection_uri = config.app_connection_uri(); info!("Listening on {connection_uri}..."); let listener = TcpListener::bind(connection_uri).await.unwrap(); let logging_layer = TraceLayer::new_for_http() .make_span_with(|request: &Request| { let path = request .extensions() .get::() .map(MatchedPath::as_str).unwrap_or(request.uri().path()); info_span!("api_request", request_id = %Uuid::now_v7(), method = %request.method(), %path, status = tracing::field::Empty) }) .on_response(|response: &Response, duration: Duration, span: &Span| { let status = response.status(); span.record("status", status.to_string()); match status { w if w.is_redirection() => warn!(duration = ?format_duration(duration).to_string()), e if e.is_client_error() || e.is_server_error() => error!(duration = ?format_duration(duration).to_string()), _ => info!(duration = ?format_duration(duration).to_string()) } }); let state = AppState::new(db_pool, cache_pool, config, mail_sender); let app = Router::new() .merge(user::requests(state.clone())) .merge(auth::requests(state.clone())) .merge(account::requests(state.clone())) .merge(budget::requests(state.clone())) .layer(logging_layer); info!("API started successfully."); axum::serve(listener, app) .await .map_err(AppError::app_startup)?; Ok(()) }