Files
Learn_System/plans/my-materials/PLAN_V2.md
T
Maxim Dolgolyov 786419ce01 feat(materials): серверные миниатюры (sharp) + пагинация рендера списка
Миниатюры: 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>
2026-06-13 14:40:23 +03:00

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.