hvac: add device delete (retire stale mhi_modbus rows after MQTT switch)

There was no way to remove a zone_devices mapping — a gap exposed by the
Modbus->MQTT switchover, which leaves the old mhi_modbus rows stale beside
the freshly auto-registered mhi_mqtt ones. Adds DELETE /api/devices/:id
(manage_devices cap, unlinks photo files then deletes; device_photos
cascade) and a 'Delete device' button in the expanded device row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-08-13 13:51:41 +00:00
parent 6325532cfe
commit 7ce95a31da
3 changed files with 53 additions and 7 deletions

View file

@ -162,6 +162,31 @@ export async function deviceRoutes(app, opts) {
return { ok: true }
})
// DELETE /api/devices/:id — remove a device mapping entirely. Main use: retiring
// the old mhi_modbus rows after a gateway is switched to native MQTT (the units
// re-register as fresh mhi_mqtt devices, leaving the Modbus rows stale). Photo
// rows cascade via FK; their files are unlinked here so nothing is orphaned.
app.delete('/api/devices/:id', { preHandler: requireCap('manage_devices') }, async (req, reply) => {
const { rows } = await pool.query(
'SELECT id, discovered_name, external_ref, zone_id FROM zone_devices WHERE id = $1',
[req.params.id]
)
if (!rows.length) return reply.status(404).send({ error: 'Device not found' })
const device = rows[0]
const { rows: photos } = await pool.query('SELECT file_path FROM device_photos WHERE device_id = $1', [device.id])
for (const p of photos) {
await unlink(join(UPLOADS_DIR, p.file_path)).catch(() => {})
}
await pool.query('DELETE FROM zone_devices WHERE id = $1', [device.id]) // device_photos cascade
await logActivity(device.zone_id, 'device_command', {
note: `Deleted device ${device.discovered_name || device.external_ref}`,
source: 'manual',
userEmail: req.user.email,
})
return { ok: true }
})
// POST /api/devices/:id/create-maintenance-asset — explicit stub. Real integration
// needs maintenance's own Settings -> API Keys page + POST /api/public/assets first
// (see lib/maintenance-client.js). Returns a clear "not implemented" response rather