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:
Maxim Dolgolyov
2026-06-04 14:21:18 +03:00
parent ac1857c931
commit 55c8c5fa51
7 changed files with 63 additions and 6 deletions
+14 -1
View File
@@ -1029,7 +1029,7 @@ window.LS = {
myAssignments, teacherAssignments, startAssignment, assignmentResults, assignmentSessionReview, assignmentQuestionStats,
getTests, createTest, getTest, updateTest, deleteTest, addQuestionsToTest, removeQFromTest,
getGamificationMe, getGamAchievements, getLeaderboard, getXPHistory, getChallenges, claimChallenge, setGoalTier, getFrames, setFrame, reportLabActivity,
getFiles, uploadFile, downloadFileUrl, deleteFile, getFileAccess, assignFile, unassignFile,
getFiles, uploadFile, uploadMaterialFile, downloadFileUrl, deleteFile, getFileAccess, assignFile, unassignFile,
getFolders, createFolder, renameFolder, deleteFolder, moveFile,
getFolderAccess, clearFolderAccess, assignFolder, unassignFolder, getStudentsList,
submitWork, resubmitWork, getMySubmissions, getClassSubmissions, reviewSubmission, deleteSubmission, submissionDownloadUrl,
@@ -1244,6 +1244,19 @@ async function uploadFile(formData) {
if (!res.ok) throw Object.assign(new Error(data.error || 'Upload failed'), { status: res.status });
return data;
}
/* Personal image upload for «Мои материалы» — any logged-in user (students too).
* Separate from uploadFile() which is the teacher library (role + permission). */
async function uploadMaterialFile(formData) {
const token = getToken();
const res = await fetch(API + '/files/personal', {
method: 'POST',
headers: token ? { 'Authorization': `Bearer ${token}` } : {},
body: formData,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) throw Object.assign(new Error(data.error || 'Upload failed'), { status: res.status });
return data;
}
function downloadFileUrl(id) { return `${API}/files/${id}/download`; }
async function listMaterials() { return req('GET', '/materials'); }
async function saveMaterial(data) { return req('POST', '/materials', data); }