use axum::{ extract::{Path, Query, State}, http::StatusCode, response::IntoResponse, Json, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; use validator::Validate; use plc_platform_core::util::{ pagination::{PaginatedResponse, PaginationParams}, response::ApiErr, }; use crate::AppState; async fn notify_units( state: &AppState, unit_ids: impl IntoIterator, ) { let mut seen = std::collections::HashSet::new(); for unit_id in unit_ids { if seen.insert(unit_id) { state.control_runtime.notify_unit(unit_id).await; } } } #[derive(Deserialize, Validate)] pub struct GetEquipmentListQuery { #[validate(length(min = 1, max = 100))] pub keyword: Option, #[serde(flatten)] pub pagination: PaginationParams, } #[derive(Serialize)] pub struct SignalRolePoint { pub point_id: uuid::Uuid, pub signal_role: String, pub point_monitor: Option, } #[derive(Serialize)] pub struct EquipmentListItem { #[serde(flatten)] pub equipment: plc_platform_core::model::Equipment, pub point_count: i64, pub role_points: Vec, } pub async fn get_equipment_list( State(state): State, Query(query): Query, ) -> Result { query.validate()?; let total = crate::service::get_equipment_count(&state.pool, query.keyword.as_deref()).await?; let items = crate::service::get_equipment_paginated( &state.pool, query.keyword.as_deref(), query.pagination.page_size, query.pagination.offset(), ) .await?; let equipment_ids: Vec = items.iter().map(|item| item.equipment.id).collect(); let role_point_rows = crate::service::get_signal_role_points_batch(&state.pool, &equipment_ids).await?; let monitor_guard = state .connection_manager .get_point_monitor_data_read_guard() .await; let mut role_points_map: std::collections::HashMap> = std::collections::HashMap::new(); for rp in role_point_rows { role_points_map .entry(rp.equipment_id) .or_default() .push(SignalRolePoint { point_id: rp.point_id, signal_role: rp.signal_role, point_monitor: monitor_guard.get(&rp.point_id).cloned(), }); } let data = items .into_iter() .map(|item| { let equipment_id = item.equipment.id; EquipmentListItem { equipment: item.equipment, point_count: item.point_count, role_points: role_points_map.remove(&equipment_id).unwrap_or_default(), } }) .collect::>(); Ok(Json(PaginatedResponse::new( data, total, query.pagination.page, query.pagination.page_size, ))) } pub async fn get_equipment( State(state): State, Path(equipment_id): Path, ) -> Result { let equipment = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?; match equipment { Some(item) => Ok(Json(item)), None => Err(ApiErr::NotFound("Equipment not found".to_string(), None)), } } pub async fn get_equipment_points( State(state): State, Path(equipment_id): Path, ) -> Result { let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?; if exists.is_none() { return Err(ApiErr::NotFound("Equipment not found".to_string(), None)); } let points = crate::service::get_points_by_equipment_id(&state.pool, equipment_id).await?; Ok(Json(points)) } #[derive(Debug, Deserialize, Validate)] pub struct CreateEquipmentReq { pub unit_id: Option, #[validate(length(min = 1, max = 100))] pub code: String, #[validate(length(min = 1, max = 100))] pub name: String, pub kind: Option, pub description: Option, } #[derive(Debug, Deserialize, Validate)] pub struct UpdateEquipmentReq { pub unit_id: Option>, #[validate(length(min = 1, max = 100))] pub code: Option, #[validate(length(min = 1, max = 100))] pub name: Option, pub kind: Option, pub description: Option, } #[derive(Debug, Deserialize, Validate)] pub struct BatchSetEquipmentUnitReq { pub equipment_ids: Vec, pub unit_id: Option, } pub async fn create_equipment( State(state): State, Json(payload): Json, ) -> Result { payload.validate()?; let exists = crate::service::get_equipment_by_code(&state.pool, &payload.code).await?; if exists.is_some() { return Err(ApiErr::BadRequest( "Equipment code already exists".to_string(), None, )); } if let Some(unit_id) = payload.unit_id { let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?; if unit_exists.is_none() { return Err(ApiErr::NotFound("Unit not found".to_string(), None)); } } let equipment_id = crate::service::create_equipment( &state.pool, payload.unit_id, &payload.code, &payload.name, payload.kind.as_deref(), payload.description.as_deref(), ) .await?; if let Some(unit_id) = payload.unit_id { notify_units(&state, [unit_id]).await; } Ok(( StatusCode::CREATED, Json(serde_json::json!({ "id": equipment_id, "ok_msg": "Equipment created successfully" })), )) } pub async fn update_equipment( State(state): State, Path(equipment_id): Path, Json(payload): Json, ) -> Result { payload.validate()?; if payload.unit_id.is_none() && payload.code.is_none() && payload.name.is_none() && payload.kind.is_none() && payload.description.is_none() { return Ok(Json(serde_json::json!({"ok_msg": "No fields to update"}))); } let exists = crate::service::get_equipment_by_id(&state.pool, equipment_id).await?; let existing_equipment = if let Some(equipment) = exists { equipment } else { return Err(ApiErr::NotFound("Equipment not found".to_string(), None)); }; if let Some(Some(unit_id)) = payload.unit_id { let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?; if unit_exists.is_none() { return Err(ApiErr::NotFound("Unit not found".to_string(), None)); } } if let Some(code) = payload.code.as_deref() { let duplicate = crate::service::get_equipment_by_code(&state.pool, code).await?; if duplicate .as_ref() .is_some_and(|item| item.id != equipment_id) { return Err(ApiErr::BadRequest( "Equipment code already exists".to_string(), None, )); } } crate::service::update_equipment( &state.pool, equipment_id, payload.unit_id, payload.code.as_deref(), payload.name.as_deref(), payload.kind.as_deref(), payload.description.as_deref(), ) .await?; let mut unit_ids = Vec::new(); if let Some(unit_id) = existing_equipment.unit_id { unit_ids.push(unit_id); } let next_unit_id = match payload.unit_id { Some(next) => next, None => existing_equipment.unit_id, }; if let Some(unit_id) = next_unit_id { unit_ids.push(unit_id); } notify_units(&state, unit_ids).await; Ok(Json(serde_json::json!({ "ok_msg": "Equipment updated successfully" }))) } pub async fn batch_set_equipment_unit( State(state): State, Json(payload): Json, ) -> Result { payload.validate()?; if payload.equipment_ids.is_empty() { return Err(ApiErr::BadRequest( "equipment_ids cannot be empty".to_string(), None, )); } if let Some(unit_id) = payload.unit_id { let unit_exists = crate::service::get_unit_by_id(&state.pool, unit_id).await?; if unit_exists.is_none() { return Err(ApiErr::NotFound("Unit not found".to_string(), None)); } } let before_unit_ids = crate::service::get_unit_ids_by_equipment_ids(&state.pool, &payload.equipment_ids).await?; let updated_count = crate::service::batch_set_equipment_unit( &state.pool, &payload.equipment_ids, payload.unit_id, ) .await?; let mut unit_ids = before_unit_ids; if let Some(unit_id) = payload.unit_id { unit_ids.push(unit_id); } notify_units(&state, unit_ids).await; Ok(Json(serde_json::json!({ "ok_msg": "Equipment unit updated successfully", "updated_count": updated_count }))) } pub async fn delete_equipment( State(state): State, Path(equipment_id): Path, ) -> Result { let unit_ids = crate::service::get_unit_ids_by_equipment_ids(&state.pool, &[equipment_id]).await?; let deleted = crate::service::delete_equipment(&state.pool, equipment_id).await?; if !deleted { return Err(ApiErr::NotFound("Equipment not found".to_string(), None)); } notify_units(&state, unit_ids).await; Ok(StatusCode::NO_CONTENT) }