- Add src/api/ module with server functions for networks, hosts, applications - Each server function retrieves the pool via use_context::<AnyPool>() - Pool is injected via provide_context in two places in main.rs: * leptos_routes_with_context: for SSR renders and inline server fn calls * handle_server_fns_with_context on /api/*fn_name: for WASM client calls - create_host validates IP against network CIDR before inserting - create_network validates CIDR format before inserting Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
152 lines
6.0 KiB
Rust
152 lines
6.0 KiB
Rust
// main.rs — Axum server entry point
|
|
//
|
|
// This file is compiled ONLY when the "ssr" feature is enabled.
|
|
// `#[cfg(feature = "ssr")]` works like `#ifdef` in C:
|
|
// the guarded code does not exist in the WASM bundle.
|
|
//
|
|
// Run the server:
|
|
// cargo run --features ssr
|
|
//
|
|
// Run with verbose logs:
|
|
// RUST_LOG=debug cargo run --features ssr
|
|
|
|
#[cfg(feature = "ssr")]
|
|
#[tokio::main]
|
|
// `#[tokio::main]` turns the synchronous `fn main()` into an async function
|
|
// managed by the Tokio runtime. Without it, Rust cannot execute `async` code.
|
|
async fn main() {
|
|
use axum::Router;
|
|
use leptos::config::get_configuration;
|
|
use leptos::prelude::provide_context;
|
|
use leptos::view;
|
|
use leptos_axum::{
|
|
generate_route_list, handle_server_fns_with_context, LeptosRoutes,
|
|
};
|
|
use rust_ipam::{
|
|
app::{App, Shell},
|
|
server::{
|
|
config::AppConfig,
|
|
db::{create_pool, run_migrations},
|
|
routes::not_found_handler,
|
|
state::AppState,
|
|
},
|
|
};
|
|
use tower_http::services::ServeDir;
|
|
|
|
// Initialize structured logging.
|
|
// tracing::info!(), tracing::warn!(), etc. produce no output without this.
|
|
// RUST_LOG=debug cargo run --features ssr → enables debug-level logs
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()),
|
|
)
|
|
.init();
|
|
|
|
tracing::info!("Starting Rust IPAM server...");
|
|
|
|
// Load configuration from environment variables / .env file.
|
|
// The server cannot start without knowing which database to connect to,
|
|
// so we abort immediately on any configuration error.
|
|
let app_config = AppConfig::from_env()
|
|
.expect("Configuration error — check your .env file");
|
|
|
|
tracing::info!("Database: {} ({})", app_config.backend, app_config.database_url);
|
|
|
|
// Connect to the database and apply any pending migrations.
|
|
// The server cannot serve data without a working database, so we abort on error.
|
|
let pool = create_pool(&app_config)
|
|
.await
|
|
.expect("Failed to connect to database");
|
|
|
|
run_migrations(&pool, &app_config.backend)
|
|
.await
|
|
.expect("Database migration failed");
|
|
|
|
tracing::info!("Database ready.");
|
|
|
|
// `Some("Cargo.toml")` tells Leptos to read the [package.metadata.leptos]
|
|
// section from Cargo.toml (file paths, output names, server address...).
|
|
let conf = get_configuration(Some("Cargo.toml"))
|
|
.expect("Failed to load Leptos configuration from Cargo.toml");
|
|
let leptos_options = conf.leptos_options;
|
|
let addr = leptos_options.site_addr;
|
|
|
|
// Combine Leptos options and the database pool into a single shared state.
|
|
// `AppState` implements `FromRef<AppState> for LeptosOptions` so Leptos
|
|
// can still extract just what it needs from the full state.
|
|
let state = AppState { leptos_options: leptos_options.clone(), db: pool };
|
|
|
|
// Walk all `<Route>` components inside `App` to build the list of URLs
|
|
// that Leptos SSR must handle.
|
|
let routes = generate_route_list(App);
|
|
|
|
// Clone the pool so we can inject it into two different contexts:
|
|
// 1. `leptos_routes_with_context` — SSR rendering + server functions called during SSR
|
|
// 2. `handle_server_fns_with_context` — server functions called from the WASM client
|
|
let pool_for_routes = state.db.clone();
|
|
let pool_for_fns = state.db.clone();
|
|
|
|
// Build the Axum router using the builder pattern (method chaining).
|
|
//
|
|
// `Router::<AppState>::new()` explicitly tells Rust the state type is `AppState`.
|
|
// Without this annotation, type inference would default to `LeptosOptions`
|
|
// (inferred from `leptos_routes`) and then reject `.with_state(state: AppState)`.
|
|
let app = Router::<AppState>::new()
|
|
// Serve static files compiled by trunk (WASM, JS...).
|
|
// Trunk places them in target/site/pkg/ as configured in [package.metadata.leptos].
|
|
.nest_service("/pkg", ServeDir::new("target/site/pkg"))
|
|
// Handle server function HTTP calls from the WASM client.
|
|
//
|
|
// `#[server]` functions register themselves at "/api/<fn-name>".
|
|
// `handle_server_fns_with_context` runs the server function body and injects
|
|
// `additional_context` into the Leptos context before execution,
|
|
// so server functions can call `use_context::<AnyPool>()` to get the pool.
|
|
.route(
|
|
"/api/*fn_name",
|
|
axum::routing::post({
|
|
let pool = pool_for_fns;
|
|
move |req| {
|
|
let pool = pool.clone();
|
|
handle_server_fns_with_context(
|
|
move || provide_context(pool.clone()),
|
|
req,
|
|
)
|
|
}
|
|
}),
|
|
)
|
|
// Mount all Leptos routes into Axum.
|
|
// `leptos_routes_with_context` injects the pool into the Leptos context
|
|
// for every SSR render — needed for server functions called during SSR
|
|
// (e.g. when a `Resource` pre-fetches data on the server).
|
|
.leptos_routes_with_context(
|
|
&state,
|
|
routes,
|
|
{
|
|
move || provide_context(pool_for_routes.clone())
|
|
},
|
|
{
|
|
let leptos_options = state.leptos_options.clone();
|
|
move || view! { <Shell options=leptos_options.clone()/> }
|
|
},
|
|
)
|
|
.fallback(not_found_handler)
|
|
// Share AppState (Leptos options + DB pool) with all handlers.
|
|
.with_state(state);
|
|
|
|
let listener = tokio::net::TcpListener::bind(&addr)
|
|
.await
|
|
.expect(&format!("Failed to bind to address {}", addr));
|
|
|
|
tracing::info!("Server listening on http://{}", addr);
|
|
|
|
axum::serve(listener, app)
|
|
.await
|
|
.expect("Fatal server error");
|
|
}
|
|
|
|
// This empty block is required so the compiler finds a `fn main()`
|
|
// when building in WASM mode (where the "ssr" feature is not enabled).
|
|
// In WASM, the real entry point is `hydrate()` in lib.rs.
|
|
#[cfg(not(feature = "ssr"))]
|
|
fn main() {}
|