feat(rack-planner): implement port-to-port connections (patch cables) with dynamic SVG visualization layer
This commit is contained in:
@@ -12,6 +12,7 @@ import { vlansRouter } from './routes/vlans';
|
||||
import { serviceMapRouter } from './routes/serviceMap';
|
||||
import { nodesRouter } from './routes/nodes';
|
||||
import { edgesRouter } from './routes/edges';
|
||||
import connectionsRouter from './routes/connections';
|
||||
import { authMiddleware } from './middleware/authMiddleware';
|
||||
import { errorHandler } from './middleware/errorHandler';
|
||||
|
||||
@@ -44,6 +45,7 @@ app.use('/api/vlans', vlansRouter);
|
||||
app.use('/api/maps', serviceMapRouter);
|
||||
app.use('/api/nodes', nodesRouter);
|
||||
app.use('/api/edges', edgesRouter);
|
||||
app.use('/api/connections', connectionsRouter);
|
||||
|
||||
// ---- Serve Vite build in production ----
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
|
||||
37
server/routes/connections.ts
Normal file
37
server/routes/connections.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Router } from 'express';
|
||||
import * as connService from '../services/connectionService';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// POST /api/connections
|
||||
router.post('/', async (req, res, next) => {
|
||||
try {
|
||||
const { fromPortId, toPortId, color, label } = req.body;
|
||||
const conn = await connService.createConnection({ fromPortId, toPortId, color, label });
|
||||
res.status(201).json(conn);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/connections/:id
|
||||
router.delete('/:id', async (req, res, next) => {
|
||||
try {
|
||||
await connService.deleteConnection(req.params.id);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/connections/ports/:p1/:p2 (remove link between two specific ports)
|
||||
router.delete('/ports/:p1/:p2', async (req, res, next) => {
|
||||
try {
|
||||
await connService.deleteByPorts(req.params.p1, req.params.p2);
|
||||
res.json({ success: true });
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
34
server/services/connectionService.ts
Normal file
34
server/services/connectionService.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { prisma } from '../lib/prisma';
|
||||
import { AppError } from '../types/index';
|
||||
|
||||
export async function createConnection(data: { fromPortId: string; toPortId: string; color?: string; label?: string }) {
|
||||
// Check if both ports exist
|
||||
const [from, to] = await Promise.all([
|
||||
prisma.port.findUnique({ where: { id: data.fromPortId } }),
|
||||
prisma.port.findUnique({ where: { id: data.toPortId } }),
|
||||
]);
|
||||
|
||||
if (!from || !to) throw new AppError('One or both ports not found', 404, 'NOT_FOUND');
|
||||
if (from.id === to.id) throw new AppError('Cannot connect a port to itself', 400, 'BAD_REQUEST');
|
||||
|
||||
// Check if ports are already occupied?
|
||||
// (In real life, a port can only have one cable, but we might allow one source and one target per port if we want to be flexible, but better to prevent simple loops)
|
||||
|
||||
// Create connection (if it already exists, use upsert or just throw error; @@unique already handles it)
|
||||
return prisma.connection.create({ data });
|
||||
}
|
||||
|
||||
export async function deleteConnection(id: string) {
|
||||
return prisma.connection.delete({ where: { id } });
|
||||
}
|
||||
|
||||
export async function deleteByPorts(portId1: string, portId2: string) {
|
||||
return prisma.connection.deleteMany({
|
||||
where: {
|
||||
OR: [
|
||||
{ fromPortId: portId1, toPortId: portId2 },
|
||||
{ fromPortId: portId2, toPortId: portId1 },
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -12,6 +12,8 @@ const rackInclude = {
|
||||
vlans: {
|
||||
include: { vlan: true },
|
||||
},
|
||||
sourceConnections: true,
|
||||
targetConnections: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user