Files
zero2prod/src/startup.rs
2025-08-30 01:39:12 +02:00

99 lines
3.0 KiB
Rust

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<EmailClient>,
pub base_url: String,
pub hmac_secret: SecretString,
}
impl Application {
pub async fn build(configuration: Settings) -> Result<Self, std::io::Error> {
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::<MatchedPath>()
.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)
}