Initial commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "networkview-backend",
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"dev": "nodemon src/index.js",
|
||||
"start": "node src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"morgan": "^1.10.0",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const DB_PATH = path.join(__dirname, '../../../data/networkview.db');
|
||||
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
||||
const db = new Database(DB_PATH);
|
||||
|
||||
// Enable WAL mode for better performance
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS sites (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
location TEXT,
|
||||
notes TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rooms (
|
||||
id TEXT PRIMARY KEY,
|
||||
site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
notes TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS racks (
|
||||
id TEXT PRIMARY KEY,
|
||||
room_id TEXT REFERENCES rooms(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
total_units INTEGER DEFAULT 42,
|
||||
manufacturer TEXT,
|
||||
model TEXT,
|
||||
notes TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS components (
|
||||
id TEXT PRIMARY KEY,
|
||||
rack_id TEXT REFERENCES racks(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL DEFAULT 'other',
|
||||
position INTEGER,
|
||||
height_units INTEGER DEFAULT 1,
|
||||
manufacturer TEXT,
|
||||
model TEXT,
|
||||
serial_number TEXT,
|
||||
asset_tag TEXT,
|
||||
ip_address TEXT,
|
||||
mac_address TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
notes TEXT DEFAULT '',
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ports (
|
||||
id TEXT PRIMARY KEY,
|
||||
component_id TEXT NOT NULL REFERENCES components(id) ON DELETE CASCADE,
|
||||
port_number INTEGER NOT NULL,
|
||||
label TEXT,
|
||||
port_type TEXT DEFAULT 'RJ45',
|
||||
connected_to_port_id TEXT REFERENCES ports(id) ON DELETE SET NULL,
|
||||
notes TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
|
||||
// Migrate: add port_count column if it doesn't exist yet
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN port_count INTEGER DEFAULT NULL'); } catch (_) {}
|
||||
try { db.exec('ALTER TABLE components ADD COLUMN sfp_count INTEGER DEFAULT NULL'); } catch (_) {}
|
||||
|
||||
module.exports = db;
|
||||
@@ -0,0 +1,51 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
const path = require('path');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(morgan('dev'));
|
||||
|
||||
// API routes
|
||||
app.use('/api/sites', require('./routes/sites'));
|
||||
app.use('/api/rooms', require('./routes/rooms'));
|
||||
app.use('/api/racks', require('./routes/racks'));
|
||||
app.use('/api/components', require('./routes/components'));
|
||||
|
||||
// Search across all entities
|
||||
const db = require('./db');
|
||||
app.get('/api/search', (req, res) => {
|
||||
const { q } = req.query;
|
||||
if (!q || q.trim().length < 2) return res.json({ sites: [], rooms: [], racks: [], components: [] });
|
||||
|
||||
const term = `%${q.trim()}%`;
|
||||
const sites = db.prepare('SELECT id, name, location FROM sites WHERE name LIKE ? OR location LIKE ? LIMIT 10').all(term, term);
|
||||
const rooms = db.prepare('SELECT id, name, site_id FROM rooms WHERE name LIKE ? LIMIT 10').all(term);
|
||||
const racks = db.prepare('SELECT id, name, room_id FROM racks WHERE name LIKE ? OR model LIKE ? LIMIT 10').all(term, term);
|
||||
const components = db.prepare(`
|
||||
SELECT id, name, type, rack_id, model, ip_address FROM components
|
||||
WHERE name LIKE ? OR model LIKE ? OR ip_address LIKE ? OR serial_number LIKE ? LIMIT 20
|
||||
`).all(term, term, term, term);
|
||||
|
||||
res.json({ sites, rooms, racks, components });
|
||||
});
|
||||
|
||||
// Health check
|
||||
app.get('/api/health', (_req, res) => res.json({ status: 'ok', time: new Date().toISOString() }));
|
||||
|
||||
// Serve built frontend in production
|
||||
const FRONTEND_DIST = path.join(__dirname, '../../frontend/dist');
|
||||
app.use(express.static(FRONTEND_DIST));
|
||||
app.get('*', (_req, res) => {
|
||||
res.sendFile(path.join(FRONTEND_DIST, 'index.html'), err => {
|
||||
if (err) res.status(200).json({ message: 'NetworkView API running. Start frontend separately in dev mode.' });
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`NetworkView backend running on http://localhost:${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,233 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../db');
|
||||
const router = express.Router();
|
||||
|
||||
function checkPositionOverlap(rackId, position, heightUnits, excludeId = null) {
|
||||
const end = position + heightUnits - 1;
|
||||
const existing = db.prepare(`
|
||||
SELECT id, name, position, height_units FROM components
|
||||
WHERE rack_id = ?
|
||||
AND id != ?
|
||||
AND position IS NOT NULL
|
||||
AND position <= ? AND (position + height_units - 1) >= ?
|
||||
`).all(rackId, excludeId || '', end, position);
|
||||
return existing;
|
||||
}
|
||||
|
||||
// GET /api/components?rackId=
|
||||
router.get('/', (req, res) => {
|
||||
const { rackId } = req.query;
|
||||
if (!rackId) return res.status(400).json({ error: 'rackId query param required' });
|
||||
|
||||
const components = db.prepare(
|
||||
'SELECT * FROM components WHERE rack_id = ? ORDER BY position ASC NULLS LAST'
|
||||
).all(rackId);
|
||||
res.json(components);
|
||||
});
|
||||
|
||||
// GET /api/components/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const component = db.prepare('SELECT * FROM components WHERE id = ?').get(req.params.id);
|
||||
if (!component) return res.status(404).json({ error: 'Component not found' });
|
||||
|
||||
const rawPorts = db.prepare('SELECT * FROM ports WHERE component_id = ? ORDER BY port_number').all(req.params.id);
|
||||
const ports = rawPorts.map(p => {
|
||||
if (!p.connected_to_port_id) return p;
|
||||
const lp = db.prepare('SELECT * FROM ports WHERE id = ?').get(p.connected_to_port_id);
|
||||
if (!lp) return p;
|
||||
const lc = db.prepare('SELECT id, name, type FROM components WHERE id = ?').get(lp.component_id);
|
||||
return { ...p, linked_port: lc ? { id: lp.id, port_number: lp.port_number, label: lp.label, component_id: lc.id, component_name: lc.name, component_type: lc.type } : null };
|
||||
});
|
||||
|
||||
let rack = null, room = null, site = null;
|
||||
if (component.rack_id) {
|
||||
rack = db.prepare('SELECT id, name, total_units, room_id FROM racks WHERE id = ?').get(component.rack_id);
|
||||
if (rack?.room_id) {
|
||||
room = db.prepare('SELECT id, name, site_id FROM rooms WHERE id = ?').get(rack.room_id);
|
||||
if (room?.site_id) site = db.prepare('SELECT id, name FROM sites WHERE id = ?').get(room.site_id);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({ ...component, ports, rack, room, site });
|
||||
});
|
||||
|
||||
// POST /api/components
|
||||
router.post('/', (req, res) => {
|
||||
const {
|
||||
rack_id, name, type, position, height_units,
|
||||
manufacturer, model, serial_number, asset_tag,
|
||||
ip_address, mac_address, status, notes
|
||||
} = req.body;
|
||||
|
||||
if (!name) return res.status(400).json({ error: 'name is required' });
|
||||
|
||||
if (rack_id) {
|
||||
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(rack_id);
|
||||
if (!rack) return res.status(404).json({ error: 'Rack not found' });
|
||||
|
||||
if (position != null) {
|
||||
const h = height_units || 1;
|
||||
if (position < 1 || position + h - 1 > rack.total_units) {
|
||||
return res.status(400).json({ error: `Position ${position} with ${h}U height exceeds rack size (${rack.total_units}U)` });
|
||||
}
|
||||
const overlapping = checkPositionOverlap(rack_id, position, h);
|
||||
if (overlapping.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: `Position overlaps with existing component: ${overlapping[0].name}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { port_count, sfp_count } = req.body;
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO components
|
||||
(id, rack_id, name, type, position, height_units, manufacturer, model,
|
||||
serial_number, asset_tag, ip_address, mac_address, status, notes, port_count, sfp_count)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id, rack_id || null, name, type || 'other',
|
||||
position ?? null, height_units || 1,
|
||||
manufacturer || null, model || null,
|
||||
serial_number || null, asset_tag || null,
|
||||
ip_address || null, mac_address || null,
|
||||
status || 'active', notes || '',
|
||||
port_count ?? null, sfp_count ?? null
|
||||
);
|
||||
|
||||
res.status(201).json(db.prepare('SELECT * FROM components WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
// PUT /api/components/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const component = db.prepare('SELECT * FROM components WHERE id = ?').get(req.params.id);
|
||||
if (!component) return res.status(404).json({ error: 'Component not found' });
|
||||
|
||||
const {
|
||||
name, type, position, height_units, manufacturer, model,
|
||||
serial_number, asset_tag, ip_address, mac_address, status, notes, port_count, sfp_count
|
||||
} = req.body;
|
||||
|
||||
const newPos = position ?? component.position;
|
||||
const newH = height_units ?? component.height_units;
|
||||
|
||||
if (component.rack_id && newPos != null) {
|
||||
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(component.rack_id);
|
||||
if (newPos < 1 || newPos + newH - 1 > rack.total_units) {
|
||||
return res.status(400).json({ error: `Position exceeds rack size (${rack.total_units}U)` });
|
||||
}
|
||||
const overlapping = checkPositionOverlap(component.rack_id, newPos, newH, req.params.id);
|
||||
if (overlapping.length > 0) {
|
||||
return res.status(400).json({ error: `Position overlaps with: ${overlapping[0].name}` });
|
||||
}
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE components SET
|
||||
name = ?, type = ?, position = ?, height_units = ?,
|
||||
manufacturer = ?, model = ?, serial_number = ?, asset_tag = ?,
|
||||
ip_address = ?, mac_address = ?, status = ?, notes = ?,
|
||||
port_count = ?, sfp_count = ?,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
name ?? component.name,
|
||||
type ?? component.type,
|
||||
newPos,
|
||||
newH,
|
||||
manufacturer ?? component.manufacturer,
|
||||
model ?? component.model,
|
||||
serial_number ?? component.serial_number,
|
||||
asset_tag ?? component.asset_tag,
|
||||
ip_address ?? component.ip_address,
|
||||
mac_address ?? component.mac_address,
|
||||
status ?? component.status,
|
||||
notes ?? component.notes,
|
||||
port_count !== undefined ? port_count : component.port_count,
|
||||
sfp_count !== undefined ? sfp_count : component.sfp_count,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
res.json(db.prepare('SELECT * FROM components WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
// DELETE /api/components/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
const component = db.prepare('SELECT id FROM components WHERE id = ?').get(req.params.id);
|
||||
if (!component) return res.status(404).json({ error: 'Component not found' });
|
||||
|
||||
db.prepare('DELETE FROM components WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// --- Ports sub-resource ---
|
||||
|
||||
// GET /api/components/:id/ports
|
||||
router.get('/:id/ports', (req, res) => {
|
||||
const ports = db.prepare('SELECT * FROM ports WHERE component_id = ? ORDER BY port_number').all(req.params.id);
|
||||
res.json(ports);
|
||||
});
|
||||
|
||||
// POST /api/components/:id/ports
|
||||
router.post('/:id/ports', (req, res) => {
|
||||
const component = db.prepare('SELECT id FROM components WHERE id = ?').get(req.params.id);
|
||||
if (!component) return res.status(404).json({ error: 'Component not found' });
|
||||
|
||||
const { port_number, label, port_type, connected_to_port_id, notes } = req.body;
|
||||
if (port_number == null) return res.status(400).json({ error: 'port_number is required' });
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO ports (id, component_id, port_number, label, port_type, connected_to_port_id, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, req.params.id, port_number, label || null, port_type || 'RJ45', connected_to_port_id || null, notes || null);
|
||||
|
||||
// Mirror link on the other side
|
||||
if (connected_to_port_id) {
|
||||
db.prepare('UPDATE ports SET connected_to_port_id = ? WHERE id = ?').run(id, connected_to_port_id);
|
||||
}
|
||||
|
||||
res.status(201).json(db.prepare('SELECT * FROM ports WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
// PUT /api/components/:componentId/ports/:portId
|
||||
router.put('/:componentId/ports/:portId', (req, res) => {
|
||||
const port = db.prepare('SELECT * FROM ports WHERE id = ? AND component_id = ?').get(req.params.portId, req.params.componentId);
|
||||
if (!port) return res.status(404).json({ error: 'Port not found' });
|
||||
|
||||
const { label, port_type, connected_to_port_id, notes } = req.body;
|
||||
const newLinkedId = connected_to_port_id !== undefined ? (connected_to_port_id || null) : port.connected_to_port_id;
|
||||
const oldLinkedId = port.connected_to_port_id;
|
||||
|
||||
// Clear old reverse link if it changed
|
||||
if (oldLinkedId && oldLinkedId !== newLinkedId) {
|
||||
db.prepare('UPDATE ports SET connected_to_port_id = NULL WHERE id = ? AND connected_to_port_id = ?').run(oldLinkedId, port.id);
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE ports SET label = ?, port_type = ?, connected_to_port_id = ?, notes = ? WHERE id = ?
|
||||
`).run(
|
||||
label ?? port.label,
|
||||
port_type ?? port.port_type,
|
||||
newLinkedId,
|
||||
notes ?? port.notes,
|
||||
req.params.portId
|
||||
);
|
||||
|
||||
// Set new reverse link
|
||||
if (newLinkedId && newLinkedId !== oldLinkedId) {
|
||||
db.prepare('UPDATE ports SET connected_to_port_id = ? WHERE id = ?').run(port.id, newLinkedId);
|
||||
}
|
||||
|
||||
res.json(db.prepare('SELECT * FROM ports WHERE id = ?').get(req.params.portId));
|
||||
});
|
||||
|
||||
// DELETE /api/components/:componentId/ports/:portId
|
||||
router.delete('/:componentId/ports/:portId', (req, res) => {
|
||||
db.prepare('DELETE FROM ports WHERE id = ? AND component_id = ?').run(req.params.portId, req.params.componentId);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,95 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../db');
|
||||
const router = express.Router();
|
||||
|
||||
// GET /api/racks?roomId=
|
||||
router.get('/', (req, res) => {
|
||||
const { roomId } = req.query;
|
||||
if (!roomId) return res.status(400).json({ error: 'roomId query param required' });
|
||||
|
||||
const racks = db.prepare(`
|
||||
SELECT ra.*, COUNT(c.id) as component_count
|
||||
FROM racks ra
|
||||
LEFT JOIN components c ON c.rack_id = ra.id
|
||||
WHERE ra.room_id = ?
|
||||
GROUP BY ra.id
|
||||
ORDER BY ra.name
|
||||
`).all(roomId);
|
||||
res.json(racks);
|
||||
});
|
||||
|
||||
// GET /api/racks/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(req.params.id);
|
||||
if (!rack) return res.status(404).json({ error: 'Rack not found' });
|
||||
|
||||
const components = db.prepare(`
|
||||
SELECT * FROM components WHERE rack_id = ? ORDER BY position ASC NULLS LAST, name ASC
|
||||
`).all(req.params.id);
|
||||
|
||||
// Attach ports to each component so the graphic view can show active ports
|
||||
const componentsWithPorts = components.map(c => {
|
||||
const ports = db.prepare('SELECT * FROM ports WHERE component_id = ? ORDER BY port_number').all(c.id);
|
||||
return { ...c, ports };
|
||||
});
|
||||
|
||||
let room = null, site = null;
|
||||
if (rack.room_id) {
|
||||
room = db.prepare('SELECT id, name, site_id FROM rooms WHERE id = ?').get(rack.room_id);
|
||||
if (room) site = db.prepare('SELECT id, name FROM sites WHERE id = ?').get(room.site_id);
|
||||
}
|
||||
|
||||
res.json({ ...rack, components: componentsWithPorts, room, site });
|
||||
});
|
||||
|
||||
// POST /api/racks
|
||||
router.post('/', (req, res) => {
|
||||
const { room_id, name, total_units, manufacturer, model, notes } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'name is required' });
|
||||
|
||||
if (room_id) {
|
||||
const room = db.prepare('SELECT id FROM rooms WHERE id = ?').get(room_id);
|
||||
if (!room) return res.status(404).json({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO racks (id, room_id, name, total_units, manufacturer, model, notes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(id, room_id || null, name, total_units || 42, manufacturer || null, model || null, notes || '');
|
||||
|
||||
res.status(201).json(db.prepare('SELECT * FROM racks WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
// PUT /api/racks/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const rack = db.prepare('SELECT * FROM racks WHERE id = ?').get(req.params.id);
|
||||
if (!rack) return res.status(404).json({ error: 'Rack not found' });
|
||||
|
||||
const { name, total_units, manufacturer, model, notes } = req.body;
|
||||
db.prepare(`
|
||||
UPDATE racks SET name = ?, total_units = ?, manufacturer = ?, model = ?, notes = ?,
|
||||
updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(
|
||||
name ?? rack.name,
|
||||
total_units ?? rack.total_units,
|
||||
manufacturer ?? rack.manufacturer,
|
||||
model ?? rack.model,
|
||||
notes ?? rack.notes,
|
||||
req.params.id
|
||||
);
|
||||
|
||||
res.json(db.prepare('SELECT * FROM racks WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
// DELETE /api/racks/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
const rack = db.prepare('SELECT id FROM racks WHERE id = ?').get(req.params.id);
|
||||
if (!rack) return res.status(404).json({ error: 'Rack not found' });
|
||||
|
||||
db.prepare('DELETE FROM racks WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,77 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../db');
|
||||
const router = express.Router();
|
||||
|
||||
// GET /api/rooms?siteId=
|
||||
router.get('/', (req, res) => {
|
||||
const { siteId } = req.query;
|
||||
if (!siteId) return res.status(400).json({ error: 'siteId query param required' });
|
||||
|
||||
const rooms = db.prepare(`
|
||||
SELECT r.*, COUNT(ra.id) as rack_count
|
||||
FROM rooms r
|
||||
LEFT JOIN racks ra ON ra.room_id = r.id
|
||||
WHERE r.site_id = ?
|
||||
GROUP BY r.id
|
||||
ORDER BY r.name
|
||||
`).all(siteId);
|
||||
res.json(rooms);
|
||||
});
|
||||
|
||||
// GET /api/rooms/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const room = db.prepare('SELECT * FROM rooms WHERE id = ?').get(req.params.id);
|
||||
if (!room) return res.status(404).json({ error: 'Room not found' });
|
||||
|
||||
const racks = db.prepare(`
|
||||
SELECT ra.*, COUNT(c.id) as component_count
|
||||
FROM racks ra
|
||||
LEFT JOIN components c ON c.rack_id = ra.id
|
||||
WHERE ra.room_id = ?
|
||||
GROUP BY ra.id
|
||||
ORDER BY ra.name
|
||||
`).all(req.params.id);
|
||||
|
||||
const site = db.prepare('SELECT id, name FROM sites WHERE id = ?').get(room.site_id);
|
||||
res.json({ ...room, racks, site });
|
||||
});
|
||||
|
||||
// POST /api/rooms
|
||||
router.post('/', (req, res) => {
|
||||
const { site_id, name, notes } = req.body;
|
||||
if (!site_id || !name) return res.status(400).json({ error: 'site_id and name are required' });
|
||||
|
||||
const site = db.prepare('SELECT id FROM sites WHERE id = ?').get(site_id);
|
||||
if (!site) return res.status(404).json({ error: 'Site not found' });
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare('INSERT INTO rooms (id, site_id, name, notes) VALUES (?, ?, ?, ?)').run(
|
||||
id, site_id, name, notes || ''
|
||||
);
|
||||
res.status(201).json(db.prepare('SELECT * FROM rooms WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
// PUT /api/rooms/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const room = db.prepare('SELECT * FROM rooms WHERE id = ?').get(req.params.id);
|
||||
if (!room) return res.status(404).json({ error: 'Room not found' });
|
||||
|
||||
const { name, notes } = req.body;
|
||||
db.prepare(`
|
||||
UPDATE rooms SET name = ?, notes = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
|
||||
`).run(name ?? room.name, notes ?? room.notes, req.params.id);
|
||||
|
||||
res.json(db.prepare('SELECT * FROM rooms WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
// DELETE /api/rooms/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
const room = db.prepare('SELECT id FROM rooms WHERE id = ?').get(req.params.id);
|
||||
if (!room) return res.status(404).json({ error: 'Room not found' });
|
||||
|
||||
db.prepare('DELETE FROM rooms WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,70 @@
|
||||
const express = require('express');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../db');
|
||||
const router = express.Router();
|
||||
|
||||
// GET /api/sites
|
||||
router.get('/', (req, res) => {
|
||||
const sites = db.prepare(`
|
||||
SELECT s.*, COUNT(r.id) as room_count
|
||||
FROM sites s
|
||||
LEFT JOIN rooms r ON r.site_id = s.id
|
||||
GROUP BY s.id
|
||||
ORDER BY s.name
|
||||
`).all();
|
||||
res.json(sites);
|
||||
});
|
||||
|
||||
// GET /api/sites/:id
|
||||
router.get('/:id', (req, res) => {
|
||||
const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(req.params.id);
|
||||
if (!site) return res.status(404).json({ error: 'Site not found' });
|
||||
|
||||
const rooms = db.prepare(`
|
||||
SELECT r.*, COUNT(ra.id) as rack_count
|
||||
FROM rooms r
|
||||
LEFT JOIN racks ra ON ra.room_id = r.id
|
||||
WHERE r.site_id = ?
|
||||
GROUP BY r.id
|
||||
ORDER BY r.name
|
||||
`).all(req.params.id);
|
||||
|
||||
res.json({ ...site, rooms });
|
||||
});
|
||||
|
||||
// POST /api/sites
|
||||
router.post('/', (req, res) => {
|
||||
const { name, location, notes } = req.body;
|
||||
if (!name) return res.status(400).json({ error: 'Name is required' });
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare('INSERT INTO sites (id, name, location, notes) VALUES (?, ?, ?, ?)').run(
|
||||
id, name, location || null, notes || ''
|
||||
);
|
||||
res.status(201).json(db.prepare('SELECT * FROM sites WHERE id = ?').get(id));
|
||||
});
|
||||
|
||||
// PUT /api/sites/:id
|
||||
router.put('/:id', (req, res) => {
|
||||
const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(req.params.id);
|
||||
if (!site) return res.status(404).json({ error: 'Site not found' });
|
||||
|
||||
const { name, location, notes } = req.body;
|
||||
db.prepare(`
|
||||
UPDATE sites SET name = ?, location = ?, notes = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(name ?? site.name, location ?? site.location, notes ?? site.notes, req.params.id);
|
||||
|
||||
res.json(db.prepare('SELECT * FROM sites WHERE id = ?').get(req.params.id));
|
||||
});
|
||||
|
||||
// DELETE /api/sites/:id
|
||||
router.delete('/:id', (req, res) => {
|
||||
const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(req.params.id);
|
||||
if (!site) return res.status(404).json({ error: 'Site not found' });
|
||||
|
||||
db.prepare('DELETE FROM sites WHERE id = ?').run(req.params.id);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user