fix: classroom review — 11 исправлений из code review

- sessions.js: endSession закрывает classroom_attendance (left_at), чистит classroom_muted
- sessions.js: joinSession восстанавливает mute-состояние при реконнекте
- strokes.js: updateStroke проверяет авторство штриха (не только canDraw)
- strokes.js: clearPage валидирует page_num как положительное целое
- strokes.js: postStrokes ограничивает массив 500 штрихами
- pages.js: duplicatePage сохраняет user_id при копировании штрихов
- pages.js: changePage валидирует page_num
- pages.js: updatePageTemplate делает INSERT OR IGNORE перед UPDATE
- permissions.js: mutePeer сохраняет в classroom_muted; добавлен unmutePeer
- permissions.js: getOnlineStudents не возвращает email
- chat.js: exportChat экранирует переводы строк в именах и сообщениях
- guestClassroom.js: санитизация имени гостя (убираем HTML-символы)
- ws-server.js: mute_peer сохраняет в БД; добавлен обработчик unmute_peer
- routes/classroom.js: rate-limit для cursor/preview/signal/strokes; маршрут DELETE /mute
- migrations/001_classroom_muted.sql: новая таблица classroom_muted
This commit is contained in:
Maxim Dolgolyov
2026-05-07 14:26:19 +03:00
parent 90f6a1d91e
commit c0f20ef020
10 changed files with 85 additions and 27 deletions
+7 -5
View File
@@ -41,8 +41,9 @@ function addPage(req, res) {
function changePage(req, res) {
const sessionId = Number(req.params.id);
const { page_num } = req.body;
if (!page_num) return res.status(400).json({ error: 'page_num required' });
const page_num = Number(req.body?.page_num);
if (!Number.isInteger(page_num) || page_num < 1)
return res.status(400).json({ error: 'page_num required (positive integer)' });
const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=? AND status='active'`).get(sessionId);
if (!session) return res.status(404).json({ error: 'Сессия не активна' });
@@ -64,6 +65,7 @@ function updatePageTemplate(req, res) {
if (session.teacher_id !== req.user.id && req.user.role !== 'admin')
return res.status(403).json({ error: 'Нет доступа' });
db.prepare('INSERT OR IGNORE INTO classroom_pages (session_id, page_num, template) VALUES (?,?,?)').run(sessionId, session.current_page, template);
db.prepare('UPDATE classroom_pages SET template=? WHERE session_id=? AND page_num=?').run(template, sessionId, session.current_page);
emitToSession(sessionId, { type: 'classroom_template_changed', sessionId, pageNum: session.current_page, template });
res.json({ ok: true, template });
@@ -120,9 +122,9 @@ function duplicatePage(req, res) {
db.prepare('INSERT OR IGNORE INTO classroom_pages (session_id, page_num, template, name) VALUES (?,?,?,?)').run(sessionId, newPage, srcTpl, newName);
const strokes = db.prepare('SELECT tool, data FROM classroom_strokes WHERE session_id=? AND page_num=? ORDER BY seq').all(sessionId, srcPage);
const ins = db.prepare('INSERT INTO classroom_strokes (session_id, page_num, tool, data, seq) VALUES (?,?,?,?,?)');
db.transaction(() => { strokes.forEach((s, i) => ins.run(sessionId, newPage, s.tool, s.data, i + 1)); })();
const strokes = db.prepare('SELECT tool, data, user_id FROM classroom_strokes WHERE session_id=? AND page_num=? ORDER BY seq').all(sessionId, srcPage);
const ins = db.prepare('INSERT INTO classroom_strokes (session_id, page_num, tool, data, user_id, seq) VALUES (?,?,?,?,?,?)');
db.transaction(() => { strokes.forEach((s, i) => ins.run(sessionId, newPage, s.tool, s.data, s.user_id, i + 1)); })();
db.prepare('UPDATE classroom_sessions SET current_page=? WHERE id=?').run(newPage, sessionId);
emitToSession(sessionId, { type: 'classroom_page_duplicated', sessionId, srcPage, newPage, template: srcTpl, name: newName });