Files
LMS/docs/15-stage5-course-materials.md

5.8 KiB
Raw 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 — legacy-файл, публичен как раньше;
    • глобальная опубликованная статья ALL — публична, в том числе анонимно;
    • глобальные STUDENTS/TEACHERS — только с JWT и соответствующей ролью;
    • курсовой актив — только с JWT и доступом к курсу: админ видит любой курс, преподаватель — свой (включая скрытые материалы), студент — только опубликованные материалы своего курса. Скрытый актив для студента отдаёт 404 (не раскрывает существование), чужой курс — 403.
  • Web-прокси /api/v1/content/assets/{name} передаёт Bearer-токен из web-сессии, поэтому защищённые картинки на страницах /learn/... открываются, а на публичных страницах прокси остаётся анонимным.

Остаточный риск — legacy-активы без привязки. V12 закрывает только файлы, на которые есть ссылки в markdown. Если файл был загружен, но ни одна статья на него не ссылается (осиротевший или удалённый из текста актив), он остаётся публичным. Нужна инвентаризация таких файлов и привязка к статье либо удаление; до этого поведение сохраняется осознанно, чтобы не ломать старые публичные ссылки.

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

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