feat(unit): embed equipments with role_points in unit list and get responses
Unit list and single-unit endpoints now include per-unit equipment list with signal-role points and monitor data, consistent with unit detail. Uses batch queries to avoid N+1 DB calls. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5a481a5eb3
commit
0545388b85
|
|
@ -26,11 +26,19 @@ pub struct GetUnitListQuery {
|
||||||
pub pagination: PaginationParams,
|
pub pagination: PaginationParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct UnitEquipmentItem {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub equipment: crate::model::Equipment,
|
||||||
|
pub role_points: Vec<crate::handler::equipment::SignalRolePoint>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
pub struct UnitWithRuntime {
|
pub struct UnitWithRuntime {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub unit: crate::model::ControlUnit,
|
pub unit: crate::model::ControlUnit,
|
||||||
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||||
|
pub equipments: Vec<UnitEquipmentItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_unit_list(
|
pub async fn get_unit_list(
|
||||||
|
|
@ -40,7 +48,7 @@ pub async fn get_unit_list(
|
||||||
query.validate()?;
|
query.validate()?;
|
||||||
|
|
||||||
let total = crate::service::get_units_count(&state.pool, query.keyword.as_deref()).await?;
|
let total = crate::service::get_units_count(&state.pool, query.keyword.as_deref()).await?;
|
||||||
let data = crate::service::get_units_paginated(
|
let units = crate::service::get_units_paginated(
|
||||||
&state.pool,
|
&state.pool,
|
||||||
query.keyword.as_deref(),
|
query.keyword.as_deref(),
|
||||||
query.pagination.page_size,
|
query.pagination.page_size,
|
||||||
|
|
@ -49,11 +57,54 @@ pub async fn get_unit_list(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let all_runtimes = state.control_runtime.get_all().await;
|
let all_runtimes = state.control_runtime.get_all().await;
|
||||||
let data = data
|
|
||||||
|
let unit_ids: Vec<Uuid> = units.iter().map(|u| u.id).collect();
|
||||||
|
let all_equipments =
|
||||||
|
crate::service::get_equipment_by_unit_ids(&state.pool, &unit_ids).await?;
|
||||||
|
|
||||||
|
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
||||||
|
let role_point_rows =
|
||||||
|
crate::service::get_signal_role_points_batch(&state.pool, &eq_ids).await?;
|
||||||
|
|
||||||
|
let monitor_guard = state
|
||||||
|
.connection_manager
|
||||||
|
.get_point_monitor_data_read_guard()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut role_points_map: std::collections::HashMap<
|
||||||
|
Uuid,
|
||||||
|
Vec<crate::handler::equipment::SignalRolePoint>,
|
||||||
|
> = std::collections::HashMap::new();
|
||||||
|
for rp in role_point_rows {
|
||||||
|
role_points_map
|
||||||
|
.entry(rp.equipment_id)
|
||||||
|
.or_default()
|
||||||
|
.push(crate::handler::equipment::SignalRolePoint {
|
||||||
|
point_id: rp.point_id,
|
||||||
|
signal_role: rp.signal_role,
|
||||||
|
point_monitor: monitor_guard.get(&rp.point_id).cloned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
drop(monitor_guard);
|
||||||
|
|
||||||
|
let mut equipments_by_unit: std::collections::HashMap<Uuid, Vec<UnitEquipmentItem>> =
|
||||||
|
std::collections::HashMap::new();
|
||||||
|
for eq in all_equipments {
|
||||||
|
let role_points = role_points_map.remove(&eq.id).unwrap_or_default();
|
||||||
|
if let Some(unit_id) = eq.unit_id {
|
||||||
|
equipments_by_unit
|
||||||
|
.entry(unit_id)
|
||||||
|
.or_default()
|
||||||
|
.push(UnitEquipmentItem { equipment: eq, role_points });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = units
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|unit| {
|
.map(|unit| {
|
||||||
let runtime = all_runtimes.get(&unit.id).cloned();
|
let runtime = all_runtimes.get(&unit.id).cloned();
|
||||||
UnitWithRuntime { unit, runtime }
|
let equipments = equipments_by_unit.remove(&unit.id).unwrap_or_default();
|
||||||
|
UnitWithRuntime { unit, runtime, equipments }
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
@ -138,7 +189,41 @@ pub async fn get_unit(
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||||
let runtime = state.control_runtime.get(unit_id).await;
|
let runtime = state.control_runtime.get(unit_id).await;
|
||||||
Ok(Json(UnitWithRuntime { unit, runtime }))
|
|
||||||
|
let all_equipments =
|
||||||
|
crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
||||||
|
let eq_ids: Vec<Uuid> = all_equipments.iter().map(|e| e.id).collect();
|
||||||
|
let role_point_rows =
|
||||||
|
crate::service::get_signal_role_points_batch(&state.pool, &eq_ids).await?;
|
||||||
|
let monitor_guard = state
|
||||||
|
.connection_manager
|
||||||
|
.get_point_monitor_data_read_guard()
|
||||||
|
.await;
|
||||||
|
let mut role_points_map: std::collections::HashMap<
|
||||||
|
Uuid,
|
||||||
|
Vec<crate::handler::equipment::SignalRolePoint>,
|
||||||
|
> = std::collections::HashMap::new();
|
||||||
|
for rp in role_point_rows {
|
||||||
|
role_points_map
|
||||||
|
.entry(rp.equipment_id)
|
||||||
|
.or_default()
|
||||||
|
.push(crate::handler::equipment::SignalRolePoint {
|
||||||
|
point_id: rp.point_id,
|
||||||
|
signal_role: rp.signal_role,
|
||||||
|
point_monitor: monitor_guard.get(&rp.point_id).cloned(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
drop(monitor_guard);
|
||||||
|
|
||||||
|
let equipments = all_equipments
|
||||||
|
.into_iter()
|
||||||
|
.map(|eq| {
|
||||||
|
let role_points = role_points_map.remove(&eq.id).unwrap_or_default();
|
||||||
|
UnitEquipmentItem { equipment: eq, role_points }
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Json(UnitWithRuntime { unit, runtime, equipments }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,21 @@ pub async fn get_all_enabled_units(pool: &PgPool) -> Result<Vec<ControlUnit>, sq
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_equipment_by_unit_ids(
|
||||||
|
pool: &PgPool,
|
||||||
|
unit_ids: &[Uuid],
|
||||||
|
) -> Result<Vec<crate::model::Equipment>, sqlx::Error> {
|
||||||
|
if unit_ids.is_empty() {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
sqlx::query_as::<_, crate::model::Equipment>(
|
||||||
|
r#"SELECT * FROM equipment WHERE unit_id = ANY($1) ORDER BY unit_id, created_at"#,
|
||||||
|
)
|
||||||
|
.bind(unit_ids)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_equipment_by_unit_id(
|
pub async fn get_equipment_by_unit_id(
|
||||||
pool: &PgPool,
|
pool: &PgPool,
|
||||||
unit_id: Uuid,
|
unit_id: Uuid,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue