modbus: add explicit cascade delete (device + readings + toggles + HA cleanup)
frontend / frontend (push) Successful in 2m4s
pytest / test (push) Successful in 7m32s

A disabled device with historical readings still cannot be deleted by default
(409 guard stays, to prevent accidental data loss). A new explicit opt-in lets
the user remove it for real:

- DELETE /api/modbus/devices/{uuid}?cascade=true removes the device's readings
  and exposed_entity_toggle rows, then the device, in one transaction (FK order:
  readings before device). Best-effort clears the HA discovery configs first
  (MQTT not connected -> no-op, never blocks the delete). Default (no cascade)
  unchanged. Returns 200 with deletion counts.
- Frontend: a 'Force Delete (all data)' button appears only after the 409, with
  a red irreversible warning; cascade is sent only on that explicit action.
- Also corrected a stale comment: FK RESTRICT IS enforced at runtime now.
This commit is contained in:
2026-06-24 17:38:52 +02:00
parent d7f0731d1c
commit 682e06d256
11 changed files with 499 additions and 42 deletions
+17 -4
View File
@@ -686,12 +686,23 @@ export interface paths {
* Delete Device
* @description Delete a Modbus device.
*
* **Default behaviour (cascade=false)**:
* Returns 409 Conflict if the device has any associated readings; use
* ``enabled=false`` to disable it instead.
*
* **Application-layer safety**: the check is performed via an explicit
* SELECT COUNT query — not by relying on SQLite's FK RESTRICT constraint,
* which is not enforced at runtime in this project (no PRAGMA foreign_keys=ON).
* **Cascade delete (cascade=true)**:
* Permanently deletes the device together with all its readings and any
* ``ExposedEntityToggle`` rows whose key matches ``modbus.<uuid>.*``.
* Also makes a best-effort attempt to clear the device's HA Discovery
* config topics from MQTT (empty retained payload) before the DB rows
* are removed. MQTT failures are swallowed — the DB deletion proceeds
* regardless.
* Returns HTTP 200 with a ``ModbusDeleteResponse`` JSON body on success.
*
* **Application-layer safety**: the 409 guard uses an explicit SELECT COUNT
* query to return a friendly message. FK RESTRICT is enforced at runtime
* (the app sets ``PRAGMA foreign_keys=ON``), so the cascade path deletes
* readings before the device.
*/
delete: operations["delete_device_api_modbus_devices__uuid__delete"];
options?: never;
@@ -3190,7 +3201,9 @@ export interface operations {
};
delete_device_api_modbus_devices__uuid__delete: {
parameters: {
query?: never;
query?: {
cascade?: boolean;
};
header?: {
"X-CSRF-Token"?: string | null;
};