2025-05-02 23:48:54 -07:00
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
use axum::{
|
|
|
|
|
extract::{FromRef, FromRequestParts},
|
|
|
|
|
http::request::Parts,
|
|
|
|
|
};
|
|
|
|
|
use oauth2::basic::BasicClient;
|
|
|
|
|
|
2025-05-13 00:02:33 -07:00
|
|
|
use crate::{app_error::AppError, auth, nav::NavbarBuilder, sessions::PgStore, settings::Settings};
|
2025-05-02 23:48:54 -07:00
|
|
|
|
|
|
|
|
/// Global app configuration
|
|
|
|
|
pub struct App {
|
2025-05-13 00:02:33 -07:00
|
|
|
pub diesel_pool: deadpool_diesel::postgres::Pool,
|
2025-05-02 23:48:54 -07:00
|
|
|
pub navbar_template: NavbarBuilder,
|
|
|
|
|
pub oauth_client: BasicClient,
|
2025-05-13 00:02:33 -07:00
|
|
|
pub pg_pool: deadpool_postgres::Pool,
|
2025-05-02 23:48:54 -07:00
|
|
|
pub reqwest_client: reqwest::Client,
|
|
|
|
|
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<Self> {
|
|
|
|
|
let database_url = settings.database_url.clone();
|
2025-05-13 00:02:33 -07:00
|
|
|
let diesel_manager = deadpool_diesel::postgres::Manager::from_config(
|
|
|
|
|
database_url.clone(),
|
2025-05-02 23:48:54 -07:00
|
|
|
deadpool_diesel::Runtime::Tokio1,
|
|
|
|
|
deadpool_diesel::ManagerConfig {
|
|
|
|
|
// Reset role after each interaction is recycled so that user
|
|
|
|
|
// sessions remain isolated by deadpool interaction
|
|
|
|
|
recycling_method: deadpool_diesel::RecyclingMethod::CustomQuery(
|
2025-05-13 00:02:33 -07:00
|
|
|
std::borrow::Cow::Owned("RESET ROLE;".to_owned()),
|
2025-05-02 23:48:54 -07:00
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
);
|
2025-05-13 00:02:33 -07:00
|
|
|
let diesel_pool = deadpool_diesel::postgres::Pool::builder(diesel_manager).build()?;
|
|
|
|
|
|
|
|
|
|
let pg_config = deadpool_postgres::Config {
|
|
|
|
|
url: Some(database_url),
|
|
|
|
|
manager: Some(deadpool_postgres::ManagerConfig {
|
|
|
|
|
recycling_method: deadpool_postgres::RecyclingMethod::Clean,
|
|
|
|
|
}),
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
let pg_pool = pg_config.create_pool(
|
|
|
|
|
Some(deadpool_postgres::Runtime::Tokio1),
|
|
|
|
|
postgres_native_tls::MakeTlsConnector::new(native_tls::TlsConnector::new()?),
|
|
|
|
|
)?;
|
2025-05-02 23:48:54 -07:00
|
|
|
|
2025-05-13 00:02:33 -07:00
|
|
|
let session_store = PgStore::new(diesel_pool.clone());
|
2025-05-02 23:48:54 -07:00
|
|
|
let reqwest_client = reqwest::ClientBuilder::new().https_only(true).build()?;
|
|
|
|
|
let oauth_client = auth::new_oauth_client(&settings)?;
|
|
|
|
|
|
|
|
|
|
Ok(Self {
|
2025-05-13 00:02:33 -07:00
|
|
|
diesel_pool,
|
2025-05-02 23:48:54 -07:00
|
|
|
navbar_template: NavbarBuilder::default().with_base_path(&settings.base_path),
|
|
|
|
|
oauth_client,
|
2025-05-13 00:02:33 -07:00
|
|
|
pg_pool,
|
2025-05-02 23:48:54 -07:00
|
|
|
reqwest_client,
|
|
|
|
|
session_store,
|
|
|
|
|
settings,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Global app configuration, arced for relatively inexpensive clones
|
|
|
|
|
pub type AppState = Arc<App>;
|
|
|
|
|
|
|
|
|
|
/// State extractor for shared reqwest client
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct ReqwestClient(pub reqwest::Client);
|
|
|
|
|
|
|
|
|
|
impl<S> FromRef<S> for ReqwestClient
|
|
|
|
|
where
|
|
|
|
|
S: Into<AppState> + Clone,
|
|
|
|
|
{
|
|
|
|
|
fn from_ref(state: &S) -> Self {
|
|
|
|
|
ReqwestClient(Into::<AppState>::into(state.clone()).reqwest_client.clone())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-13 00:02:33 -07:00
|
|
|
/// Extractor to automatically obtain a Deadpool Diesel connection
|
|
|
|
|
pub struct DieselConn(pub deadpool_diesel::postgres::Connection);
|
|
|
|
|
|
|
|
|
|
impl<S> FromRequestParts<S> for DieselConn
|
|
|
|
|
where
|
|
|
|
|
S: Into<AppState> + Clone + Sync,
|
|
|
|
|
{
|
|
|
|
|
type Rejection = AppError;
|
|
|
|
|
|
|
|
|
|
async fn from_request_parts(_: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
|
|
|
let conn = Into::<AppState>::into(state.clone())
|
|
|
|
|
.diesel_pool
|
|
|
|
|
.get()
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(Self(conn))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Extractor to automatically obtain a Deadpool tokio-postgres connection
|
|
|
|
|
pub struct PgConn(pub deadpool_postgres::Object);
|
2025-05-02 23:48:54 -07:00
|
|
|
|
2025-05-13 00:02:33 -07:00
|
|
|
impl<S> FromRequestParts<S> for PgConn
|
2025-05-02 23:48:54 -07:00
|
|
|
where
|
|
|
|
|
S: Into<AppState> + Clone + Sync,
|
|
|
|
|
{
|
|
|
|
|
type Rejection = AppError;
|
|
|
|
|
|
|
|
|
|
async fn from_request_parts(_: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
2025-05-13 00:02:33 -07:00
|
|
|
let conn = Into::<AppState>::into(state.clone()).pg_pool.get().await?;
|
2025-05-02 23:48:54 -07:00
|
|
|
Ok(Self(conn))
|
|
|
|
|
}
|
|
|
|
|
}
|