#[cfg(feature = "ssr")] #[tokio::main] async fn main() -> anyhow::Result<()> { use axum::{ body::Body, extract::State, http::Request, response::IntoResponse, routing::{any, get}, Router, }; use cnats::app::{shell, App}; use cnats::server::{oidc, sse, store, AppState}; use leptos::prelude::*; use leptos_axum::{generate_route_list, LeptosRoutes}; use std::sync::Arc; use tower_sessions::{MemoryStore, SessionManagerLayer}; dotenvy::dotenv().ok(); tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "info,cnats=debug".into()), ) .init(); let conf = get_configuration(None)?; let leptos_options = conf.leptos_options; let addr = leptos_options.site_addr; let routes = generate_route_list(App); let nats_url = std::env::var("NATS_URL").unwrap_or_else(|_| "nats://127.0.0.1:4222".to_string()); tracing::info!(%nats_url, "connecting to NATS"); // async-nats does not honor userinfo embedded in the URL, so pass any // credentials explicitly via ConnectOptions. let parsed = url::Url::parse(&nats_url)?; let mut nats_opts = async_nats::ConnectOptions::new(); if !parsed.username().is_empty() { nats_opts = nats_opts.user_and_password( parsed.username().to_string(), parsed.password().unwrap_or_default().to_string(), ); } let nats = nats_opts.connect(&nats_url).await?; let database_url = std::env::var("DATABASE_URL") .unwrap_or_else(|_| "postgres://cnats:cnats@127.0.0.1:5432/cnats".to_string()); tracing::info!("connecting to postgres"); let pool = sqlx::postgres::PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await?; store::init_schema(&pool).await?; // Archive chat.room.* into Postgres via a durable JetStream consumer. tokio::spawn(store::run_consumer(nats.clone(), pool.clone())); let oidc_state = Arc::new(oidc::Oidc::from_env().await?); let state = AppState { leptos_options: leptos_options.clone(), nats, oidc: oidc_state, pool, }; // Dev-friendly defaults: in-memory sessions, secure cookies only when // COOKIE_SECURE=true (set it behind TLS in production). let cookie_secure = std::env::var("COOKIE_SECURE") .map(|v| v == "true" || v == "1") .unwrap_or(false); let session_layer = SessionManagerLayer::new(MemoryStore::default()) .with_secure(cookie_secure) .with_name("cnats_session"); async fn server_fn_handler( State(state): State, request: Request, ) -> impl IntoResponse { leptos_axum::handle_server_fns_with_context( move || provide_context(state.clone()), request, ) .await } let app = Router::new() .route("/auth/login", get(oidc::login)) .route("/auth/callback", get(oidc::callback)) .route("/auth/logout", get(oidc::logout)) .route("/sse/{room}", get(sse::room_events)) .route("/call-sse/{room}", get(sse::call_events)) .route("/api/{*fn_name}", any(server_fn_handler)) .leptos_routes_with_context( &state, routes, { let state = state.clone(); move || provide_context(state.clone()) }, { let leptos_options = leptos_options.clone(); move || shell(leptos_options.clone()) }, ) .fallback(leptos_axum::file_and_error_handler::(shell)) .layer(session_layer) .with_state(state); tracing::info!("listening on http://{addr}"); let listener = tokio::net::TcpListener::bind(&addr).await?; axum::serve(listener, app.into_make_service()).await?; Ok(()) } #[cfg(not(feature = "ssr"))] fn main() { // The browser build is a cdylib; this stub only exists so `cargo check` // without features still succeeds. }