feat(materials): «Мои материалы» v2 — харднинг безопасности и доводка UX

Безопасность/целостность: allowlist схемы URL (safeUrl) против stored-XSS через javascript:-ссылку; ссылочно-подсчётная чистка файлов при delete/смене url (releaseFileForUrl, учёт share-алиасов); квота на пользователя — число материалов + байты (колонка bytes, миграция 073).

Производительность: список отдаёт превью body (1000 симв.) + body_trunc; полный текст — ленивый GET /api/materials/:id (getOne, owner-only).

Фичи/UX (my-materials.html): теги-UI (ввод + чипы-фильтр + пилюля), ссылка на исходный урок, сортировка, множественный выбор + массовые действия, цвет/порядок папок, live-KaTeX в редакторе заметки.

Тесты: backend/tests/materials.test.js (16 тестов) — ранее их не было.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-13 14:21:30 +03:00
parent 222005c0ba
commit abe84b9f90
8 changed files with 578 additions and 34 deletions
+2 -1
View File
@@ -1037,7 +1037,7 @@ window.LS = {
crJoin, crLeave, crSendChat, crGetChat, crGetAttendance, crSignal, crGetOnlineStudents, crGetMySession,
crGetMyHistory, crGetClassHistory, crGetSessionSummary, crExportChatUrl, crGetAllNotes, crDeleteHistory,
crAdminGetAllHistory, crAdminGetTeachersList,
listMaterials, saveMaterial, updateMaterial, deleteMaterial, shareMaterial, getActivity,
listMaterials, getMaterial, saveMaterial, updateMaterial, deleteMaterial, shareMaterial, getActivity,
createMaterialCollection, updateMaterialCollection, deleteMaterialCollection,
customSimsList, customSimGet, customSimCreate, customSimUpdate, customSimDelete,
customSimShare, customSimClone, customSimRelated, customSimAddLink, customSimDelLink,
@@ -1253,6 +1253,7 @@ async function uploadMaterialFile(formData) {
}
function downloadFileUrl(id) { return `${API}/files/${id}/download`; }
async function listMaterials() { return req('GET', '/materials'); }
async function getMaterial(id) { return req('GET', `/materials/${id}`); }
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}`); }