v0.8.19
This commit is contained in:
+18
-1
@@ -1,4 +1,5 @@
|
||||
async fn discover(State(state): State<AppState>, Json(request): Json<DiscoveryRequest>) -> Result<Json<Value>, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let settings = state.settings.read().await.clone();
|
||||
let timeout_ms = request.timeout_ms.unwrap_or(settings.discovery_timeout_ms).clamp(500, 30_000);
|
||||
let broadcast = request.broadcast.unwrap_or(settings.discovery_broadcast);
|
||||
@@ -50,6 +51,7 @@ async fn list_devices(State(state): State<AppState>) -> Result<Json<Vec<Device>>
|
||||
}
|
||||
|
||||
async fn add_device(State(state): State<AppState>, Json(input): Json<ManualDeviceRequest>) -> Result<(StatusCode, Json<Device>), AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
if input.name.trim().is_empty() || input.mac.trim().is_empty() || input.ip.trim().is_empty() {
|
||||
return Err(AppError::BadRequest("name, mac and ip are required".into()));
|
||||
}
|
||||
@@ -114,6 +116,7 @@ async fn get_device(State(state): State<AppState>, Path(id): Path<String>) -> Re
|
||||
}
|
||||
|
||||
async fn patch_device(State(state): State<AppState>, Path(id): Path<String>, Json(patch): Json<DevicePatch>) -> Result<Json<Device>, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
if patch.enabled == Some(false) {
|
||||
engine::disable_device_safely(&state, &id).await?;
|
||||
}
|
||||
@@ -145,6 +148,12 @@ async fn patch_device(State(state): State<AppState>, Path(id): Path<String>, Jso
|
||||
}
|
||||
|
||||
async fn delete_device(State(state): State<AppState>, Path(id): Path<String>) -> Result<StatusCode, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
// Keep reference validation and the destructive DB operation in one serialized window.
|
||||
// Lock order for cross-resource destructive operations: configuration -> automation -> house -> schedule -> zones -> device.
|
||||
let _automation_guard = state.lock_automation_operation().await;
|
||||
let _house_guard = state.lock_house_operation().await;
|
||||
let _schedule_guard = state.lock_schedule_operation().await;
|
||||
if state.db.get_device(&id)?.is_none() { return Err(AppError::NotFound(format!("device {id}"))); }
|
||||
if state.db.list_automations()?.iter().any(|item| {
|
||||
item.trigger_device_id.as_deref() == Some(id.as_str())
|
||||
@@ -156,16 +165,24 @@ async fn delete_device(State(state): State<AppState>, Path(id): Path<String>) ->
|
||||
.filter(|zone| zone.device_id == id)
|
||||
.map(|zone| zone.id)
|
||||
.collect();
|
||||
let mut sorted_zone_ids: Vec<String> = removed_zone_ids.iter().cloned().collect();
|
||||
sorted_zone_ids.sort();
|
||||
let mut zone_guards = Vec::with_capacity(sorted_zone_ids.len());
|
||||
for zone_id in &sorted_zone_ids {
|
||||
zone_guards.push(state.lock_zone_operation(zone_id).await);
|
||||
}
|
||||
ensure_zone_removal_safe(&state, &removed_zone_ids)?;
|
||||
ensure_device_stopped_for_detach(&state, &id, "device.deleted").await?;
|
||||
if !state.db.delete_device(&id)? { return Err(AppError::NotFound(format!("device {id}"))); }
|
||||
remove_zone_ids_from_groups(&state, &removed_zone_ids)?;
|
||||
drop(zone_guards);
|
||||
remove_zone_ids_from_groups_locked(&state, &removed_zone_ids).await?;
|
||||
state.log("info", "device.deleted", "Device deleted", json!({"device_id": id}));
|
||||
state.broadcast("device.deleted", json!({"id": id}));
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
async fn bind_device(State(state): State<AppState>, Path(id): Path<String>) -> Result<Json<Device>, AppError> {
|
||||
let _configuration_guard = state.lock_configuration_operation().await;
|
||||
let _device_guard = state.lock_device_operation(&id).await;
|
||||
let mut device = state.db.get_device(&id)?.ok_or_else(|| AppError::NotFound(format!("device {id}")))?;
|
||||
if device.simulated { return Ok(Json(device)); }
|
||||
|
||||
Reference in New Issue
Block a user