use axum::{extract::{State, Query}, Json}; use serde::Deserialize; use sqlx::Row; use crate::AppState; use super::{ApiResponse, Pagination}; #[derive(Debug, Deserialize)] pub struct AssetListParams { pub device_uid: Option, pub search: Option, pub page: Option, pub page_size: Option, } pub async fn list_hardware( State(state): State, Query(params): Query, ) -> Json> { let limit = params.page_size.unwrap_or(20).min(100); let offset = params.page.unwrap_or(1).saturating_sub(1) * limit; // Normalize empty strings to None let device_uid = params.device_uid.as_deref().filter(|s| !s.is_empty()).map(String::from); let search = params.search.as_deref().filter(|s| !s.is_empty()).map(String::from); let rows = sqlx::query( "SELECT id, device_uid, cpu_model, cpu_cores, memory_total_mb, disk_model, disk_total_mb, gpu_model, motherboard, serial_number, reported_at FROM hardware_assets WHERE 1=1 AND (? IS NULL OR device_uid = ?) AND (? IS NULL OR cpu_model LIKE '%' || ? || '%' OR gpu_model LIKE '%' || ? || '%') ORDER BY reported_at DESC LIMIT ? OFFSET ?" ) .bind(&device_uid).bind(&device_uid) .bind(&search).bind(&search).bind(&search) .bind(limit).bind(offset) .fetch_all(&state.db) .await; match rows { Ok(records) => { let items: Vec = records.iter().map(|r| serde_json::json!({ "id": r.get::("id"), "device_uid": r.get::("device_uid"), "cpu_model": r.get::("cpu_model"), "cpu_cores": r.get::("cpu_cores"), "memory_total_mb": r.get::("memory_total_mb"), "disk_model": r.get::("disk_model"), "disk_total_mb": r.get::("disk_total_mb"), "gpu_model": r.get::, _>("gpu_model"), "motherboard": r.get::, _>("motherboard"), "serial_number": r.get::, _>("serial_number"), "reported_at": r.get::("reported_at"), })).collect(); Json(ApiResponse::ok(serde_json::json!({ "hardware": items, "page": params.page.unwrap_or(1), "page_size": limit, }))) } Err(e) => Json(ApiResponse::internal_error("query hardware assets", e)), } } pub async fn list_software( State(state): State, Query(params): Query, ) -> Json> { let limit = params.page_size.unwrap_or(20).min(100); let offset = params.page.unwrap_or(1).saturating_sub(1) * limit; // Normalize empty strings to None let device_uid = params.device_uid.as_deref().filter(|s| !s.is_empty()).map(String::from); let search = params.search.as_deref().filter(|s| !s.is_empty()).map(String::from); let rows = sqlx::query( "SELECT id, device_uid, name, version, publisher, install_date, install_path FROM software_assets WHERE 1=1 AND (? IS NULL OR device_uid = ?) AND (? IS NULL OR name LIKE '%' || ? || '%' OR publisher LIKE '%' || ? || '%') ORDER BY name ASC LIMIT ? OFFSET ?" ) .bind(&device_uid).bind(&device_uid) .bind(&search).bind(&search).bind(&search) .bind(limit).bind(offset) .fetch_all(&state.db) .await; match rows { Ok(records) => { let items: Vec = records.iter().map(|r| serde_json::json!({ "id": r.get::("id"), "device_uid": r.get::("device_uid"), "name": r.get::("name"), "version": r.get::, _>("version"), "publisher": r.get::, _>("publisher"), "install_date": r.get::, _>("install_date"), "install_path": r.get::, _>("install_path"), })).collect(); Json(ApiResponse::ok(serde_json::json!({ "software": items, "page": params.page.unwrap_or(1), "page_size": limit, }))) } Err(e) => Json(ApiResponse::internal_error("query software assets", e)), } } pub async fn list_changes( State(state): State, Query(params): Query, ) -> Json> { let limit = params.page_size.unwrap_or(20).min(100); let offset = params.page.unwrap_or(1).saturating_sub(1) * limit; let device_uid = params.device_uid.as_deref().filter(|s| !s.is_empty()).map(String::from); let rows = sqlx::query( "SELECT id, device_uid, change_type, change_detail, detected_at FROM asset_changes WHERE 1=1 AND (? IS NULL OR device_uid = ?) ORDER BY detected_at DESC LIMIT ? OFFSET ?" ) .bind(&device_uid).bind(&device_uid) .bind(limit).bind(offset) .fetch_all(&state.db) .await; match rows { Ok(records) => { let items: Vec = records.iter().map(|r| serde_json::json!({ "id": r.get::("id"), "device_uid": r.get::("device_uid"), "change_type": r.get::("change_type"), "change_detail": r.get::("change_detail"), "detected_at": r.get::("detected_at"), })).collect(); Json(ApiResponse::ok(serde_json::json!({ "changes": items, "page": params.page.unwrap_or(1), "page_size": limit, }))) } Err(e) => Json(ApiResponse::internal_error("query asset changes", e)), } }