786419ce01
Миниатюры: uploadPersonalFile генерирует webp ≤480px (sharp), возвращает {url, thumbUrl}; колонка thumb_url (мигр.074); грид рисует <img> на миниатюре, просмотр/скачивание/аннотация — полный url. Ссылочная чистка матчит url И thumb_url; share копирует thumb; квота учитывает файл+миниатюру. Сейверы board-clip/material-save/textbook-clip/draw пробрасывают thumbUrl.
Пагинация: клиент рендерит PAGE_SIZE=60 карточек + «Показать ещё» (сброс на смену фильтра), сохраняя клиентский поиск/сортировку над полным списком.
Тесты: materials.test.js 16→19. План V2 выполнен.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
6.5 KiB
Markdown
64 lines
6.5 KiB
Markdown
# «Мои материалы» — v2: харднинг и доводка
|
|
|
|
> Составлен Opus 2026-06-13. Базовый план (PLAN.md, Фазы 1–6) **полностью реализован**.
|
|
> Его раздел «Сквозные риски» отложил ровно то, что закрывает этот план: учёт/лимиты/чистку
|
|
> хранилища и `materials.test.js`. Источник истины по текущему состоянию — код
|
|
> (`studentMaterialsController.js`, `materials.js`, `my-materials.html`, `board-clip.js`,
|
|
> `material-save.js`) и [[reference_student_materials]].
|
|
|
|
Готчи проекта: новый `:id`-роут → `// @public-by-design` + проверка владельца; большие HTML — только Edit;
|
|
без эмодзи (inline SVG `.ic`); коммит поимённо + push; перезапуск сервера при правке backend; ветка
|
|
`feature/sim-builder` в рабочем дереве — НЕ коммитить чужие правки, только свои файлы.
|
|
|
|
---
|
|
|
|
## Фаза 1 — Целостность и безопасность (backend, фундамент) ✅ цель этого захода
|
|
|
|
1. **Ссылочно-подсчётная чистка файлов.** `DELETE /:id` и смена `url` (аннотация) сейчас оставляют
|
|
файл в `uploads/materials/` сиротой. `share` копирует `url` дословно → несколько строк ссылаются на
|
|
ОДИН файл, поэтому `unlink` только когда на `url` не ссылается ни одна строка. Хелпер
|
|
`releaseFileForUrl(url)` вызывается ПОСЛЕ delete/update.
|
|
2. **Allowlist схемы URL.** `create`/`update` принимали любой `url` → `link` со схемой `javascript:`
|
|
рендерится как рабочий `<a href>` (раздача делает это вектором учитель→ученики). Хелпер `safeUrl`:
|
|
только `http(s)://` или app-relative `/…` (не `//host`); иначе 400.
|
|
3. **Квота на пользователя.** Колонка `bytes` (мигр. 073), счёт `SUM(bytes)`/`COUNT(*)`. Лимит по числу
|
|
материалов — в `create()`; лимит по байтам — в `uploadPersonalFile` (до приёма файла). Конфигурируемо
|
|
через `MATERIALS_MAX_ITEMS` / `MATERIALS_MAX_BYTES` (для тестов — низкий потолок).
|
|
4. **`backend/tests/materials.test.js`** — CRUD, владелец (403/404), коллекции, share-копия + роль/owner,
|
|
валидация URL, лимит числа, ссылочная чистка (прямой вызов хелпера на временном файле).
|
|
|
|
## Фаза 2 — Производительность ✅
|
|
- ✅ `GET /api/materials` отдаёт **обрезанный** `body` (первые 1000 симв.) + флаг `body_trunc`; полный текст —
|
|
ленивый `GET /api/materials/:id` (`getOne`, owner-only). Клиент `ensureFullBody()` подгружает перед
|
|
просмотром/правкой/флешкартой (иначе правка сохранила бы усечённый текст).
|
|
- ✅ Пагинация рендера: клиент держит весь список (поиск/фильтр/сортировка в памяти), но в DOM рисует
|
|
`PAGE_SIZE=60` карточек + «Показать ещё»; `_shown` сбрасывается на смену фильтра. Снимает стоимость
|
|
рендера тысяч узлов, не ломая клиентский поиск (keyset на сервере не нужен на текущих объёмах).
|
|
- ✅ Серверные миниатюры `board/image`: `uploadPersonalFile` (sharp → webp ≤480px) возвращает `{url, thumbUrl}`;
|
|
колонка `thumb_url` (мигр. **074**); грид рисует `<img src=thumb_url||url>`, просмотр/скачивание/аннотация —
|
|
полный `url`. Чистится по ссылкам (releaseFileForUrl теперь матчит url **и** thumb_url); share копирует thumb;
|
|
квота считает файл+миниатюру. Клиентские сейверы (board-clip/material-save/textbook-clip/draw) пробрасывают `thumbUrl`.
|
|
|
|
## Фаза 3 — Доводка заложенных фич ✅
|
|
- ✅ UI тегов: ввод в модалках создания/правки + чипы на карточке (клик → фильтр) + пилюля активного фильтра.
|
|
- ✅ Ссылка «открыть исходный урок» на карточке (`/my-lessons?session=<id>`, есть `source_session_id`).
|
|
- ✅ Цвет папки (палитра 8 пресетов, тинт иконки в рейле) + сортировка папок «Выше/Ниже» в модалке правки
|
|
(нормализует `sort_order` к индексам). `safeColor` гейтит inline-style инъекцию (только hex).
|
|
|
|
## Фаза 4 — UX ✅
|
|
- ✅ Варианты сортировки (новые/старые/имя/тип) — селект в тулбаре.
|
|
- ✅ Множественный выбор (чекбокс на карточке) + панель массовых действий (переместить/удалить, reuse per-item API).
|
|
- ✅ Живое превью KaTeX в редакторе заметки (oninput → `mmPreview` → `mathHtml`).
|
|
|
|
### Статус — ПЛАН V2 ВЫПОЛНЕН
|
|
**Ф1–Ф4 ✅.** Backend: 19 тестов `materials.test.js` (CRUD/владелец/коллекции/share/URL-allowlist/квота/
|
|
ссылочная чистка url+thumb/round-trip thumb_url). Frontend: headless-смоук `my-materials.html` (синтаксис +
|
|
deep-link/теги/чекбокс/bulk/тинт папки + `<img>` на thumb_url + пагинация «Показать ещё»). sharp-пайплайн и
|
|
client-сейверы (board-clip/material-save/textbook-clip) проверены. Открытого из плана не осталось.
|
|
|
|
---
|
|
|
|
## Порядок
|
|
**Ф1 (этот заход) → Ф2 → Ф3 → Ф4.** Ф1 — серверный фундамент (риск-возврат, без него фронт-фичи множат
|
|
мусор). Дальше преимущественно фронтенд `my-materials.html` + точечные ручки API.
|