feat(api): add Leptos server functions bridging client and server
- 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>
This commit is contained in:
77
src/api/hosts.rs
Normal file
77
src/api/hosts.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
// api/hosts.rs — Server functions for hosts
|
||||
|
||||
use leptos::prelude::*;
|
||||
|
||||
use crate::models::Host;
|
||||
|
||||
// ─── Queries ──────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Returns all hosts belonging to a given network.
|
||||
#[server]
|
||||
pub async fn get_hosts_by_network(network_id: i64) -> Result<Vec<Host>, ServerFnError> {
|
||||
use sqlx::AnyPool;
|
||||
use crate::server::repository::hosts as repo;
|
||||
|
||||
let pool = use_context::<AnyPool>()
|
||||
.ok_or_else(|| ServerFnError::new("Database pool not found in context"))?;
|
||||
|
||||
repo::list_hosts_by_network(&pool, network_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))
|
||||
}
|
||||
|
||||
// ─── Mutations ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Creates a new host inside the specified network.
|
||||
///
|
||||
/// Validates that `ip` falls within the CIDR of `network_id`.
|
||||
/// Returns an error if the network does not exist or the IP is out of range.
|
||||
#[server]
|
||||
pub async fn create_host(
|
||||
name: String,
|
||||
ip: String,
|
||||
network_id: i64,
|
||||
) -> Result<Host, ServerFnError> {
|
||||
use sqlx::AnyPool;
|
||||
use crate::server::{
|
||||
repository::{hosts, networks},
|
||||
validation::validate_ip_in_network,
|
||||
};
|
||||
|
||||
let pool = use_context::<AnyPool>()
|
||||
.ok_or_else(|| ServerFnError::new("Database pool not found in context"))?;
|
||||
|
||||
// Look up the parent network to get its CIDR for IP validation.
|
||||
let network = networks::find_network(&pool, network_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))?
|
||||
.ok_or_else(|| {
|
||||
ServerFnError::new(format!("Network {network_id} not found"))
|
||||
})?;
|
||||
|
||||
// Enforce the business rule: a host's IP must belong to its network's CIDR.
|
||||
// This check runs on the server before any INSERT, so the database stays consistent.
|
||||
validate_ip_in_network(&ip, &network.cidr)
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))?;
|
||||
|
||||
hosts::create_host(&pool, &name, &ip, network_id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))
|
||||
}
|
||||
|
||||
/// Deletes a host by id.
|
||||
///
|
||||
/// Also removes all its port associations (via `ON DELETE CASCADE`).
|
||||
/// Returns `true` if the host existed and was deleted.
|
||||
#[server]
|
||||
pub async fn delete_host(id: i64) -> Result<bool, ServerFnError> {
|
||||
use sqlx::AnyPool;
|
||||
use crate::server::repository::hosts as repo;
|
||||
|
||||
let pool = use_context::<AnyPool>()
|
||||
.ok_or_else(|| ServerFnError::new("Database pool not found in context"))?;
|
||||
|
||||
repo::delete_host(&pool, id)
|
||||
.await
|
||||
.map_err(|e| ServerFnError::new(e.to_string()))
|
||||
}
|
||||
Reference in New Issue
Block a user