const router = require('express').Router(); const multer = require('multer'); const path = require('path'); const crypto = require('crypto'); const { authMiddleware, requireRole, requirePermission } = require('../middleware/auth'); const rateLimit = require('../middleware/rateLimit'); const c = require('../controllers/classroomController'); /* ── multer for chat image attachments ─────────────────────────────────── */ const _chatUploadsDir = path.join(__dirname, '../../uploads/chat'); const _chatStorage = multer.diskStorage({ destination: (req, file, cb) => cb(null, _chatUploadsDir), filename: (req, file, cb) => { const ext = path.extname(file.originalname).toLowerCase().replace(/[^.a-z0-9]/g, ''); cb(null, crypto.randomBytes(14).toString('hex') + ext); }, }); const chatUpload = multer({ storage: _chatStorage, limits: { fileSize: 5 * 1024 * 1024 }, fileFilter: (req, file, cb) => { const ok = ['image/jpeg','image/png','image/gif','image/webp'].includes(file.mimetype); cb(null, ok); }, }); const teacher = [authMiddleware, requireRole('teacher', 'admin')]; const auth = [authMiddleware]; const chatLimiter = rateLimit({ windowMs: 5_000, max: 5, message: 'Слишком много сообщений, подождите', byUser: true }); const reactionLimiter = rateLimit({ windowMs: 5_000, max: 15, message: 'Слишком много реакций, подождите', byUser: true }); const handLimiter = rateLimit({ windowMs: 5_000, max: 5, message: 'Не так часто', byUser: true }); const cursorLimiter = rateLimit({ windowMs: 2_000, max: 60, message: 'Слишком часто', byUser: true }); const previewLimiter = rateLimit({ windowMs: 2_000, max: 60, message: 'Слишком часто', byUser: true }); const signalLimiter = rateLimit({ windowMs: 10_000, max: 30, message: 'Слишком много сигналов', byUser: true }); const strokesLimiter = rateLimit({ windowMs: 5_000, max: 100, message: 'Слишком много штрихов', byUser: true }); // Template library — MUST be before /:id to avoid shadowing router.get('/admin/active', ...teacher, c.adminGetActiveSessions); router.get('/admin/sessions', ...teacher, c.adminGetAllSessions); router.get('/admin/teachers-list', ...teacher, c.adminGetTeachersList); router.get('/templates', ...teacher, c.getTemplates); router.delete('/templates/:tid', ...teacher, c.deleteTemplate); // History — MUST be before /:id to avoid shadowing router.get('/my/history', ...auth, c.getMyHistory); router.get('/class/:classId/history', ...auth, c.getClassHistory); // Session lifecycle router.post('/', ...teacher, requirePermission('classroom.host'), c.createSession); router.get('/online-students', ...teacher, c.getOnlineStudents); router.get('/my/session', ...auth, c.getMySession); router.get('/class/:classId/active', ...auth, c.getActiveSession); router.get('/my/active', ...auth, c.getMyActive); router.get('/:id', ...auth, c.getSession); router.delete('/:id', ...teacher, c.endSession); // Attendance router.post('/:id/join', ...auth, c.joinSession); router.post('/:id/leave', ...auth, c.leaveSession); router.get('/:id/participants', ...auth, c.getParticipants); router.get('/:id/attendance', ...teacher, c.getAttendance); // Chat router.post('/:id/chat', ...auth, chatLimiter, c.sendChat); router.get('/:id/chat', ...auth, c.getChat); router.post('/:id/chat/upload', ...auth, chatUpload.single('file'), c.uploadChatAttachment); router.post('/:id/chat/:msgId/react', ...auth, reactionLimiter, c.reactToMessage); // WebRTC signaling router.post('/:id/signal', ...auth, signalLimiter, c.signal); // Whiteboard strokes router.post('/:id/strokes', ...auth, strokesLimiter, c.postStrokes); router.get('/:id/strokes', ...auth, c.getStrokes); router.delete('/:id/strokes/:strokeId', ...teacher, c.deleteStroke); router.patch('/:id/strokes/:strokeId', ...auth, c.updateStroke); router.post('/:id/stroke-preview', ...auth, previewLimiter, c.previewStroke); // Multi-page router.get('/:id/pages', ...auth, c.getPages); router.post('/:id/pages', ...teacher, c.addPage); router.put('/:id/page', ...teacher, c.changePage); router.patch('/:id/page-template', ...teacher, c.updatePageTemplate); router.patch('/:id/board-theme', ...teacher, c.updateBoardTheme); router.patch('/:id/pages/:pageNum/name', ...teacher, c.renamePage); router.post('/:id/pages/:pageNum/duplicate', ...teacher, c.duplicatePage); router.delete('/:id/pages/:pageNum', ...teacher, c.deletePage); // Hand raise router.post('/:id/hand', ...auth, handLimiter, c.raiseHand); router.delete('/:id/hand', ...auth, handLimiter, c.lowerHand); router.get('/:id/hands', ...auth, c.getHands); // Whiteboard: clear page router.post('/:id/clear-page', ...teacher, c.clearPage); // WebRTC: mute/unmute peer, screen share broadcast router.post('/:id/mute', ...teacher, c.mutePeer); router.delete('/:id/mute', ...teacher, c.unmutePeer); router.post('/:id/screen', ...teacher, c.screenStart); router.delete('/:id/screen', ...teacher, c.screenStop); // Simulation: open/close/state/mode for all participants router.post('/:id/sim', ...teacher, c.simOpen); router.delete('/:id/sim', ...teacher, c.simClose); router.post('/:id/sim/state', ...teacher, c.simState); router.post('/:id/sim/mode', ...teacher, c.simMode); router.post('/:id/sim/annotate', ...teacher, c.simAnnotate); // Textbook: open/close/navigate for all participants router.post('/:id/textbook', ...teacher, c.textbookOpen); router.delete('/:id/textbook', ...teacher, c.textbookClose); router.post('/:id/textbook/nav', ...teacher, c.textbookNav); router.post('/:id/textbook/mode', ...teacher, c.textbookMode); // Cursor broadcast (all participants) router.post('/:id/cursor', ...auth, cursorLimiter, c.broadcastCursor); // Message pin (teacher only) router.post('/:id/chat/:msgId/pin', ...teacher, c.pinMessage); // Collaborative drawing permissions router.post('/:id/allow-draw/:userId', ...teacher, c.allowDraw); router.delete('/:id/allow-draw/:userId', ...teacher, c.revokeDraw); // Session notes (per user) router.get('/:id/notes', ...auth, c.getNotes); router.put('/:id/notes', ...auth, c.saveNotes); router.get('/:id/notes/all', ...teacher, c.getAllNotes); // Session summary & history detail router.get('/:id/summary', ...auth, c.getSessionSummary); router.get('/:id/chat/export', ...teacher, c.exportChat); router.delete('/:id/history', ...teacher, c.deleteHistorySession); // Save current session as template router.post('/:id/save-template', ...teacher, c.saveTemplate); // Load template into current session router.post('/:id/load-template', ...teacher, c.loadTemplate); // Guest token (generate/revoke/get) router.get('/:id/guest-token', ...teacher, c.getGuestToken); router.post('/:id/guest-token', ...teacher, c.generateGuestToken); router.delete('/:id/guest-token', ...teacher, c.revokeGuestToken); module.exports = router;