- Add server/repository/ module with networks, hosts, ports, applications - Use sqlx::query() + manual row mapping (no compile-time DB required) - Handle unique-constraint conflicts with is_unique_violation() for cross-database compatibility (SQLite + PostgreSQL via AnyPool) - add_port_to_host auto-registers the port in the catalog (prevents FK errors) - application_ports has no FK to ports (intentional: loose association) - Add DbError::NotFound variant for missing-record cases Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
80 lines
3.4 KiB
Rust
80 lines
3.4 KiB
Rust
// repository/networks.rs — CRUD for the `networks` table
|
|
|
|
use sqlx::{AnyPool, Row};
|
|
|
|
use crate::models::Network;
|
|
use crate::server::db::DbError;
|
|
|
|
// ─── Read ─────────────────────────────────────────────────────────────────────
|
|
|
|
/// Returns every network ordered by id.
|
|
pub async fn list_networks(pool: &AnyPool) -> Result<Vec<Network>, DbError> {
|
|
// `fetch_all` runs the query and collects every row into a Vec.
|
|
// It returns an error if the query fails; an empty table returns Ok(vec![]).
|
|
let rows = sqlx::query("SELECT id, cidr FROM networks ORDER BY id")
|
|
.fetch_all(pool)
|
|
.await?;
|
|
|
|
// `.iter().map(...).collect()` transforms each raw DB row into a Network struct.
|
|
Ok(rows.iter().map(row_to_network).collect())
|
|
}
|
|
|
|
/// Returns a single network by id, or `None` if it does not exist.
|
|
pub async fn find_network(pool: &AnyPool, id: i64) -> Result<Option<Network>, DbError> {
|
|
// `fetch_optional` returns `Ok(None)` when no row matches — unlike
|
|
// `fetch_one`, which returns an error when nothing is found.
|
|
let row = sqlx::query("SELECT id, cidr FROM networks WHERE id = $1")
|
|
.bind(id) // `$1` is replaced with the value of `id` at runtime
|
|
.fetch_optional(pool)
|
|
.await?;
|
|
|
|
// `Option::map` applies the conversion only if the row is Some.
|
|
Ok(row.as_ref().map(row_to_network))
|
|
}
|
|
|
|
// ─── Write ────────────────────────────────────────────────────────────────────
|
|
|
|
/// Inserts a new network and returns the created record (with its auto-generated id).
|
|
///
|
|
/// Fails with `DbError::Connection` if the CIDR is already registered
|
|
/// (the `cidr` column has a UNIQUE constraint).
|
|
///
|
|
/// `RETURNING id, cidr` reads back the inserted row in a single round-trip,
|
|
/// avoiding a separate SELECT after the INSERT.
|
|
/// Requires SQLite ≥ 3.35 (2021) and any PostgreSQL version.
|
|
pub async fn create_network(pool: &AnyPool, cidr: &str) -> Result<Network, DbError> {
|
|
let row = sqlx::query("INSERT INTO networks (cidr) VALUES ($1) RETURNING id, cidr")
|
|
.bind(cidr)
|
|
.fetch_one(pool) // exactly one row is returned by RETURNING
|
|
.await?;
|
|
|
|
Ok(row_to_network(&row))
|
|
}
|
|
|
|
/// Deletes a network by id and all its hosts (via `ON DELETE CASCADE`).
|
|
///
|
|
/// Returns `true` if a row was deleted, `false` if the id did not exist.
|
|
pub async fn delete_network(pool: &AnyPool, id: i64) -> Result<bool, DbError> {
|
|
// `execute` runs the query without fetching rows.
|
|
// `rows_affected()` tells us how many rows were actually deleted.
|
|
let result = sqlx::query("DELETE FROM networks WHERE id = $1")
|
|
.bind(id)
|
|
.execute(pool)
|
|
.await?;
|
|
|
|
Ok(result.rows_affected() > 0)
|
|
}
|
|
|
|
// ─── Row mapping ──────────────────────────────────────────────────────────────
|
|
|
|
/// Converts a raw database row into a `Network` struct.
|
|
///
|
|
/// `row.get("col")` extracts a typed value by column name.
|
|
/// The type must implement `sqlx::Decode` for the `Any` backend.
|
|
fn row_to_network(row: &sqlx::any::AnyRow) -> Network {
|
|
Network {
|
|
id: row.get("id"),
|
|
cidr: row.get("cidr"),
|
|
}
|
|
}
|