use crate::{configuration::Settings, email_client::EmailClient, routes::*}; use axum::{ Router, extract::MatchedPath, http::Request, routing::{get, post}, }; use axum_messages::MessagesManagerLayer; use secrecy::SecretString; use sqlx::{PgPool, postgres::PgPoolOptions}; use std::sync::Arc; use tokio::net::TcpListener; use tower_http::trace::TraceLayer; use tower_sessions::{MemoryStore, SessionManagerLayer}; use uuid::Uuid; pub struct Application { listener: TcpListener, router: Router, } #[derive(Clone)] pub struct AppState { pub connection_pool: PgPool, pub email_client: Arc, pub base_url: String, pub hmac_secret: SecretString, } impl Application { pub async fn build(configuration: Settings) -> Result { let address = format!( "{}:{}", configuration.application.host, configuration.application.port ); let listener = TcpListener::bind(address).await?; let connection_pool = PgPoolOptions::new().connect_lazy_with(configuration.database.with_db()); let email_client = EmailClient::new(configuration.email_client); let router = app( connection_pool, email_client, configuration.application.base_url, configuration.application.hmac_secret, ); Ok(Self { listener, router }) } pub async fn run_until_stopped(self) -> Result<(), std::io::Error> { tracing::debug!("listening on {}", self.listener.local_addr().unwrap()); axum::serve(self.listener, self.router).await } pub fn local_addr(&self) -> String { self.listener.local_addr().unwrap().to_string() } } pub fn app( connection_pool: PgPool, email_client: EmailClient, base_url: String, hmac_secret: SecretString, ) -> Router { let app_state = AppState { connection_pool, email_client: Arc::new(email_client), base_url, hmac_secret, }; Router::new() .route("/", get(home)) .route("/login", get(get_login).post(post_login)) .route("/health_check", get(health_check)) .route("/subscriptions", post(subscribe)) .route("/subscriptions/confirm", get(confirm)) .route("/newsletters", post(publish_newsletter)) .layer( TraceLayer::new_for_http().make_span_with(|request: &Request<_>| { let matched_path = request .extensions() .get::() .map(MatchedPath::as_str); let request_id = Uuid::new_v4().to_string(); tracing::info_span!( "http_request", method = ?request.method(), matched_path, request_id, some_other_field = tracing::field::Empty, ) }), ) .layer(MessagesManagerLayer) .layer(SessionManagerLayer::new(MemoryStore::default())) .with_state(app_state) }