97 lines
3.8 KiB
Rust
97 lines
3.8 KiB
Rust
use dioxus::prelude::*;
|
|
|
|
use axum::routing::get;
|
|
use axum::{middleware, Extension};
|
|
use time::Duration;
|
|
use tower_sessions::{cookie::Key, MemoryStore, SessionManagerLayer};
|
|
|
|
use crate::infrastructure::{
|
|
auth_callback, auth_login,
|
|
config::{KeycloakConfig, LlmProvidersConfig, ServiceUrls, SmtpConfig, StripeConfig},
|
|
database::Database,
|
|
logout, require_auth,
|
|
server_state::{ServerState, ServerStateInner},
|
|
PendingOAuthStore,
|
|
};
|
|
|
|
/// Start the Axum server with Dioxus fullstack, session management,
|
|
/// MongoDB, and Keycloak OAuth routes.
|
|
///
|
|
/// Loads all configuration from environment variables once, connects
|
|
/// to MongoDB, and builds a [`ServerState`] shared across every request.
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `Error` if the tokio runtime, config loading, DB connection,
|
|
/// or TCP listener fails.
|
|
pub fn server_start(app: fn() -> Element) -> Result<(), super::Error> {
|
|
tokio::runtime::Runtime::new()?.block_on(async move {
|
|
// Load .env once at startup.
|
|
dotenvy::dotenv().ok();
|
|
|
|
// ---- Load and leak config structs for 'static lifetime ----
|
|
let keycloak: &'static KeycloakConfig = Box::leak(Box::new(KeycloakConfig::from_env()?));
|
|
let smtp: &'static SmtpConfig = Box::leak(Box::new(SmtpConfig::from_env()?));
|
|
let services: &'static ServiceUrls = Box::leak(Box::new(ServiceUrls::from_env()?));
|
|
let stripe: &'static StripeConfig = Box::leak(Box::new(StripeConfig::from_env()?));
|
|
let llm_providers: &'static LlmProvidersConfig =
|
|
Box::leak(Box::new(LlmProvidersConfig::from_env()?));
|
|
|
|
tracing::info!("Configuration loaded");
|
|
|
|
// ---- Connect to MongoDB ----
|
|
let mongo_uri =
|
|
std::env::var("MONGODB_URI").unwrap_or_else(|_| "mongodb://localhost:27017".into());
|
|
let mongo_db = std::env::var("MONGODB_DATABASE").unwrap_or_else(|_| "certifai".into());
|
|
|
|
let db = Database::connect(&mongo_uri, &mongo_db).await?;
|
|
tracing::info!("Connected to MongoDB (database: {mongo_db})");
|
|
|
|
// ---- Build ServerState ----
|
|
let server_state: ServerState = ServerStateInner {
|
|
db,
|
|
keycloak,
|
|
smtp,
|
|
services,
|
|
stripe,
|
|
llm_providers,
|
|
}
|
|
.into();
|
|
|
|
// ---- Session layer ----
|
|
let key = Key::generate();
|
|
let store = MemoryStore::default();
|
|
let session = SessionManagerLayer::new(store)
|
|
.with_secure(false)
|
|
// Lax is required so the browser sends the session cookie
|
|
// on the redirect back from Keycloak (cross-origin GET).
|
|
.with_same_site(tower_sessions::cookie::SameSite::Lax)
|
|
.with_expiry(tower_sessions::Expiry::OnInactivity(Duration::hours(24)))
|
|
.with_signed(key);
|
|
|
|
// ---- Build router ----
|
|
let addr = dioxus_cli_config::fullstack_address_or_localhost();
|
|
let listener = tokio::net::TcpListener::bind(addr).await?;
|
|
|
|
// Layers wrap in reverse order: session (outermost) -> auth
|
|
// middleware -> extensions -> route handlers. The session layer
|
|
// must be outermost so the `Session` extractor is available to
|
|
// the auth middleware, which gates all `/api/` server function
|
|
// routes (except `check-auth`).
|
|
let router = axum::Router::new()
|
|
.route("/auth", get(auth_login))
|
|
.route("/auth/callback", get(auth_callback))
|
|
.route("/logout", get(logout))
|
|
.serve_dioxus_application(ServeConfig::new(), app)
|
|
.layer(Extension(PendingOAuthStore::default()))
|
|
.layer(Extension(server_state))
|
|
.layer(middleware::from_fn(require_auth))
|
|
.layer(session);
|
|
|
|
tracing::info!("Serving at {addr}");
|
|
axum::serve(listener, router.into_make_service()).await?;
|
|
|
|
Ok(())
|
|
})
|
|
}
|