refactor: split classroomController.js into 7 domain files (phase 2 of 2)
1618-line monolith split into: classroom/_shared.js — GUEST_EVENTS, emitToSession, hasAccess, canDraw classroom/sessions.js — lifecycle + guest tokens (12 functions) classroom/strokes.js — CRUD + cursor + preview (7 functions) classroom/pages.js — page CRUD + theme (8 functions) classroom/chat.js — messages, reactions, attachments, export (7 functions) classroom/permissions.js — draw, hand, mute, screen, attendance (11 functions) classroom/sim.js — simulation relay (5 functions) classroom/admin.js — history, notes, templates, admin views (14 functions) classroomController.js is now a 9-line re-export facade. routes/classroom.js unchanged. All 65 exports verified. Tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
'use strict';
|
||||
const db = require('../../db/db');
|
||||
const crypto = require('crypto');
|
||||
const { emitToUser, invalidateSession } = require('../../ws-server');
|
||||
const { emitToSession, hasAccess, canDraw } = require('./_shared');
|
||||
|
||||
function createSession(req, res) {
|
||||
const { class_id, user_ids, title = '' } = req.body;
|
||||
const teacher = req.user;
|
||||
|
||||
const classroomEnabled = db.prepare("SELECT value FROM app_settings WHERE key='feature_classroom_enabled'").get();
|
||||
if (classroomEnabled?.value === '0') {
|
||||
return res.status(403).json({ error: 'Модуль онлайн-уроков отключён администратором' });
|
||||
}
|
||||
|
||||
if (!class_id && (!user_ids || !user_ids.length)) {
|
||||
return res.status(400).json({ error: 'Укажите class_id или user_ids' });
|
||||
}
|
||||
|
||||
if (class_id) {
|
||||
const cls = teacher.role === 'admin'
|
||||
? db.prepare('SELECT id, name FROM classes WHERE id=?').get(class_id)
|
||||
: db.prepare('SELECT id, name FROM classes WHERE id=? AND teacher_id=?').get(class_id, teacher.id);
|
||||
if (!cls) return res.status(403).json({ error: 'Нет доступа к классу' });
|
||||
|
||||
db.prepare(`UPDATE classroom_sessions SET status='ended', ended_at=datetime('now')
|
||||
WHERE class_id=? AND status='active'`).run(class_id);
|
||||
}
|
||||
|
||||
const { lastInsertRowid } = db.prepare(
|
||||
`INSERT INTO classroom_sessions (class_id, teacher_id, title) VALUES (?,?,?)`
|
||||
).run(class_id || null, teacher.id, title);
|
||||
|
||||
const sessionId = Number(lastInsertRowid);
|
||||
db.prepare('INSERT INTO classroom_pages (session_id, page_num) VALUES (?,1)').run(sessionId);
|
||||
|
||||
if (!class_id && user_ids) {
|
||||
const ins = db.prepare('INSERT OR IGNORE INTO classroom_invites (session_id, user_id) VALUES (?,?)');
|
||||
for (const uid of user_ids) ins.run(sessionId, uid);
|
||||
}
|
||||
|
||||
const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
emitToSession(sessionId, { type: 'classroom_started', sessionId, title, classId: class_id || null, teacherName: teacher.name });
|
||||
res.json(session);
|
||||
}
|
||||
|
||||
function getSession(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Не найдено' });
|
||||
if (!hasAccess(session, req.user.id, req.user.role))
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
|
||||
const pageCount = db.prepare('SELECT COUNT(*) AS c FROM classroom_pages WHERE session_id=?').get(sessionId).c;
|
||||
const attendance = db.prepare(`
|
||||
SELECT a.user_id, u.name, a.joined_at, a.left_at
|
||||
FROM classroom_attendance a JOIN users u ON u.id = a.user_id
|
||||
WHERE a.session_id=? ORDER BY a.joined_at
|
||||
`).all(sessionId);
|
||||
const drawAllowed = canDraw(sessionId, req.user.id, session);
|
||||
res.json({ ...session, pageCount, attendance, canDraw: drawAllowed });
|
||||
}
|
||||
|
||||
function endSession(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Не найдено' });
|
||||
if (session.teacher_id !== req.user.id && req.user.role !== 'admin')
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
|
||||
db.prepare(`UPDATE classroom_sessions SET status='ended', ended_at=datetime('now') WHERE id=?`).run(sessionId);
|
||||
db.prepare('DELETE FROM classroom_hands WHERE session_id=?').run(sessionId);
|
||||
db.prepare('DELETE FROM classroom_draw_permissions WHERE session_id=?').run(sessionId);
|
||||
emitToSession(sessionId, { type: 'classroom_ended', sessionId });
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
function getActiveSession(req, res) {
|
||||
const classId = Number(req.params.classId);
|
||||
if (req.user.role !== 'teacher' && req.user.role !== 'admin') {
|
||||
const isMember = db.prepare('SELECT 1 FROM class_members WHERE class_id=? AND user_id=?').get(classId, req.user.id);
|
||||
if (!isMember) return res.status(403).json({ error: 'Нет доступа' });
|
||||
}
|
||||
const session = db.prepare(
|
||||
`SELECT * FROM classroom_sessions WHERE class_id=? AND status='active' ORDER BY id DESC LIMIT 1`
|
||||
).get(classId);
|
||||
if (!session) return res.json({ active: false });
|
||||
res.json({ active: true, session });
|
||||
}
|
||||
|
||||
function getMyActive(req, res) {
|
||||
const userId = req.user.id;
|
||||
const sessions = db.prepare(`
|
||||
SELECT s.* FROM classroom_sessions s
|
||||
JOIN classroom_invites i ON i.session_id = s.id
|
||||
WHERE i.user_id=? AND s.status='active' ORDER BY s.id DESC
|
||||
`).all(userId);
|
||||
res.json({ sessions });
|
||||
}
|
||||
|
||||
function joinSession(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=? AND status='active'`).get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Сессия не активна' });
|
||||
if (!hasAccess(session, req.user.id, req.user.role))
|
||||
return res.status(403).json({ error: 'Нет доступа' });
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO classroom_attendance (session_id, user_id)
|
||||
VALUES (?,?)
|
||||
ON CONFLICT(session_id, user_id) DO UPDATE SET joined_at=datetime('now'), left_at=NULL
|
||||
`).run(sessionId, req.user.id);
|
||||
|
||||
invalidateSession(sessionId);
|
||||
emitToSession(sessionId, { type: 'classroom_user_joined', sessionId, userId: req.user.id, userName: req.user.name });
|
||||
|
||||
const drawAllowed = canDraw(sessionId, req.user.id, session) && session.teacher_id !== req.user.id;
|
||||
if (drawAllowed) emitToUser(req.user.id, { type: 'classroom_draw_permitted', sessionId });
|
||||
res.json({ ok: true, canDraw: drawAllowed });
|
||||
}
|
||||
|
||||
function leaveSession(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
db.prepare(`UPDATE classroom_attendance SET left_at=datetime('now')
|
||||
WHERE session_id=? AND user_id=? AND left_at IS NULL`).run(sessionId, req.user.id);
|
||||
|
||||
invalidateSession(sessionId);
|
||||
const session = db.prepare('SELECT * FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (session) emitToSession(sessionId, { type: 'classroom_user_left', sessionId, userId: req.user.id });
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
function signal(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const { target_user_id, payload } = req.body;
|
||||
if (!target_user_id || !payload) return res.status(400).json({ error: 'target_user_id и payload обязательны' });
|
||||
|
||||
const session = db.prepare(`SELECT * FROM classroom_sessions WHERE id=? AND status='active'`).get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Сессия не активна' });
|
||||
if (!hasAccess(session, req.user.id, req.user.role)) return res.status(403).json({ error: 'Нет доступа' });
|
||||
if (!hasAccess(session, target_user_id, 'student')) return res.status(403).json({ error: 'Цель не является участником сессии' });
|
||||
|
||||
emitToUser(target_user_id, { type: 'classroom_signal', sessionId, from: req.user.id, payload });
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
function getMySession(req, res) {
|
||||
const userId = req.user.id;
|
||||
const role = req.user.role;
|
||||
let session = null;
|
||||
|
||||
if (role === 'teacher' || role === 'admin') {
|
||||
session = db.prepare(
|
||||
`SELECT * FROM classroom_sessions WHERE teacher_id=? AND status='active' ORDER BY id DESC LIMIT 1`
|
||||
).get(userId);
|
||||
} else {
|
||||
session = db.prepare(`
|
||||
SELECT cs.* FROM classroom_sessions cs
|
||||
JOIN class_members cm ON cm.class_id = cs.class_id
|
||||
WHERE cm.user_id=? AND cs.status='active' ORDER BY cs.id DESC LIMIT 1
|
||||
`).get(userId);
|
||||
if (!session) {
|
||||
session = db.prepare(`
|
||||
SELECT cs.* FROM classroom_sessions cs
|
||||
JOIN classroom_invites ci ON ci.session_id = cs.id
|
||||
WHERE ci.user_id=? AND cs.status='active' ORDER BY cs.id DESC LIMIT 1
|
||||
`).get(userId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!session) return res.json({ session: null });
|
||||
|
||||
const pageCount = db.prepare('SELECT COUNT(*) AS c FROM classroom_pages WHERE session_id=?').get(session.id).c;
|
||||
const attendance = db.prepare(`
|
||||
SELECT a.user_id, u.name, a.joined_at, a.left_at
|
||||
FROM classroom_attendance a JOIN users u ON u.id = a.user_id
|
||||
WHERE a.session_id=? ORDER BY a.joined_at
|
||||
`).all(session.id);
|
||||
const wasJoined = attendance.some(a => a.user_id === userId);
|
||||
res.json({ session: { ...session, pageCount, attendance }, wasJoined });
|
||||
}
|
||||
|
||||
function generateGuestToken(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare('SELECT id, teacher_id FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Not found' });
|
||||
if (session.teacher_id !== req.user.id && req.user.role !== 'admin')
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
const token = crypto.randomBytes(24).toString('base64url');
|
||||
db.prepare('UPDATE classroom_sessions SET guest_token=? WHERE id=?').run(token, sessionId);
|
||||
res.json({ token, url: `/guest-board.html?token=${token}` });
|
||||
}
|
||||
|
||||
function revokeGuestToken(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare('SELECT id, teacher_id FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Not found' });
|
||||
if (session.teacher_id !== req.user.id && req.user.role !== 'admin')
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
db.prepare('UPDATE classroom_sessions SET guest_token=NULL WHERE id=?').run(sessionId);
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
function getGuestToken(req, res) {
|
||||
const sessionId = Number(req.params.id);
|
||||
const session = db.prepare('SELECT id, teacher_id, guest_token FROM classroom_sessions WHERE id=?').get(sessionId);
|
||||
if (!session) return res.status(404).json({ error: 'Not found' });
|
||||
if (session.teacher_id !== req.user.id && req.user.role !== 'admin')
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
if (!session.guest_token) return res.json({ token: null, url: null });
|
||||
res.json({ token: session.guest_token, url: `/guest-board.html?token=${session.guest_token}` });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createSession, getSession, endSession, getActiveSession, getMyActive,
|
||||
joinSession, leaveSession, signal, getMySession,
|
||||
generateGuestToken, revokeGuestToken, getGuestToken,
|
||||
};
|
||||
Reference in New Issue
Block a user