diff --git a/backend/src/controllers/studentMaterialsController.js b/backend/src/controllers/studentMaterialsController.js index 24f773a..cc89127 100644 --- a/backend/src/controllers/studentMaterialsController.js +++ b/backend/src/controllers/studentMaterialsController.js @@ -3,6 +3,7 @@ * A user keeps copies of items saved from live lessons; the copies are * independent of the session lifecycle. */ const db = require('../db/db'); +const { emit } = require('../sse'); const KINDS = ['board', 'note', 'link', 'image']; @@ -129,4 +130,44 @@ function deleteCollection(req, res) { res.json({ ok: true }); } -module.exports = { list, create, update, remove, createCollection, updateCollection, deleteCollection }; +/* POST /api/materials/:id/share — teacher hands a material out to a class or + a student. Each recipient gets an independent COPY (survives later edits/ + deletes by the teacher). Body: { classId } | { userId }. */ +function share(req, res) { + const mat = db.prepare('SELECT user_id, kind, title, body, url FROM student_materials WHERE id = ?').get(req.params.id); + if (!mat) return res.status(404).json({ error: 'not found' }); + if (mat.user_id !== req.user.id && req.user.role !== 'admin') return res.status(403).json({ error: 'forbidden' }); + + const b = req.body || {}; + let recipients = []; + if (b.classId) { + const cls = db.prepare('SELECT id, teacher_id FROM classes WHERE id = ?').get(b.classId); + if (!cls) return res.status(404).json({ error: 'class not found' }); + if (cls.teacher_id !== req.user.id && req.user.role !== 'admin') return res.status(403).json({ error: 'not your class' }); + recipients = db.prepare('SELECT user_id FROM class_members WHERE class_id = ?').all(b.classId).map(r => r.user_id); + } else if (b.userId) { + const uid = Number(b.userId); + if (Number.isFinite(uid)) recipients = [uid]; + } else { + return res.status(400).json({ error: 'classId or userId required' }); + } + + const teacherName = (db.prepare('SELECT name FROM users WHERE id = ?').get(req.user.id) || {}).name || 'Учитель'; + const srcTitle = 'Раздатка: ' + teacherName; + const ins = db.prepare(`INSERT INTO student_materials (user_id, kind, title, body, url, source_session_id, source_title) VALUES (?,?,?,?,?,NULL,?)`); + let sent = 0; + db.transaction(() => { + for (const uid of recipients) { + if (!uid || uid === req.user.id) continue; + ins.run(uid, mat.kind, mat.title, mat.body, mat.url, srcTitle); + try { + emit(uid, { type: 'notification', notif_type: 'material_shared', + message: `Новый материал от ${teacherName}: «${mat.title || 'материал'}»`, link: '/my-materials' }); + } catch (e) { /* ignore notify failure */ } + sent++; + } + })(); + res.json({ ok: true, sent }); +} + +module.exports = { list, create, update, remove, createCollection, updateCollection, deleteCollection, share }; diff --git a/backend/src/routes/materials.js b/backend/src/routes/materials.js index 806f0b3..2090da4 100644 --- a/backend/src/routes/materials.js +++ b/backend/src/routes/materials.js @@ -1,11 +1,14 @@ 'use strict'; const express = require('express'); const router = express.Router(); -const { authMiddleware } = require('../middleware/auth'); +const { authMiddleware, requireRole } = require('../middleware/auth'); const c = require('../controllers/studentMaterialsController'); router.use(authMiddleware); +// Teacher hands a material out to a class/student (copies to recipients) +router.post('/:id/share', requireRole('teacher', 'admin'), c.share); + router.get('/', c.list); router.post('/', c.create); diff --git a/frontend/my-materials.html b/frontend/my-materials.html index 186b8cc..e0f58e8 100644 --- a/frontend/my-materials.html +++ b/frontend/my-materials.html @@ -79,7 +79,8 @@ diff --git a/js/api.js b/js/api.js index 564eb7d..98e543f 100644 --- a/js/api.js +++ b/js/api.js @@ -1048,7 +1048,7 @@ window.LS = { crJoin, crLeave, crSendChat, crGetChat, crGetAttendance, crSignal, crGetOnlineStudents, crGetMySession, crGetMyHistory, crGetClassHistory, crGetSessionSummary, crExportChatUrl, crGetAllNotes, crDeleteHistory, crAdminGetAllHistory, crAdminGetTeachersList, - listMaterials, saveMaterial, updateMaterial, deleteMaterial, + listMaterials, saveMaterial, updateMaterial, deleteMaterial, shareMaterial, createMaterialCollection, updateMaterialCollection, deleteMaterialCollection, fcListDecks, fcCreateDeck, fcAddCard, escapeHtml, esc, @@ -1249,6 +1249,7 @@ async function listMaterials() { return req('GET', '/materials'); } async function saveMaterial(data) { return req('POST', '/materials', data); } async function updateMaterial(id, d) { return req('PATCH', `/materials/${id}`, d); } async function deleteMaterial(id) { return req('DELETE', `/materials/${id}`); } +async function shareMaterial(id, d) { return req('POST', `/materials/${id}/share`, d); } async function createMaterialCollection(d) { return req('POST', '/materials/collections', d); } async function updateMaterialCollection(id,d){ return req('PATCH', `/materials/collections/${id}`, d); } async function deleteMaterialCollection(id) { return req('DELETE', `/materials/collections/${id}`); }