290 lines
9.7 KiB
JavaScript
290 lines
9.7 KiB
JavaScript
'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 <mxCell id="d.drawio_id"
|
|
const cellPattern = new RegExp(`(<mxCell id="${d.drawio_id}"[^>]*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;
|