@
feat: teacher_students — назначения ученикам без класса
Новая модель «Мои ученики» — учитель связывает с собой учеников
независимо от классов (репетиторский сценарий).
Backend:
- Таблица teacher_students (teacher_id, student_id, added_at, note)
+ индекс на student_id для обратного поиска
- GET/POST/PATCH/DELETE /api/teacher-students — управление списком
- Добавление по email с проверкой роли student/free_student
- Уведомление ученику при добавлении
- createDirectAssignment: проверка inClass расширена до
inClass OR (teacher_id, student_id) в teacher_students
- listStudents (/api/classes/students): возвращает объединение
учеников из классов + из teacher_students. Это автоматически
обновляет student-picker в /textbooks без правок UI.
Frontend:
- /my-students — таблица личных учеников + форма добавления
по email + заметка + счётчик созданных заданий
- Сайдбар: пункт «Мои ученики» (user-plus, только для учителей)
Миграция 006_teacher_students.sql.
Что работает end-to-end:
- Добавить ученика на /my-students
- Открыть /textbooks → «Назначить» → «Ученику» → ученик ищется
в общем списке (классовые + личные)
- Создаётся запись в assignments с user_id, видна ученику на
дашборде с пометкой «Личное задание»
@
This commit is contained in:
@@ -529,14 +529,21 @@ function createDirectAssignment(req, res) {
|
||||
if (!student) return res.status(404).json({ error: 'Ученик с таким email не найден' });
|
||||
}
|
||||
|
||||
// Учитель может выдать личное задание только ученику из своего класса
|
||||
// Учитель может выдать личное задание ученику из своего класса ИЛИ из «Мои ученики»
|
||||
if (req.user.role === 'teacher') {
|
||||
const inClass = db.prepare(`
|
||||
SELECT 1 FROM class_members cm
|
||||
JOIN classes c ON c.id = cm.class_id
|
||||
WHERE cm.user_id = ? AND c.teacher_id = ?
|
||||
`).get(student.id, req.user.id);
|
||||
if (!inClass) return res.status(403).json({ error: 'Ученик не входит ни в один из ваших классов' });
|
||||
const linked = inClass ? null : db.prepare(
|
||||
'SELECT 1 FROM teacher_students WHERE teacher_id=? AND student_id=?'
|
||||
).get(req.user.id, student.id);
|
||||
if (!inClass && !linked) {
|
||||
return res.status(403).json({
|
||||
error: 'Ученик не входит в ваши классы и не добавлен в «Мои ученики». Добавьте его на странице «Мои ученики».',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test_id = test_id ? Number(test_id) : null;
|
||||
|
||||
@@ -355,14 +355,20 @@ function listStudents(req, res) {
|
||||
).all();
|
||||
return res.json(rows);
|
||||
}
|
||||
// Teacher: only students in their classes
|
||||
// Teacher: students in their classes + personal students (teacher_students)
|
||||
const rows = db.prepare(`
|
||||
SELECT DISTINCT u.id, u.name, u.email FROM users u
|
||||
JOIN class_members cm ON cm.user_id = u.id
|
||||
JOIN classes c ON c.id = cm.class_id
|
||||
WHERE c.teacher_id = ? AND u.role IN ('student','free_student')
|
||||
ORDER BY u.name
|
||||
`).all(req.user.id);
|
||||
SELECT id, name, email FROM (
|
||||
SELECT DISTINCT u.id, u.name, u.email FROM users u
|
||||
JOIN class_members cm ON cm.user_id = u.id
|
||||
JOIN classes c ON c.id = cm.class_id
|
||||
WHERE c.teacher_id = ? AND u.role IN ('student','free_student')
|
||||
UNION
|
||||
SELECT u.id, u.name, u.email FROM users u
|
||||
JOIN teacher_students ts ON ts.student_id = u.id
|
||||
WHERE ts.teacher_id = ? AND u.role IN ('student','free_student')
|
||||
)
|
||||
ORDER BY name
|
||||
`).all(req.user.id, req.user.id);
|
||||
res.json(rows);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user