use std::collections::HashMap; use axum::extract::Path; use axum::response::IntoResponse; use axum::{Json, extract::Query, http::StatusCode}; use chrono::{DateTime, Utc}; use log::{debug, error}; use serde::Deserialize; use utoipa::ToSchema; use crate::{ db, model::devices::{Device, DeviceSummary}, }; use crate::web_server::utils; #[utoipa::path( get, path = "/api/devices", tag = "devices", params( ("is_registered" = Option, Query, description = "Filter by registration status"), ("last_seen_from" = Option, Query, description = "Filter devices seen after this datetime (RFC3339)"), ("last_seen_to" = Option, Query, description = "Filter devices seen before this datetime (RFC3339)"), ("owner" = Option, Query, description = "Filter by owner"), ("device_type" = Option, Query, description = "Filter by device type"), ("vendor" = Option, Query, description = "Filter by vendor"), ("page_offset" = Option, Query, description = "Pagination offset"), ("page_limit" = Option, Query, description = "Maximum number of results to return"), ), responses( (status = 200, description = "List of devices", body = Vec), (status = 500, description = "Internal server error"), ), security(("bearer_auth" = [])) )] pub async fn list( Query(params): Query>, ) -> Result>, StatusCode> { let is_registered: Option = utils::parse_parameter_bool(¶ms, "is_registered"); let last_seen_from: Option> = utils::parse_parameter_date(¶ms, "last_seen_from"); let last_seen_to: Option> = utils::parse_parameter_date(¶ms, "last_seen_to"); let owner: Option = utils::parse_parameter_string(¶ms, "owner"); let device_type: Option = utils::parse_parameter_string(¶ms, "device_type"); let vendor: Option = utils::parse_parameter_string(¶ms, "vendor"); let page_offset: Option = utils::parse_parameter_int(¶ms, "page_offset"); let page_limit: Option = utils::parse_parameter_int(¶ms, "page_limit"); match db::devices::list_devices( is_registered, last_seen_from, last_seen_to, owner, device_type, vendor, page_offset, page_limit, ) { Ok(value) => Ok(Json(value)), Err(err) => { error!("Error listing devices: {}", err); Err(StatusCode::INTERNAL_SERVER_ERROR) } } } #[utoipa::path( get, path = "/api/devices/{mac_address}", tag = "devices", params( ("mac_address" = String, Path, description = "MAC address of the device"), ), responses( (status = 200, description = "Device found", body = Device), (status = 404, description = "Device not found"), ), security(("bearer_auth" = [])) )] pub async fn read(Path(mac_address): Path) -> Result, StatusCode> { match db::devices::read(mac_address) { Some(value) => Ok(Json(value)), None => Err(StatusCode::NOT_FOUND), } } #[utoipa::path( put, path = "/api/devices", tag = "devices", request_body = RegisterDevicePayload, responses( (status = 201, description = "Device registered"), (status = 404, description = "Device not found"), (status = 409, description = "Device already registered"), (status = 500, description = "Internal server error"), ), security(("bearer_auth" = [])) )] pub async fn register(Json(payload): Json) -> impl IntoResponse { debug!( "Device registration received: mac_address={}, owner={}, device_type={}", payload.mac_address, payload.owner, payload.device_type ); let mut device = match db::devices::read(payload.mac_address) { Some(value) => value, None => { return ( axum::http::StatusCode::NOT_FOUND, "Device not found or could not be read", ); } }; if device.is_registered { return ( axum::http::StatusCode::CONFLICT, "Device already registered", ); } device.is_registered = true; device.owner = payload.owner; device.device_type = payload.device_type; match db::devices::update(device) { Ok(_) => (axum::http::StatusCode::CREATED, "Device registered"), Err(err) => { error!("Error registering device in the database: {}", err); ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Error registering device in the server, check your logs", ) } } } #[utoipa::path( delete, path = "/api/devices/{mac_address}", tag = "devices", params( ("mac_address" = String, Path, description = "MAC address of the device"), ), responses( (status = 200, description = "Device unregistered"), (status = 404, description = "Device not found"), (status = 409, description = "Device is not registered"), (status = 500, description = "Internal server error"), ), security(("bearer_auth" = [])) )] pub async fn unregister(Path(mac_address): Path) -> impl IntoResponse { let mut device = match db::devices::read(mac_address) { Some(value) => value, None => { return ( axum::http::StatusCode::NOT_FOUND, "Device not found or could not be read", ); } }; if !device.is_registered { return ( axum::http::StatusCode::CONFLICT, "Device not registered, you cannot un-register it again", ); } device.is_registered = false; device.owner = "".to_string(); match db::devices::update(device) { Ok(_) => (axum::http::StatusCode::OK, "Device un-registered"), Err(err) => { error!("Error updating device in the database: {}", err); ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Error updating device in the server, check your logs", ) } } } #[utoipa::path( get, path = "/api/devices/summary", tag = "devices", responses( (status = 200, description = "Device summary counts", body = DeviceSummary), (status = 500, description = "Internal server error"), ), security(("bearer_auth" = [])) )] pub async fn summary() -> Result, StatusCode> { match db::devices::get_summary() { Ok(value) => Ok(Json(value)), Err(err) => { error!("Error getting device summary: {}", err); Err(StatusCode::INTERNAL_SERVER_ERROR) } } } // Payload structs #[derive(Deserialize, ToSchema)] pub struct RegisterDevicePayload { mac_address: String, owner: String, device_type: String, }