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
+40
View File
@@ -0,0 +1,40 @@
const router = require('express').Router();
const { authMiddleware, requireRole, requirePermission } = require('../middleware/auth');
const validate = require('../middleware/validate');
const rateLimit = require('../middleware/rateLimit');
const {
getMe, getAchievements, getLeaderboard, getXPHistory,
getChallenges, claimChallenge, setGoalTier, getFrames, setFrame,
onLabExperiment,
adminAward, adminReset, adminGamStats, adminGetUser
} = require('../controllers/gamificationController');
const labLimiter = rateLimit({ windowMs: 60_000, max: 30, message: 'Слишком частые запросы лаборатории' });
const labSchema = { body: { reactionsDiscovered: { type: 'number', min: 0, max: 100, integer: true } } };
router.use(authMiddleware);
router.get('/me', getMe);
router.get('/achievements', getAchievements);
router.get('/leaderboard', getLeaderboard);
router.get('/xp-history', getXPHistory);
router.get('/challenges', getChallenges);
router.post('/challenges/:id/claim', requirePermission('gamification.challenges'), claimChallenge);
router.post('/goal-tier', requirePermission('gamification.challenges'), setGoalTier);
router.get('/frames', getFrames);
router.post('/frame', requirePermission('shop.purchase'), setFrame);
/* Lab experiment tracking */
router.post('/lab-activity', requirePermission('simulations.access'), labLimiter, validate(labSchema), (req, res) => {
const discovered = Number(req.body.reactionsDiscovered) || 0;
onLabExperiment(req.user.id, discovered);
res.json({ ok: true });
});
/* Admin routes */
router.post('/admin/award', requireRole('admin', 'teacher'), adminAward);
router.post('/admin/reset', requireRole('admin'), adminReset);
router.get('/admin/stats', requireRole('admin', 'teacher'), adminGamStats);
router.get('/admin/user/:id', requireRole('admin', 'teacher'), adminGetUser);
module.exports = router;