fix(materials): личная загрузка картинок без права library.upload
POST /api/files требует teacher/admin + library.upload — поэтому сохранение картинок в «Мои материалы» (вырезка области учебника, обрезка доски, рисунок, аннотация) падало с 403 у учеников и учителей без этого права. Добавлен auth-only эндпоинт POST /api/files/personal (только картинки, is_public=1) + LS.uploadMaterialFile. На него переключены board-clip, material-save, textbook-clip (вырезка области) и рисовалка в my-materials. Загрузка в учительскую библиотеку (library/lesson-editor) не тронута. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -167,6 +167,36 @@ function uploadFile(req, res) {
|
||||
res.status(201).json({ id: r.lastInsertRowid });
|
||||
}
|
||||
|
||||
/* ── POST /api/files/personal ────────────────────────────────────────────
|
||||
* Personal image upload for «Мои материалы» (board crops, drawings, textbook
|
||||
* clips). Allowed for ANY authenticated user (incl. students) — unlike the
|
||||
* teacher library upload above. Image-only; stored is_public=1 so the
|
||||
* material's download URL resolves (and survives teacher «раздача»). */
|
||||
function uploadPersonalFile(req, res) {
|
||||
if (!req.file) return res.status(400).json({ error: 'No file uploaded' });
|
||||
|
||||
const filePath = path.resolve(UPLOADS_DIR, req.file.filename);
|
||||
if (!checkMagicBytes(filePath, req.file.mimetype)) {
|
||||
try { fs.unlinkSync(filePath); } catch {}
|
||||
return res.status(400).json({ error: 'Содержимое файла не соответствует его расширению' });
|
||||
}
|
||||
|
||||
const title = (req.body && req.body.title && req.body.title.trim()) || req.file.originalname || 'Изображение';
|
||||
let r;
|
||||
try {
|
||||
r = db.prepare(`
|
||||
INSERT INTO files (title, original_name, stored_name, mimetype, size, is_public, uploaded_by)
|
||||
VALUES (?, ?, ?, ?, ?, 1, ?)
|
||||
`).run(title, req.file.originalname, req.file.filename, req.file.mimetype, req.file.size, req.user.id);
|
||||
} catch (err) {
|
||||
const fp = path.resolve(UPLOADS_DIR, req.file.filename);
|
||||
if (fp.startsWith(UPLOADS_DIR + path.sep)) { try { fs.unlinkSync(fp); } catch {} }
|
||||
throw err;
|
||||
}
|
||||
|
||||
res.status(201).json({ id: r.lastInsertRowid });
|
||||
}
|
||||
|
||||
/* ── GET /api/files/:id/download ─────────────────────────────────────── */
|
||||
function downloadFile(req, res) {
|
||||
const f = db.prepare('SELECT * FROM files WHERE id = ?').get(req.params.id);
|
||||
@@ -343,4 +373,4 @@ function clearFolderAccess(req, res) {
|
||||
res.json({ ok: true });
|
||||
}
|
||||
|
||||
module.exports = { listFiles, uploadFile, downloadFile, deleteFile, getFileAccess, assignFile, unassignFile, listFolders, createFolder, renameFolder, deleteFolder, moveFile, getFolderAccess, assignFolder, unassignFolder, clearFolderAccess };
|
||||
module.exports = { listFiles, uploadFile, uploadPersonalFile, downloadFile, deleteFile, getFileAccess, assignFile, unassignFile, listFolders, createFolder, renameFolder, deleteFolder, moveFile, getFolderAccess, assignFolder, unassignFolder, clearFolderAccess };
|
||||
|
||||
Reference in New Issue
Block a user