docs: этапы 3–4 — роли, профиль и жизненный цикл
This commit is contained in:
172
docs/13-stage3-report.md
Normal file
172
docs/13-stage3-report.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# 13. Отчёт этапа 3: доступ и профиль студента курса
|
||||
|
||||
**Дата:** 2026-10-27.
|
||||
**Статус:** этап 3 выполнен; этап 4 (повышение и жизненный цикл) — следующий.
|
||||
|
||||
## 1. Что сделано
|
||||
|
||||
### Аккаунты преподавателей
|
||||
|
||||
- Добавлен административный API `/api/v1/teachers`: список, создание,
|
||||
сброс пароля. Создаётся только аккаунт роли `TEACHER`.
|
||||
- Логин проверяется на длину (минимум 3 символа) и уникальность; пустой пароль
|
||||
заменяется сгенерированным. Новый пароль помечается как временный
|
||||
(`mustChangePassword`), показывается один раз.
|
||||
- В админке появилась страница `/admin/teachers` с созданием преподавателя
|
||||
и сбросом пароля; одноразовый пароль выводится во всплывающем уведомлении.
|
||||
|
||||
### Курсовая область видимости
|
||||
|
||||
- `CourseAccessService` в academic-service определяет доступные курсы по роли:
|
||||
- `ADMIN` — все курсы, включая архивные;
|
||||
- `TEACHER` — только активные курсы с активным назначением
|
||||
`course_teachers` (`removed_at IS NULL`);
|
||||
- `STUDENT` — только активные курсы активных групп, в которых студент
|
||||
состоит сейчас (группы запрашиваются у identity-service);
|
||||
- анонимный пользователь до сервиса не доходит — gateway требует JWT.
|
||||
- `GET /api/v1/courses/mine` возвращает курсы текущего пользователя.
|
||||
- `GET /api/v1/courses/{id}` проверяет доступ к курсу. Админские операции
|
||||
создания, переименования, архивирования, назначения преподавателей
|
||||
и привязки групп остались только для `ADMIN`.
|
||||
- Список лабораторных без `courseId` фильтруется по доступным курсам;
|
||||
с `courseId` доступ проверяется явно. Карточка лабораторной тоже проверяет
|
||||
доступ через её курс. Лабораторная без курса доступна только админу.
|
||||
- Недоступный курс/лабораторная дают `403`, несуществующая — `404`.
|
||||
Недоступность identity при проверке групп даёт `502`, а не `403`.
|
||||
|
||||
### Внутренние API identity
|
||||
|
||||
Добавлены read-only эндпоинты для межсервисных вызовов:
|
||||
|
||||
| Метод | Путь | Назначение |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/v1/internal/students/{studentId}/context` | карточка студента: ФИО, №, статус, год набора, текущая группа |
|
||||
| `POST` | `/api/v1/internal/accounts/lookup` | пакетные логины авторов комментариев и оценок |
|
||||
| `GET` | `/api/v1/internal/groups/{groupId}` | группа и текущий состав без учётных данных |
|
||||
|
||||
Эндпоинты защищены заголовком `X-Internal-Token`; сравнение токена —
|
||||
constant-time. Gateway не маршрутизирует `/api/v1/internal/**`, поэтому снаружи
|
||||
эти пути недоступны.
|
||||
|
||||
### Кабинет преподавателя
|
||||
|
||||
- `/teacher` — список назначенных курсов с количеством групп и лабораторных.
|
||||
- `/teacher/courses/{id}` — карточка курса: привязанные группы и лабораторные
|
||||
в режиме просмотра. Редактирование курса и лабораторных остаётся у админа.
|
||||
- `/teacher/courses/{courseId}/groups/{groupId}` — состав группы, привязанной
|
||||
к курсу, со ссылками на профиль студента.
|
||||
- Доступ к `/teacher/**` разрешён роли `TEACHER` и админу; студент
|
||||
перенаправляется в свой кабинет, аноним — на вход. После входа преподаватель
|
||||
попадает на `/teacher`.
|
||||
|
||||
### Профиль студента курса
|
||||
|
||||
- Progress-service собирает агрегированный профиль одним запросом:
|
||||
`GET /api/v1/courses/{courseId}/students/{studentId}/profile`.
|
||||
- В ответе: курс, карточка студента, сводка и полный список лабораторных курса.
|
||||
- Для каждой лабораторной: вариант и уровень, число попыток, дата последней
|
||||
сдачи, оценка и её публикация, общий комментарий, комментарии преподавателей,
|
||||
защита (статус, код, задание, остаток времени), все попытки с файлами
|
||||
и ссылкой на скачивание.
|
||||
- Скачивание любой попытки:
|
||||
`GET /api/v1/courses/{courseId}/submissions/{submissionId}/download`
|
||||
(ZIP). Проверяется, что сдача относится к лабораторной этого курса;
|
||||
чужой `submissionId` и чужой курс отклоняются.
|
||||
- BFF-страницы профиля:
|
||||
- `/teacher/courses/{courseId}/students/{studentId}`;
|
||||
- `/admin/courses/{courseId}/students/{studentId}`.
|
||||
- Авторство: миграция `V13__profile_authors.sql` добавляет
|
||||
`grade_comments.author_account_id` и `lab_grades.updated_by_account_id`;
|
||||
новые комментарии и оценки сохраняют автора. Старые записи показываются
|
||||
без автора. Индекс `lab_grades(lab_id)` ускоряет выборку по списку лаб.
|
||||
- В ведомости группы ФИО студента и кнопка «Профиль» ведут на профиль, если
|
||||
группа открыта из карточки курса. На странице группы преподавателя те же
|
||||
ссылки доступны из состава группы.
|
||||
|
||||
## 2. Правила доступа
|
||||
|
||||
| Объект | ADMIN | TEACHER | STUDENT |
|
||||
|---|---|---|---|
|
||||
| Курс | любой, включая архивный | только активный назначенный | только активный курс текущей группы |
|
||||
| Лабораторные курса | все | все курса, включая черновики | все лабораторные доступного курса |
|
||||
| Состав группы курса | любая привязанная группа | группа должна быть активной привязкой курса | доступ к курсу по своей группе |
|
||||
| Профиль студента | любой студент любого курса | студент должен состоять в активной группе курса | только свой профиль |
|
||||
| Оценки и комментарии | все | все, включая неопубликованные | только опубликованные |
|
||||
| Скачивание попытки | любая попытка курса | только студент из активной группы курса | отдельный download-эндпоинт недоступен |
|
||||
| Внутренние API identity | по сервисному токену | по сервисному токену | по сервисному токену |
|
||||
|
||||
Дополнительно:
|
||||
|
||||
- Преподаватель чужого курса получает `403`; студент чужого профиля — `403`.
|
||||
- Студент видит только опубликованные оценки и комментарии. Черновики
|
||||
и неопубликованные комментарии скрыты.
|
||||
- Профиль не даёт доступа к другим курсам студента.
|
||||
- Автор комментария/оценки показывается логином; старые записи без автора —
|
||||
«—». Логины подтягиваются одним пакетным запросом.
|
||||
|
||||
## 3. Токен и конфигурация
|
||||
|
||||
- Общий секрет: `LMS_INTERNAL_TOKEN`.
|
||||
- Где задаётся: `lms.internal.token` в `application.yml` identity, academic
|
||||
и progress. Локально допускается значение по умолчанию
|
||||
`dev-internal-token`; в production оно обязательно заменяется.
|
||||
- `docker-compose.prod.yml` передаёт токен в identity, academic и progress;
|
||||
`.env.prod.example` содержит заготовку без секрета.
|
||||
- Заголовок: `X-Internal-Token`. Пустой или неверный токен — `403`.
|
||||
- Gateway не публикует внутренние пути; вызовы идут напрямую между сервисами
|
||||
по внутренней сети Docker.
|
||||
|
||||
## 4. UI
|
||||
|
||||
- `/admin/teachers` — создание преподавателя, список, сброс пароля,
|
||||
отображение временного пароля один раз.
|
||||
- `/teacher` — «Мои курсы».
|
||||
- `/teacher/courses/{id}` — группы и лабораторные курса, переход к составу
|
||||
группы.
|
||||
- `/teacher/courses/{courseId}/groups/{groupId}` — студенты группы, ФИО
|
||||
и кнопка «Открыть» ведут в профиль.
|
||||
- `/teacher/courses/{courseId}/students/{studentId}` и
|
||||
`/admin/courses/{courseId}/students/{studentId}` — единый шаблон профиля
|
||||
с разными маршрутами скачивания и возврата.
|
||||
- В навигации: «Преподаватели» у админа, «Мои курсы» у преподавателя.
|
||||
- Профиль показывает сводку (лабораторных, сдано, оценено, средний балл,
|
||||
защиты), каждую лабораторную, комментарии, защиты и все попытки.
|
||||
|
||||
## 5. Тесты
|
||||
|
||||
Добавлены и расширены наборы:
|
||||
|
||||
- identity: `InternalControllerIntegrationTest` (12) — контекст студента,
|
||||
пакетные логины, состав группы, неверный/пустой токен, неизвестные id;
|
||||
`TeacherIntegrationTest` (6) — создание, генерация пароля, список,
|
||||
сброс пароля, запрет для не-админа.
|
||||
- academic: `CourseAccessServiceTest` (16) — роли, чужие/архивные курсы,
|
||||
снятые назначения, студенты без групп, `mine`, доступ к лабораторным;
|
||||
`CourseRosterServiceTest` (6) — состав группы, чужая группа/преподаватель,
|
||||
недоступность identity.
|
||||
- progress: `StudentProfileIntegrationTest` (5) — неопубликованные данные
|
||||
для преподавателя, только опубликованное для студента, чужой курс/студент,
|
||||
скачивание чужой сдачи.
|
||||
- web: `StudentProfileTemplateTest`, `ProfileDtoJsonTest`,
|
||||
`TeacherGroupTemplateTest`, `TeacherInterceptorTest`,
|
||||
`CourseGroupRosterDtoTest` — контракты, шаблоны, роли и ссылки.
|
||||
|
||||
По результатам последнего прогона (2026-10-27) перечисленные наборы прошли
|
||||
с `0 failures / 0 errors`.
|
||||
|
||||
## 6. Известные пробелы
|
||||
|
||||
- **Контент курса.** URL материалов вида `/learn/course/{course_no}/...`,
|
||||
проверка `chapters.course_id/audience`, наследуемая видимость активов
|
||||
и выдача файлов по курсу ещё не реализованы. Схема подготовлена на этапе 2.
|
||||
- **Права преподавателя.** Кабинет преподавателя — просмотр: курс, группы,
|
||||
состав, лабораторные и профили. Редактирование лабораторных, оценок
|
||||
и раздачи вариантов из кабинета не добавлялось.
|
||||
- **Названия групп в кабинете.** На странице курса преподавателя группа
|
||||
показывается UUID: academic отдаёт `groupId`, а имя группы не входит
|
||||
в контракт карточки курса. Нужен batch-запрос имён или расширение DTO.
|
||||
- **Собственный профиль студента.** API допускает `STUDENT` только для своего
|
||||
профиля, но отдельной студенческой BFF-страницы профиля нет; текущие
|
||||
страницы профиля — преподавательская и админская.
|
||||
- **Аудит скачиваний.** Скачивание попыток пока не пишется в аудит;
|
||||
это этап 6.
|
||||
Reference in New Issue
Block a user