pediatric-ai-scribe-v3/src/routes/diagrams.js
2026-05-08 00:23:00 +02:00

114 lines
4.3 KiB
JavaScript

// ============================================================
// MERMAID DIAGRAMS ROUTES — per-user clinical pathway diagrams.
// Pure CRUD, auth-gated. Title/source/notes encrypted at rest
// using the same crypto helper as personal_notes.
// ============================================================
var express = require('express');
var router = express.Router();
var db = require('../db/database');
var { authMiddleware } = require('../middleware/auth');
var logger = require('../utils/logger');
var cryptoUtil = require('../utils/crypto');
router.use(authMiddleware);
var MAX_TITLE = 200;
var MAX_SOURCE = 50000;
var MAX_NOTES = 10000;
var MAX_DIAGRAMS_PER_USER = 200;
function decryptRow(row) {
if (!row) return row;
try { row.title = cryptoUtil.decryptString(row.title); } catch (e) {}
try { row.source = cryptoUtil.decryptString(row.source); } catch (e) {}
try { row.notes = cryptoUtil.decryptString(row.notes); } catch (e) {}
return row;
}
function clip(s, n) {
return String(s == null ? '' : s).trim().substring(0, n);
}
router.get('/diagrams', async function (req, res) {
try {
var rows = await db.all(
'SELECT id, title, source, notes, created_at, updated_at FROM mermaid_diagrams WHERE user_id = $1 ORDER BY updated_at DESC',
[req.user.id]
);
rows.forEach(decryptRow);
res.json({ success: true, diagrams: rows });
} catch (err) {
console.error('[diagrams] list failed:', err.message);
res.status(500).json({ error: 'Request failed' });
}
});
router.get('/diagrams/:id', async function (req, res) {
try {
var row = await db.get(
'SELECT id, title, source, notes, created_at, updated_at FROM mermaid_diagrams WHERE id = $1 AND user_id = $2',
[req.params.id, req.user.id]
);
if (!row) return res.status(404).json({ error: 'Diagram not found' });
decryptRow(row);
res.json({ success: true, diagram: row });
} catch (err) {
res.status(500).json({ error: 'Request failed' });
}
});
router.post('/diagrams', async function (req, res) {
try {
var title = clip(req.body.title, MAX_TITLE) || 'Untitled diagram';
var source = clip(req.body.source, MAX_SOURCE);
var notes = clip(req.body.notes, MAX_NOTES);
var count = await db.get('SELECT COUNT(*) as cnt FROM mermaid_diagrams WHERE user_id = $1', [req.user.id]);
if (count && Number(count.cnt) >= MAX_DIAGRAMS_PER_USER) {
return res.status(400).json({ error: 'Maximum ' + MAX_DIAGRAMS_PER_USER + ' diagrams per user' });
}
var result = await db.run(
'INSERT INTO mermaid_diagrams (user_id, title, source, notes) VALUES ($1, $2, $3, $4) RETURNING id',
[req.user.id, cryptoUtil.encryptString(title), cryptoUtil.encryptString(source), cryptoUtil.encryptString(notes)]
);
logger.audit(req.user.id, 'diagram_create', 'Created mermaid diagram', req, { category: 'clinical' });
res.json({ success: true, id: result.lastInsertRowid });
} catch (err) {
console.error('[diagrams] create failed:', err.message);
res.status(500).json({ error: 'Request failed' });
}
});
router.put('/diagrams/:id', async function (req, res) {
try {
var existing = await db.get('SELECT id FROM mermaid_diagrams WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
if (!existing) return res.status(404).json({ error: 'Diagram not found' });
var title = clip(req.body.title, MAX_TITLE) || 'Untitled diagram';
var source = clip(req.body.source, MAX_SOURCE);
var notes = clip(req.body.notes, MAX_NOTES);
await db.run(
'UPDATE mermaid_diagrams SET title = $1, source = $2, notes = $3, updated_at = NOW() WHERE id = $4 AND user_id = $5',
[cryptoUtil.encryptString(title), cryptoUtil.encryptString(source), cryptoUtil.encryptString(notes), req.params.id, req.user.id]
);
res.json({ success: true });
} catch (err) {
console.error('[diagrams] update failed:', err.message);
res.status(500).json({ error: 'Request failed' });
}
});
router.delete('/diagrams/:id', async function (req, res) {
try {
await db.run('DELETE FROM mermaid_diagrams WHERE id = $1 AND user_id = $2', [req.params.id, req.user.id]);
logger.audit(req.user.id, 'diagram_delete', 'Deleted mermaid diagram', req, { category: 'clinical' });
res.json({ success: true });
} catch (err) {
res.status(500).json({ error: 'Request failed' });
}
});
module.exports = router;