refactor(api): embed runtime in unit list/get/detail responses
Remove the standalone GET /api/unit/runtimes endpoint in favour of embedding runtime directly in existing responses: - GET /api/unit → each item now includes `runtime` field - GET /api/unit/:id → returns UnitWithRuntime - GET /api/unit/:id/detail → UnitDetail now includes `runtime` runtime is null when the engine has not yet initialised the unit. Frontend loadUnits() reads the embedded runtime field to populate state.runtimes — one request instead of two. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
42cdbbc0cc
commit
08add0d087
|
|
@ -26,6 +26,13 @@ pub struct GetUnitListQuery {
|
||||||
pub pagination: PaginationParams,
|
pub pagination: PaginationParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct UnitWithRuntime {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub unit: crate::model::ControlUnit,
|
||||||
|
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_unit_list(
|
pub async fn get_unit_list(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(query): Query<GetUnitListQuery>,
|
Query(query): Query<GetUnitListQuery>,
|
||||||
|
|
@ -41,6 +48,15 @@ pub async fn get_unit_list(
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let all_runtimes = state.control_runtime.get_all().await;
|
||||||
|
let data = data
|
||||||
|
.into_iter()
|
||||||
|
.map(|unit| {
|
||||||
|
let runtime = all_runtimes.get(&unit.id).cloned();
|
||||||
|
UnitWithRuntime { unit, runtime }
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
Ok(Json(PaginatedResponse::new(
|
Ok(Json(PaginatedResponse::new(
|
||||||
data,
|
data,
|
||||||
total,
|
total,
|
||||||
|
|
@ -118,10 +134,11 @@ pub async fn get_unit(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(unit_id): Path<Uuid>,
|
Path(unit_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, ApiErr> {
|
) -> Result<impl IntoResponse, ApiErr> {
|
||||||
match crate::service::get_unit_by_id(&state.pool, unit_id).await? {
|
let unit = crate::service::get_unit_by_id(&state.pool, unit_id)
|
||||||
Some(unit) => Ok(Json(unit)),
|
.await?
|
||||||
None => Err(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;
|
||||||
|
Ok(Json(UnitWithRuntime { unit, runtime }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
|
|
@ -142,6 +159,7 @@ pub struct EquipmentDetail {
|
||||||
pub struct UnitDetail {
|
pub struct UnitDetail {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub unit: crate::model::ControlUnit,
|
pub unit: crate::model::ControlUnit,
|
||||||
|
pub runtime: Option<crate::control::runtime::UnitRuntime>,
|
||||||
pub equipments: Vec<EquipmentDetail>,
|
pub equipments: Vec<EquipmentDetail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,6 +171,8 @@ pub async fn get_unit_detail(
|
||||||
.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 equipments = crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
let equipments = crate::service::get_equipment_by_unit_id(&state.pool, unit_id).await?;
|
||||||
let equipment_ids: Vec<Uuid> = equipments.iter().map(|e| e.id).collect();
|
let equipment_ids: Vec<Uuid> = equipments.iter().map(|e| e.id).collect();
|
||||||
let all_points = crate::service::get_points_by_equipment_ids(&state.pool, &equipment_ids).await?;
|
let all_points = crate::service::get_points_by_equipment_ids(&state.pool, &equipment_ids).await?;
|
||||||
|
|
@ -177,7 +197,7 @@ pub async fn get_unit_detail(
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Json(UnitDetail { unit, equipments }))
|
Ok(Json(UnitDetail { unit, runtime, equipments }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Validate)]
|
#[derive(Debug, Deserialize, Validate)]
|
||||||
|
|
@ -510,10 +530,3 @@ pub async fn get_unit_runtime(
|
||||||
Ok(Json(runtime))
|
Ok(Json(runtime))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all known runtimes as { unit_id: UnitRuntime }.
|
|
||||||
/// Used by the frontend on page load to populate initial state.
|
|
||||||
pub async fn get_all_unit_runtimes(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
) -> impl IntoResponse {
|
|
||||||
Json(state.control_runtime.get_all().await)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -209,10 +209,6 @@ fn build_router(state: AppState) -> Router {
|
||||||
"/api/unit",
|
"/api/unit",
|
||||||
get(handler::control::get_unit_list).post(handler::control::create_unit),
|
get(handler::control::get_unit_list).post(handler::control::create_unit),
|
||||||
)
|
)
|
||||||
.route(
|
|
||||||
"/api/unit/runtimes",
|
|
||||||
get(handler::control::get_all_unit_runtimes),
|
|
||||||
)
|
|
||||||
.route(
|
.route(
|
||||||
"/api/unit/{unit_id}",
|
"/api/unit/{unit_id}",
|
||||||
get(handler::control::get_unit)
|
get(handler::control::get_unit)
|
||||||
|
|
|
||||||
|
|
@ -180,10 +180,7 @@ export function renderUnits() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadUnits() {
|
export async function loadUnits() {
|
||||||
const [response, runtimes] = await Promise.all([
|
const response = await apiFetch("/api/unit?page=1&page_size=-1");
|
||||||
apiFetch("/api/unit?page=1&page_size=-1"),
|
|
||||||
apiFetch("/api/unit/runtimes").catch(() => ({})),
|
|
||||||
]);
|
|
||||||
state.units = response.data || [];
|
state.units = response.data || [];
|
||||||
state.unitMap = new Map(state.units.map((unit) => [unit.id, unit]));
|
state.unitMap = new Map(state.units.map((unit) => [unit.id, unit]));
|
||||||
|
|
||||||
|
|
@ -191,9 +188,9 @@ export async function loadUnits() {
|
||||||
state.selectedUnitId = null;
|
state.selectedUnitId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [unitId, runtime] of Object.entries(runtimes)) {
|
state.units.forEach((unit) => {
|
||||||
state.runtimes.set(unitId, runtime);
|
if (unit.runtime) state.runtimes.set(unit.id, unit.runtime);
|
||||||
}
|
});
|
||||||
|
|
||||||
renderUnits();
|
renderUnits();
|
||||||
renderUnitOptions(dom.equipmentUnitId?.value || "", dom.equipmentUnitId);
|
renderUnitOptions(dom.equipmentUnitId?.value || "", dom.equipmentUnitId);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue