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,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct UnitEquipmentItem {
|
||||
#[serde(flatten)]
|
||||
pub equipment: crate::model::Equipment,
|
||||
pub role_points: Vec<crate::handler::equipment::SignalRolePoint>,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct UnitWithRuntime {
|
||||
#[serde(flatten)]
|
||||
pub unit: crate::model::ControlUnit,
|
||||
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||
pub equipments: Vec<UnitEquipmentItem>,
|
||||
}
|
||||
|
||||
pub async fn get_unit_list(
|
||||
|
|
@ -40,7 +48,7 @@ pub async fn get_unit_list(
|
|||
query.validate()?;
|
||||
|
||||
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,
|
||||
query.keyword.as_deref(),
|
||||
query.pagination.page_size,
|
||||
|
|
@ -49,11 +57,54 @@ pub async fn get_unit_list(
|
|||
.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()
|
||||
.map(|unit| {
|
||||
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<_>>();
|
||||
|
||||
|
|
@ -138,7 +189,41 @@ pub async fn get_unit(
|
|||
.await?
|
||||
.ok_or_else(|| ApiErr::NotFound("Unit not found".to_string(), None))?;
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -316,6 +316,21 @@ pub async fn get_all_enabled_units(pool: &PgPool) -> Result<Vec<ControlUnit>, sq
|
|||
.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(
|
||||
pool: &PgPool,
|
||||
unit_id: Uuid,
|
||||
|
|
|
|||
Loading…
Reference in New Issue