//! Network repository operations. use crate::error::{DbError, Result}; use crate::models::{format_datetime, parse_datetime}; use chrono::Utc; use ipnet::IpNet; use nx9_wg_core::types::network::Network; use sqlx::{Row, SqlitePool}; use std::str::FromStr; use uuid::Uuid; /// Helper to convert a database row into a `Network` domain struct. fn row_to_network(r: &sqlx::sqlite::SqliteRow) -> Result { let id_str: String = r.try_get("id")?; let name: String = r.try_get("name")?; let cidr_str: String = r.try_get("cidr")?; let enabled_i64: i64 = r.try_get("enabled")?; let description: Option = r.try_get("description")?; let created_at_str: String = r.try_get("created_at")?; let updated_at_str: String = r.try_get("updated_at")?; let id = Uuid::parse_str(&id_str) .map_err(|e| DbError::Validation(format!("invalid network UUID '{id_str}': {e}")))?; let cidr = IpNet::from_str(&cidr_str) .map_err(|e| DbError::Validation(format!("invalid network CIDR '{cidr_str}': {e}")))?; Ok(Network { id, name, cidr, enabled: enabled_i64 != 0, description, created_at: parse_datetime(&created_at_str)?, updated_at: parse_datetime(&updated_at_str)?, }) } /// Create a new network record. pub async fn create_network(pool: &SqlitePool, net: &Network) -> Result<()> { let id_str = net.id.to_string(); let cidr_str = net.cidr.to_string(); let created_at_str = format_datetime(&net.created_at); let updated_at_str = format_datetime(&net.updated_at); sqlx::query( r#" INSERT INTO networks (id, name, cidr, enabled, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) "#, ) .bind(&id_str) .bind(&net.name) .bind(&cidr_str) .bind(if net.enabled { 1 } else { 0 }) .bind(&net.description) .bind(&created_at_str) .bind(&updated_at_str) .execute(pool) .await .map_err(|e| match &e { sqlx::Error::Database(dbe) if dbe.is_unique_violation() => { DbError::Conflict(format!("Network with name '{}' already exists", net.name)) } _ => DbError::Sqlx(e), })?; Ok(()) } /// Retrieve a network by UUID. pub async fn get_network(pool: &SqlitePool, id: Uuid) -> Result> { let id_str = id.to_string(); let row = sqlx::query("SELECT * FROM networks WHERE id = ?") .bind(&id_str) .fetch_optional(pool) .await .map_err(DbError::Sqlx)?; match row { Some(r) => Ok(Some(row_to_network(&r)?)), None => Ok(None), } } /// Retrieve a network by name. pub async fn get_network_by_name(pool: &SqlitePool, name: &str) -> Result> { let row = sqlx::query("SELECT * FROM networks WHERE name = ?") .bind(name) .fetch_optional(pool) .await .map_err(DbError::Sqlx)?; match row { Some(r) => Ok(Some(row_to_network(&r)?)), None => Ok(None), } } /// List all networks. pub async fn list_networks(pool: &SqlitePool) -> Result> { let rows = sqlx::query("SELECT * FROM networks ORDER BY name ASC") .fetch_all(pool) .await .map_err(DbError::Sqlx)?; let mut list = Vec::with_capacity(rows.len()); for r in rows { list.push(row_to_network(&r)?); } Ok(list) } /// Update a network record. pub async fn update_network(pool: &SqlitePool, net: &Network) -> Result<()> { let id_str = net.id.to_string(); let cidr_str = net.cidr.to_string(); let now = Utc::now().naive_utc(); let now_str = format_datetime(&now); let result = sqlx::query( r#" UPDATE networks SET name = ?, cidr = ?, enabled = ?, description = ?, updated_at = ? WHERE id = ? "#, ) .bind(&net.name) .bind(&cidr_str) .bind(if net.enabled { 1 } else { 0 }) .bind(&net.description) .bind(&now_str) .bind(&id_str) .execute(pool) .await .map_err(DbError::Sqlx)?; if result.rows_affected() == 0 { return Err(DbError::NotFound(format!("Network '{id_str}' not found"))); } Ok(()) } /// Delete a network by UUID. pub async fn delete_network(pool: &SqlitePool, id: Uuid) -> Result<()> { let id_str = id.to_string(); let result = sqlx::query("DELETE FROM networks WHERE id = ?") .bind(&id_str) .execute(pool) .await .map_err(DbError::Sqlx)?; if result.rows_affected() == 0 { return Err(DbError::NotFound(format!("Network '{id_str}' not found"))); } Ok(()) }