Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
161 lines
5.5 KiB
JavaScript
161 lines
5.5 KiB
JavaScript
/**
|
|
* Labs Edges Routes
|
|
* CRUD for edges between nodes (both v4 and legacy)
|
|
*/
|
|
import { Router } from 'express';
|
|
import { dbGet, dbRun, dbAll } from '../../../database/connection.js';
|
|
import { apiLogger } from '../../../utils/logger.js';
|
|
import { success, created, badRequest } from '../../../utils/response.js';
|
|
import { generateId } from './_helpers.js';
|
|
|
|
const router = Router();
|
|
|
|
// POST /labs/:id/edges - Create edge (supports both GOD CRM and MindWorkflow formats)
|
|
router.post('/:id/edges', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
// Support both formats: GOD CRM (source_node_id) and MindWorkflow (from)
|
|
const source_node_id = req.body.source_node_id || req.body.from;
|
|
const target_node_id = req.body.target_node_id || req.body.to;
|
|
const source_handle = req.body.source_handle || req.body.sourceHandle;
|
|
const target_handle = req.body.target_handle || req.body.targetHandle;
|
|
const label = req.body.label;
|
|
|
|
if (!source_node_id || !target_node_id) return badRequest(res, 'source and target required');
|
|
|
|
const edge_id = generateId('edge');
|
|
await dbRun(
|
|
`INSERT INTO labs_edges (lab_id, edge_id, source_node_id, target_node_id, source_handle, target_handle, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
|
|
[id, edge_id, source_node_id, target_node_id, source_handle || null, target_handle || null]
|
|
);
|
|
|
|
// Also update the source node's edges array if using JSON storage
|
|
try {
|
|
const sourceNode = await dbGet(
|
|
`SELECT * FROM "${id}" WHERE node_id = $1`,
|
|
[source_node_id]
|
|
);
|
|
|
|
if (sourceNode) {
|
|
let edges = [];
|
|
try {
|
|
edges = sourceNode.edges ? (typeof sourceNode.edges === 'string' ? JSON.parse(sourceNode.edges) : sourceNode.edges) : [];
|
|
} catch (e) {
|
|
edges = [];
|
|
}
|
|
|
|
// Add target to edges array if not already present
|
|
if (!edges.includes(target_node_id)) {
|
|
edges.push(target_node_id);
|
|
await dbRun(
|
|
`UPDATE "${id}" SET edges = $1 WHERE node_id = $2`,
|
|
[JSON.stringify(edges), source_node_id]
|
|
);
|
|
}
|
|
}
|
|
} catch (tableErr) {
|
|
// Table might not exist yet, that's ok
|
|
apiLogger.debug({ tableErr }, 'Could not update node edges array');
|
|
}
|
|
|
|
const edge = await dbGet('SELECT * FROM labs_edges WHERE edge_id = $1', [edge_id]);
|
|
|
|
// Return in MindWorkflow format
|
|
const allEdges = await dbAll('SELECT * FROM labs_edges WHERE lab_id = $1', [id]);
|
|
created(res, {
|
|
edge,
|
|
edges: allEdges.map(e => ({
|
|
id: e.edge_id,
|
|
from: e.source_node_id,
|
|
to: e.target_node_id,
|
|
sourceHandle: e.source_handle,
|
|
targetHandle: e.target_handle
|
|
}))
|
|
}, 'Edge created');
|
|
} catch (err) {
|
|
apiLogger.error({ err }, 'Failed to create edge');
|
|
badRequest(res, 'Failed to create edge');
|
|
}
|
|
});
|
|
|
|
// POST /projects/:id/edges (backward compatibility alias)
|
|
router.post('/projects/:id/edges', async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { source_node_id, target_node_id, source_handle, target_handle } = req.body;
|
|
if (!source_node_id || !target_node_id) return badRequest(res, 'source and target required');
|
|
const edge_id = generateId('edge');
|
|
await dbRun(
|
|
`INSERT INTO labs_edges (lab_id, edge_id, source_node_id, target_node_id, source_handle, target_handle, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
|
|
[id, edge_id, source_node_id, target_node_id, source_handle || null, target_handle || null]
|
|
);
|
|
const edge = await dbGet('SELECT * FROM labs_edges WHERE edge_id = $1', [edge_id]);
|
|
created(res, edge, 'Edge created');
|
|
} catch (err) {
|
|
apiLogger.error({ err }, 'Failed to create edge');
|
|
badRequest(res, 'Failed to create edge');
|
|
}
|
|
});
|
|
|
|
// DELETE /edges/:edgeId
|
|
router.delete('/edges/:edgeId', async (req, res) => {
|
|
try {
|
|
const { edgeId } = req.params;
|
|
await dbRun('DELETE FROM labs_edges WHERE edge_id = $1', [edgeId]);
|
|
success(res, { deleted: true });
|
|
} catch (err) {
|
|
apiLogger.error({ err }, 'Failed to delete edge');
|
|
badRequest(res, 'Failed to delete edge');
|
|
}
|
|
});
|
|
|
|
// DELETE /:labTableId/edges/:from/:to - Delete edge by source and target node IDs
|
|
router.delete('/:labTableId/edges/:from/:to', async (req, res) => {
|
|
try {
|
|
const { labTableId, from, to } = req.params;
|
|
|
|
// Delete from labs_edges table
|
|
await dbRun(
|
|
'DELETE FROM labs_edges WHERE lab_id = $1 AND source_node_id = $2 AND target_node_id = $3',
|
|
[labTableId, from, to]
|
|
);
|
|
|
|
// Also update the source node's edges array if using JSON storage
|
|
const sourceNode = await dbGet(
|
|
`SELECT * FROM "${labTableId}" WHERE node_id = $1`,
|
|
[from]
|
|
);
|
|
|
|
if (sourceNode && sourceNode.edges) {
|
|
let edges = [];
|
|
try {
|
|
edges = typeof sourceNode.edges === 'string' ? JSON.parse(sourceNode.edges) : sourceNode.edges;
|
|
} catch (e) {
|
|
edges = [];
|
|
}
|
|
|
|
// Remove the target from edges array
|
|
const updatedEdges = edges.filter(e => e !== to && e.to !== to);
|
|
|
|
await dbRun(
|
|
`UPDATE "${labTableId}" SET edges = $1 WHERE node_id = $2`,
|
|
[JSON.stringify(updatedEdges), from]
|
|
);
|
|
}
|
|
|
|
// Return updated edges list
|
|
const remainingEdges = await dbAll(
|
|
'SELECT * FROM labs_edges WHERE lab_id = $1',
|
|
[labTableId]
|
|
);
|
|
|
|
success(res, { edges: remainingEdges });
|
|
} catch (err) {
|
|
apiLogger.error({ err, labTableId: req.params.labTableId }, 'Failed to delete edge');
|
|
badRequest(res, 'Failed to delete edge');
|
|
}
|
|
});
|
|
|
|
export default router;
|