Files
LMS/docs/15-stage5-course-materials.md
Vladislav Kopnin 65ed08f5c2 feat(web): перевести браузерные URL курсов на courseNo
- teacher/admin course pages, content, gradebook, profile, downloads
- courseNo -> UUID только на границе REST API
- обновлены e2e, шаблоны, тесты и docs
- LDD-UI-002; baseline 59 claims / 385 тестов
2026-10-29 19:51:03 +03:00

6.3 KiB
Raw Permalink Blame History

Этап 5. Материалы курса

Статус: реализовано в academic-service и web (без изменения identity/progress).

Что изменилось

  • Публичные /api/v1/content/tree и /api/v1/content/{chapter}/{section}/{article} отдают только глобальные главы (course_id IS NULL) с аудиторией ALL. Курсовые главы через публичный контракт больше не доступны.
  • Материалы курса открываются по /api/v1/courses/{courseId}/content/... и требуют аутентификации. Доступ проверяется через CourseAccessService: админ — любой курс, преподаватель — только назначенный, студент — только курс активной группы.
  • Администратор и преподаватель курса видят скрытые главы/разделы/статьи; студент — только опубликованные.
  • В браузере материалы курса открываются по /learn/course/{course_no} и /learn/course/{course_no}/{chapter}/{section}/{article}. Анонимный пользователь перенаправляется на /login, чужой курс даёт 403.
  • Ссылки на материалы курса добавлены в кабинеты студента (карточки лабораторных), преподавателя и администратора.
  • Админский просмотр и авторство глобальных материалов (treeAll, /api/v1/content/all/..., /learn для админа) сохранены.
  • Новые глобальные главы создаются и импортируются с audience = "ALL". Миграция V11__global_audience_all.sql backfill-ом проставляет ALL старым глобальным главам; курсовые главы остаются с audience = NULL.

Видимость активов (закрыто после этапа 5)

Разрыв этапа 5 закрыт: активы привязаны к статьям и наследуют их видимость.

  • Добавлены entity/repository article_assets; импорт контента сохраняет связи для файлов, на которые ссылаются статьи.
  • Миграция V12__asset_links_backfill.sql backfill-ом восстанавливает связи для уже загруженных статей: ищет в articles.body_md ссылки вида /api/v1/content/assets/{name}, создаёт строки article_assets с MIME по расширению и size = 0. Повторный запуск не создаёт дубликатов.
  • Публичный API /api/v1/content/assets/{name} проверяет доступ по всем статьям, к которым привязан файл (достаточно одной видимой):
    • актив без связей в article_assets — secure-by-default: 404 для анонима, студента и преподавателя; только ADMIN может скачать файл для инвентаризации/очистки;
    • глобальная опубликованная статья ALL — публична, в том числе анонимно;
    • глобальные STUDENTS/TEACHERS — только с JWT и соответствующей ролью;
    • курсовой актив — только с JWT и доступом к курсу: админ видит любой курс, преподаватель — свой (включая скрытые материалы), студент — только опубликованные материалы своего курса. Скрытый актив для студента отдаёт 404 (не раскрывает существование), чужой курс — 403.
  • Web-прокси /api/v1/content/assets/{name} передаёт Bearer-токен из web-сессии, поэтому защищённые картинки на страницах /learn/... открываются, а на публичных страницах прокси остаётся анонимным.

Legacy-активы без привязки (закрыто)

V12 закрывает файлы, на которые есть ссылки в markdown. Если файл был загружен, но ни одна статья на него не ссылается, он больше не публичен: аноним, студент и преподаватель получают 404, чтобы не раскрывать существование файла. Админ скачивает такой файл и получает его имя на странице /admin/legacy-assets, через GET /api/v1/content/legacy-assets или scripts/legacy-assets.sh, после чего привязывает файл к статье или удаляет. Старые внешние ссылки на несвязанные файлы у обычных пользователей перестают работать — это осознанное поведение secure-by-default, а не регрессия. Эндпоинт и страница инвентаризации защищены ROLE_ADMIN.

Авторство материалов преподавателем (добавлено на этапе 6)

Преподаватель своего курса редактирует главы, разделы и статьи на /teacher/courses/{courseNo}/content: создание/переименование/удаление, markdown- редактор с предпросмотром, публикация/снятие с публикации. API — /api/v1/courses/{courseId}/content/**; любая изменяемая сущность проверяется на принадлежность курсу. Глобальные материалы (course_id IS NULL, аудитории ALL/STUDENTS/TEACHERS) по-прежнему создаёт и публикует только админ.