LearnSpace: full-stack educational whiteboard platform
Node.js/Express backend + vanilla JS frontend. Features: real-time collaborative whiteboard (SSE), multi-page support, LaTeX formulas, shapes/connectors, coordinate systems, number lines, compass, zoom/pan, Catmull-Rom pencil smoothing, ruler/protractor with rotation & resize controls, minimap navigation overlay, auto-measurements, multi-page thumbnails sidebar, PNG export, page templates. Student/teacher workflows: classes, assignments, library, dashboard. Mobile responsive. SQLite (better-sqlite3). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
/* ── Error handling middleware ─────────────────────────────────────────────
|
||||
requestId — attach X-Request-Id to every request (use early in middleware chain)
|
||||
errorHandler — 4-arg Express error handler (use as last app.use)
|
||||
──────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
const crypto = require('crypto');
|
||||
const logger = require('../utils/logger');
|
||||
let _errorLogStmt = null;
|
||||
function getErrorLogStmt() {
|
||||
if (!_errorLogStmt) {
|
||||
try {
|
||||
const db = require('../db/db');
|
||||
_errorLogStmt = db.prepare(
|
||||
'INSERT INTO error_log (level, message, stack, route, method, user_id) VALUES (?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
return _errorLogStmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a unique request ID to req.requestId and sets X-Request-Id header.
|
||||
* Honour an incoming X-Request-Id from trusted proxies/gateways when present.
|
||||
*/
|
||||
function requestId(req, res, next) {
|
||||
const id = req.headers['x-request-id'] || crypto.randomUUID();
|
||||
req.requestId = id;
|
||||
res.setHeader('X-Request-Id', id);
|
||||
next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Global error handler — must be registered AFTER all routes.
|
||||
*
|
||||
* Classifies errors:
|
||||
* operational (4xx) — expected client errors → warn level, message returned as-is
|
||||
* programmer (5xx) — unexpected bugs → error level, stack logged, message hidden in prod
|
||||
*/
|
||||
function errorHandler(err, req, res, _next) {
|
||||
const status = err.status || err.statusCode || 500;
|
||||
const isOperational = status >= 400 && status < 500;
|
||||
const isProd = process.env.NODE_ENV === 'production';
|
||||
|
||||
const meta = {
|
||||
requestId: req.requestId,
|
||||
method: req.method,
|
||||
path: req.path,
|
||||
status,
|
||||
userId: req.user?.id,
|
||||
role: req.user?.role,
|
||||
};
|
||||
|
||||
if (isOperational) {
|
||||
logger.warn(err.message || 'Client error', meta);
|
||||
} else {
|
||||
logger.error(err.message || 'Unhandled error', {
|
||||
...meta,
|
||||
stack: !isProd ? err.stack : undefined,
|
||||
});
|
||||
// Persist to error_log table for admin dashboard
|
||||
try {
|
||||
const s = getErrorLogStmt();
|
||||
if (s) s.run('error', (err.message || 'Unknown').slice(0, 1000), (err.stack || '').slice(0, 4000), req.path, req.method, req.user?.id || null);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const message = isProd && !isOperational
|
||||
? 'Internal server error'
|
||||
: (err.message || 'Server error');
|
||||
|
||||
if (res.headersSent) return;
|
||||
res.status(status).json({ error: message, requestId: req.requestId });
|
||||
}
|
||||
|
||||
module.exports = { requestId, errorHandler };
|
||||
Reference in New Issue
Block a user