Files
certifai/src/infrastructure/server.rs
T
sharang 208450e618
CI / Format (push) Successful in 2s
CI / Clippy (push) Successful in 2m48s
CI / Security Audit (push) Successful in 1m44s
CI / Tests (push) Successful in 4m11s
CI / Deploy (push) Successful in 4s
feat: use librechat instead of own chat (#14)
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com>
Reviewed-on: #14
2026-02-24 10:45:41 +00:00

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(())
})
}