'use strict'; const express = require('express'); const router = express.Router(); const fs = require('fs'); const path = require('path'); const xml2js = require('xml2js'); const db = require('../models/db'); const { seedDatabase } = require('../lib/seed'); const conf = require('../conf/base'); // GET /api/state - Full application state router.get('/state', (req, res) => { try { const panels = db.prepare('SELECT * FROM panels').all(); const breakers = db.prepare(` SELECT b.*, (SELECT COUNT(*) FROM devices d WHERE d.breaker_id = b.id) as device_count FROM breakers b ORDER BY b.panel_id DESC, b.slot ASC, b.sub_slot ASC `).all(); const devices = db.prepare(` SELECT d.*, b.label as breaker_label, b.color as breaker_color, b.description as breaker_desc FROM devices d LEFT JOIN breakers b ON d.breaker_id = b.id `).all(); const structures = db.prepare('SELECT * FROM structures').all(); const edges = db.prepare('SELECT * FROM edges').all(); const totalDevices = devices.length; const mappedDevices = devices.filter(d => d.breaker_id !== null).length; const unmappedDevices = totalDevices - mappedDevices; const progressPercent = totalDevices > 0 ? Math.round((mappedDevices / totalDevices) * 100) : 0; res.json({ success: true, data: { panels, breakers, devices, structures, edges, stats: { totalDevices, mappedDevices, unmappedDevices, progressPercent } } }); } catch (err) { console.error('API /state error:', err); res.status(500).json({ success: false, error: err.message }); } }); // POST /api/devices/assign - Assign one or more devices to a breaker router.post('/devices/assign', (req, res) => { try { const { deviceIds, breakerId } = req.body; if (!deviceIds || !Array.isArray(deviceIds) || deviceIds.length === 0) { return res.status(400).json({ success: false, error: 'deviceIds array is required' }); } if (breakerId !== null) { const breaker = db.prepare('SELECT id FROM breakers WHERE id = ?').get(breakerId); if (!breaker) { return res.status(404).json({ success: false, error: `Breaker ${breakerId} not found` }); } } const updateStmt = db.prepare(` UPDATE devices SET breaker_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `); const tx = db.transaction(() => { for (const id of deviceIds) { updateStmt.run(breakerId, id); } }); tx(); res.json({ success: true, updated: deviceIds.length, breakerId }); } catch (err) { console.error('API /devices/assign error:', err); res.status(500).json({ success: false, error: err.message }); } }); // POST /api/devices/bulk-room - Assign all devices in a room router.post('/devices/bulk-room', (req, res) => { try { const { room, breakerId } = req.body; if (!room) { return res.status(400).json({ success: false, error: 'room is required' }); } const updateStmt = db.prepare(` UPDATE devices SET breaker_id = ?, updated_at = CURRENT_TIMESTAMP WHERE room = ? `); const result = updateStmt.run(breakerId, room); res.json({ success: true, updated: result.changes, room, breakerId }); } catch (err) { console.error('API /devices/bulk-room error:', err); res.status(500).json({ success: false, error: err.message }); } }); // POST /api/devices/:id - Update individual device attributes router.post('/devices/:id', (req, res) => { try { const { id } = req.params; const { label, notes, type, breaker_id } = req.body; const current = db.prepare('SELECT * FROM devices WHERE id = ?').get(id); if (!current) { return res.status(404).json({ success: false, error: 'Device not found' }); } const newLabel = label !== undefined ? label : current.label; const newNotes = notes !== undefined ? notes : current.notes; const newType = type !== undefined ? type : current.type; const newBreakerId = breaker_id !== undefined ? breaker_id : current.breaker_id; db.prepare(` UPDATE devices SET label = ?, notes = ?, type = ?, breaker_id = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(newLabel, newNotes, newType, newBreakerId, id); res.json({ success: true, device: { id, label: newLabel, notes: newNotes, type: newType, breaker_id: newBreakerId } }); } catch (err) { console.error('API /devices/:id error:', err); res.status(500).json({ success: false, error: err.message }); } }); // POST /api/breakers/:id - Update breaker metadata router.post('/breakers/:id', (req, res) => { try { const { id } = req.params; const { description, amps, voltage, color, notes } = req.body; const current = db.prepare('SELECT * FROM breakers WHERE id = ?').get(id); if (!current) { return res.status(404).json({ success: false, error: 'Breaker not found' }); } const newDesc = description !== undefined ? description : current.description; const newAmps = amps !== undefined ? parseInt(amps, 10) : current.amps; const newVolt = voltage !== undefined ? parseInt(voltage, 10) : current.voltage; const newColor = color !== undefined ? color : current.color; const newNotes = notes !== undefined ? notes : current.notes; db.prepare(` UPDATE breakers SET description = ?, amps = ?, voltage = ?, color = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? `).run(newDesc, newAmps, newVolt, newColor, newNotes, id); res.json({ success: true, breaker: { id, description: newDesc, amps: newAmps, voltage: newVolt, color: newColor, notes: newNotes } }); } catch (err) { console.error('API /breakers/:id error:', err); res.status(500).json({ success: false, error: err.message }); } }); // POST /api/reset - Reset to seed data router.post('/reset', (req, res) => { try { seedDatabase(true); res.json({ success: true, message: 'Database reset to initial seed state.' }); } catch (err) { console.error('API /reset error:', err); res.status(500).json({ success: false, error: err.message }); } }); // GET /api/export/json - Export entire database state router.get('/export/json', (req, res) => { try { const panels = db.prepare('SELECT * FROM panels').all(); const breakers = db.prepare('SELECT * FROM breakers').all(); const devices = db.prepare('SELECT * FROM devices').all(); const structures = db.prepare('SELECT * FROM structures').all(); const edges = db.prepare('SELECT * FROM edges').all(); const exportPayload = { timestamp: new Date().toISOString(), panels, breakers, devices, structures, edges }; res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Disposition', 'attachment; filename="electrical_chart_export.json"'); res.send(JSON.stringify(exportPayload, null, 2)); } catch (err) { console.error('API /export/json error:', err); res.status(500).json({ success: false, error: err.message }); } }); // GET /api/export/csv - Export schedule CSV router.get('/export/csv', (req, res) => { try { const devices = db.prepare(` SELECT d.id as device_id, d.type, d.room, d.label as device_label, d.notes as device_notes, b.panel_id, b.label as breaker_label, b.description as circuit_description, b.amps, b.voltage FROM devices d LEFT JOIN breakers b ON d.breaker_id = b.id ORDER BY b.panel_id, b.slot, b.sub_slot, d.room `).all(); const headers = ['Device ID', 'Type', 'Room', 'Device Label', 'Panel', 'Breaker Slot', 'Circuit Description', 'Amps', 'Voltage', 'Notes']; const rows = devices.map(d => [ `"${d.device_id || ''}"`, `"${d.type || ''}"`, `"${d.room || ''}"`, `"${(d.device_label || '').replace(/"/g, '""')}"`, `"${d.panel_id || 'UNMAPPED'}"`, `"${d.breaker_label || 'UNMAPPED'}"`, `"${(d.circuit_description || '').replace(/"/g, '""')}"`, `"${d.amps || ''}"`, `"${d.voltage || ''}"`, `"${(d.device_notes || '').replace(/"/g, '""')}"` ]); const csvContent = [headers.join(','), ...rows.map(r => r.join(','))].join('\n'); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename="electrical_panel_schedule.csv"'); res.send(csvContent); } catch (err) { console.error('API /export/csv error:', err); res.status(500).json({ success: false, error: err.message }); } }); // GET /api/export/drawio - Export updated draw.io XML with breaker badges router.get('/export/drawio', (req, res) => { try { const xmlPath = conf.drawioSourcePath; let rawXml = fs.readFileSync(xmlPath, 'utf8'); const devices = db.prepare(` SELECT d.*, b.label as breaker_label, b.color as breaker_color FROM devices d LEFT JOIN breakers b ON d.breaker_id = b.id WHERE d.breaker_id IS NOT NULL `).all(); // In rawXml, update matching mxCell values or add labels for (const d of devices) { // Find ]*value=")([^"]*)(")`, 'g'); if (cellPattern.test(rawXml)) { rawXml = rawXml.replace(cellPattern, `$1[${d.breaker_label}]$3`); } } res.setHeader('Content-Type', 'application/xml'); res.setHeader('Content-Disposition', 'attachment; filename="mapped_electrical_diagram.drawio"'); res.send(rawXml); } catch (err) { console.error('API /export/drawio error:', err); res.status(500).json({ success: false, error: err.message }); } }); module.exports = router;