From 9ff6c9ec7ec77e511686195c13165c782ee6d4f7 Mon Sep 17 00:00:00 2001 From: rzuasti Date: Wed, 25 Feb 2026 15:54:19 -0500 Subject: [PATCH] List devices API working --- backend/Cargo.lock | 1 + backend/Cargo.toml | 1 + backend/src/main.rs | 1 + backend/src/model/devices.rs | 5 +- backend/src/utils.rs | 1 + backend/src/utils/date_serializer.rs | 20 +++++++ backend/src/web_server.rs | 85 ++++++++++++++++++++++++++-- 7 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 backend/src/utils.rs create mode 100644 backend/src/utils/date_serializer.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d00be38..7b2e857 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1548,6 +1548,7 @@ dependencies = [ "rusqlite", "rusqlite_migration", "serde", + "serde_derive", "serde_json", "tokio 1.49.0", "tower-http", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 25fb8d4..6d0c533 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,6 +9,7 @@ log = "0.4.29" pnet = "0.35.0" tokio = { version = "1.0", features = ["full"] } serde = { version = "1.0", features = ["derive"] } +serde_derive = "1.0" serde_json = "1.0" rusqlite = { version = "0.38.0", features = ["bundled", "chrono"] } rusqlite_migration = { version = "2.4.1", features = ["from-directory"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index a4bfa3d..d74250e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -9,6 +9,7 @@ mod mac_vendor_finder; mod model; mod scanner; mod settings; +mod utils; mod web_server; #[cfg(test)] diff --git a/backend/src/model/devices.rs b/backend/src/model/devices.rs index 81ebebb..c6ef6b5 100644 --- a/backend/src/model/devices.rs +++ b/backend/src/model/devices.rs @@ -1,4 +1,5 @@ -use chrono::{DateTime, Utc, serde::ts_seconds}; +use crate::utils::date_serializer; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -7,7 +8,7 @@ pub struct Device { pub mac_address: String, pub ipv4_address: String, pub vendor: String, - #[serde(with = "ts_seconds")] + #[serde(with = "date_serializer")] pub last_seen: DateTime, pub is_registered: bool, pub owner: String, diff --git a/backend/src/utils.rs b/backend/src/utils.rs new file mode 100644 index 0000000..21d9b0a --- /dev/null +++ b/backend/src/utils.rs @@ -0,0 +1 @@ +pub mod date_serializer; diff --git a/backend/src/utils/date_serializer.rs b/backend/src/utils/date_serializer.rs new file mode 100644 index 0000000..a4c7676 --- /dev/null +++ b/backend/src/utils/date_serializer.rs @@ -0,0 +1,20 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; + +fn time_to_json(t: DateTime) -> String { + t.to_rfc3339() +} + +pub fn serialize( + datetime: &DateTime, + serializer: S, +) -> Result { + time_to_json(datetime.clone()).serialize(serializer) +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let datetime: String = Deserialize::deserialize(deserializer)?; + Ok(DateTime::parse_from_str(&datetime, "%Y-%m-%d %H:%M:%S") + .map_err(D::Error::custom)? + .to_utc()) +} diff --git a/backend/src/web_server.rs b/backend/src/web_server.rs index 8c02564..2c488ef 100644 --- a/backend/src/web_server.rs +++ b/backend/src/web_server.rs @@ -1,7 +1,13 @@ -use std::error::Error; +use std::{collections::HashMap, error::Error}; -use axum::{Json, Router, extract::Path, http::StatusCode, routing::get}; -use log::{debug, error, info}; +use axum::{ + Json, Router, + extract::{Path, Query}, + http::StatusCode, + routing::get, +}; +use chrono::{DateTime, Utc}; +use log::{debug, error, info, warn}; use tower_http::services::ServeDir; use crate::{ @@ -15,7 +21,7 @@ pub async fn serve() -> Result<(), Box> { let router = Router::new() .route("/", get(|| async { "hello" })) - .route("/api/devices", get(get_devices)) + .route("/api/devices", get(list_devices)) .route("/api/notifications", get(list_notifications)) .route("/api/notifications/{id}", get(read_notification)) .route( @@ -33,8 +39,75 @@ pub async fn serve() -> Result<(), Box> { Ok(()) } -async fn get_devices() -> Result>, StatusCode> { - unimplemented!(); +fn parse_parameter_bool(params: &HashMap, name: &str) -> Option { + if params.contains_key(name) { + let param_value = params.get(name).unwrap().as_str(); + debug!("Found parameter {name} with value {}", param_value); + match param_value.parse::() { + Ok(value) => Some(value), + Err(_) => None, + } + } else { + None + } +} + +fn parse_parameter_string(params: &HashMap, name: &str) -> Option { + if params.contains_key(name) { + let param_value = params.get(name).unwrap().as_str(); + debug!("Found parameter {name} with value {}", param_value); + Some(param_value.to_string()) + } else { + None + } +} + +fn parse_parameter_date(params: &HashMap, name: &str) -> Option> { + if params.contains_key(name) { + let mut param_value = params.get(name).unwrap().to_ascii_uppercase(); + if !param_value.ends_with("Z") { + param_value.push('Z'); + } + debug!("Found parameter {name} with value {}", param_value); + match DateTime::parse_from_rfc3339(param_value.as_str()) { + Ok(value) => { + debug!("Date parsed to {}", value.to_utc().to_rfc3339()); + Some(value.to_utc()) + } + Err(err) => { + warn!("Error parsing date from parameters: {}", err); + None + } + } + } else { + None + } +} + +async fn list_devices( + Query(params): Query>, +) -> Result>, StatusCode> { + let is_registered: Option = parse_parameter_bool(¶ms, "is_registered"); + let last_seen_from: Option> = parse_parameter_date(¶ms, "last_seen_from"); + let last_seen_to: Option> = parse_parameter_date(¶ms, "last_seen_to"); + let owner: Option = parse_parameter_string(¶ms, "owner"); + let device_type: Option = parse_parameter_string(¶ms, "device_type"); + let vendor: Option = parse_parameter_string(¶ms, "vendor"); + + match db::devices::list_devices( + is_registered, + last_seen_from, + last_seen_to, + owner, + device_type, + vendor, + ) { + Ok(value) => Ok(Json(value)), + Err(err) => { + error!("Error listing devices: {}", err); + Err(StatusCode::INTERNAL_SERVER_ERROR) + } + } } async fn read_notification(Path(id): Path) -> Result, StatusCode> {