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:
Maxim Dolgolyov
2026-04-12 10:10:37 +03:00
commit be4d43105e
204 changed files with 118117 additions and 0 deletions
+54
View File
@@ -0,0 +1,54 @@
'use strict';
const db = require('../db/db');
/**
* Factory: returns middleware that loads a resource by req.params[paramKey],
* verifies ownership against req.user.id, and attaches the row as req.resource.
*
* Simple usage (table lookup):
* requireOwnership({ table: 'tests', ownerField: 'created_by' })
*
* Complex usage (JOIN / aliased field):
* requireOwnership({
* fetchFn: id => db.prepare(`
* SELECT a.id, COALESCE(c.teacher_id, a.created_by) AS teacher_id
* FROM assignments a LEFT JOIN classes c ON c.id = a.class_id WHERE a.id = ?
* `).get(id),
* ownerField: 'teacher_id',
* })
*
* Options:
* table — DB table name for `SELECT * FROM {table} WHERE id = ?`
* fetchFn — (id) => row|undefined (alternative to `table`)
* ownerField — row field compared to req.user.id (required)
* paramKey — req.params key for the record ID (default: 'id')
* adminBypass — admin role always passes (default: true)
*/
const ALLOWED_TABLES = new Set(['tests','classes','assignments','questions','courses','lessons','files','folders','shop_items','live_sessions']);
function requireOwnership({ table, fetchFn, ownerField, paramKey = 'id', adminBypass = true }) {
if (table && !ALLOWED_TABLES.has(table)) throw new Error(`requireOwnership: unknown table "${table}"`);
// Pre-compile statement once at middleware creation (server startup)
const stmt = table ? db.prepare(`SELECT * FROM ${table} WHERE id = ?`) : null;
const fetch = fetchFn || (id => stmt.get(id));
return (req, res, next) => {
const row = fetch(req.params[paramKey]);
if (!row) return res.status(404).json({ error: 'Not found' });
if (adminBypass && req.user?.role === 'admin') {
req.resource = row;
return next();
}
if (row[ownerField] !== req.user?.id) {
return res.status(403).json({ error: 'Forbidden' });
}
req.resource = row;
next();
};
}
module.exports = { requireOwnership };