feat(db): Added database setup and basic types (#5)
Co-authored-by: Sharang Parnerkar <parnerkarsharang@gmail.com> Reviewed-on: #5
This commit was merged in pull request #5.
This commit is contained in:
@@ -1,54 +1,94 @@
|
||||
use crate::infrastructure::{
|
||||
auth_callback, auth_login, logout, PendingOAuthStore, UserState, UserStateInner,
|
||||
};
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::Extension;
|
||||
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,
|
||||
/// and Keycloak OAuth routes.
|
||||
/// 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 or TCP listener fails to start.
|
||||
/// 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 {
|
||||
let state: UserState = UserStateInner {
|
||||
access_token: "abcd".into(),
|
||||
sub: "abcd".into(),
|
||||
refresh_token: "abcd".into(),
|
||||
..Default::default()
|
||||
// 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).
|
||||
// Strict would silently drop the cookie on that navigation.
|
||||
.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 are applied AFTER serve_dioxus_application so they
|
||||
// wrap both the custom Axum routes AND the Dioxus server
|
||||
// function routes (e.g. check_auth needs Session access).
|
||||
|
||||
// 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(state))
|
||||
.layer(Extension(server_state))
|
||||
.layer(middleware::from_fn(require_auth))
|
||||
.layer(session);
|
||||
|
||||
info!("Serving at {addr}");
|
||||
tracing::info!("Serving at {addr}");
|
||||
axum::serve(listener, router.into_make_service()).await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user