use std::sync::Arc; use anyhow::Result; use axum::{ extract::{FromRef, FromRequestParts}, http::request::Parts, }; use deadpool_diesel::postgres::{Connection, Pool}; use oauth2::basic::BasicClient; use crate::{ app_error::AppError, email::{Mailer, SmtpOptions}, sessions::PgStore, settings::Settings, }; /// Global app configuration pub struct App { pub db_pool: Pool, pub mailer: Mailer, pub reqwest_client: reqwest::Client, pub oauth_client: BasicClient, pub session_store: PgStore, pub settings: Settings, } impl App { /// Initialize global application functions based on config values pub async fn from_settings(settings: Settings) -> Result { let database_url = settings.database_url.clone(); let manager = deadpool_diesel::postgres::Manager::new(database_url, deadpool_diesel::Runtime::Tokio1); let db_pool = deadpool_diesel::postgres::Pool::builder(manager).build()?; let session_store = PgStore::new(db_pool.clone()); let reqwest_client = reqwest::ClientBuilder::new().https_only(true).build()?; let oauth_client = crate::auth::new_oauth_client(&settings)?; let mailer = if let Some(smtp_settings) = settings.email.smtp.clone() { Mailer::new_smtp(SmtpOptions { server: smtp_settings.server, username: smtp_settings.username, password: smtp_settings.password, })? } else if let Some(postmark_settings) = settings.email.postmark.clone() { Mailer::new_postmark(postmark_settings.server_token)? .with_reqwest_client(reqwest_client.clone()) } else { return Err(anyhow::anyhow!("no email backend settings configured")); }; Ok(Self { db_pool, mailer, oauth_client, reqwest_client, session_store, settings, }) } } /// Global app configuration, arced for relatively inexpensive clones pub type AppState = Arc; /// State extractor for shared reqwest client #[derive(Clone)] pub struct ReqwestClient(pub reqwest::Client); impl FromRef for ReqwestClient where S: Into + Clone, { fn from_ref(state: &S) -> Self { ReqwestClient(Into::::into(state.clone()).reqwest_client.clone()) } } /// Extractor to automatically obtain a Deadpool database connection pub struct DbConn(pub Connection); impl FromRequestParts for DbConn where S: Into + Clone + Sync, { type Rejection = AppError; async fn from_request_parts(_: &mut Parts, state: &S) -> Result { let conn = Into::::into(state.clone()).db_pool.get().await?; Ok(Self(conn)) } }