Files
LMS/docs/08-groups-courses-plan.md

293 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 08. План реализации: группы, курсы, преподаватели
**Статус:** план согласован, реализация по этапам 0–6, один релиз.
**Основа:** [07-groups-courses.md](07-groups-courses.md).
**Дата решений:** 2026-10-23.
Документ фиксирует решения, принятые при разборе ТЗ, и порядок реализации.
При противоречии с `07-groups-courses.md` приоритет у раздела 1 этого плана;
само ТЗ приводится в соответствие на этапе 0.
---
## 1. Зафиксированные решения
| № | Вопрос | Решение |
|---|---|---|
| 1 | Лабораторные вне курса | Не нужны. Все лабораторные принадлежат курсу. Текущие лабы переносятся в курс «Промышленное программирование на языке C». Все текущие активные группы привязываются к этому курсу. `labs.course_id` сначала nullable, после переноса — `NOT NULL`. Перед миграцией проверяются дубли номеров. |
| 2 | Идентификатор курса в URL | UUID остаётся первичным ключом. Добавляется целочисленный `courses.course_no` (`BIGINT GENERATED ALWAYS AS IDENTITY`, уникальный, неизменяемый). Курсовые материалы открываются по `/learn/course/{course_no}/...`. Все текущие материалы переносятся в курс C и доступны только студентам его групп, преподавателям курса и админу. Старые ссылки не поддерживаются. `audience` используется только для будущих глобальных материалов (`course_id IS NULL`). |
| 3 | Архив и вход | `accounts.status` (`ACTIVE/ARCHIVED`) — для всех пользователей; `students.status` (`ACTIVE/ACADEMIC_LEAVE/EXPELLED/GRADUATED/ARCHIVED`) — академический статус. Вход запрещён для `EXPELLED`, `GRADUATED`, `ARCHIVED` и архивных аккаунтов. `ACADEMIC_LEAVE` — вход в режиме только чтения. Web перепроверяет статус с кэшем 30–60 секунд; сервисы проверяют доступ к курсу при каждом запросе. |
| 4 | Журнал повышения | `promotion_runs` + `promotion_run_items` + `membership_history`. Dry-run сохраняется как план; применение идемпотентно; откат — по элементам журнала с проверкой конфликтов. |
| 5 | Объём | Полный ТЗ, этапы 0–6, один релиз с внутренними merge gate. |
| 6 | Запуск повышения | 31 августа в 00:00 МСК автоматически строится dry-run. Применение — только вручную админом после проверки плана. При пропуске даты dry-run строится при следующем старте. |
| 7 | Профиль студента курса | Преподаватель своего курса может открыть профиль студента по клику на ФИО или отдельной кнопке: статистика по лабам, комментарии преподавателей, скачивание файлов любой попытки сдачи. |
| 8 | Резервное копирование | Обязательный backup перед каждым деплоем/миграцией, ежедневный backup по расписанию, retention, проверка целостности и restore на staging. Без успешного backup деплой не продолжается. |
---
## 2. Новое требование: профиль студента курса
### 2.1. Доступ
- **Преподаватель** — только если он активен в `course_teachers` для этого курса
(`removed_at IS NULL`), а студент входит в группу, привязанную к этому курсу
(`course_groups.removed_at IS NULL`).
- **Админ** — любой студент любого курса.
- **Студент** — свой профиль и только опубликованные оценки/комментарии
(опционально; в обязательный объём текущего требования не входит).
- **Аноним** — доступа нет.
- Проверка выполняется на сервисах, а не только в web-BFF. Чужой курс/студент
должен давать `403` или `404`, а не данные.
### 2.2. Состав профиля
Карточка студента:
- ФИО, № студента, группа, статус, год набора;
- контактные/служебные поля, если появятся позже.
Сводка по курсу:
- количество лабораторных курса;
- сколько сдано, сколько проверено;
- средний балл;
- количество защит и их статусы.
Таблица по каждой лабораторной курса:
- номер и название лабы;
- вариант и уровень (если назначены);
- количество попыток сдачи;
- дата последней попытки;
- оценка;
- статус публикации оценки;
- статус защиты;
- общий комментарий к оценке;
- количество комментариев.
Комментарии преподавателей по каждой лабе:
- общий комментарий оценки (`lab_grades.general_comment`);
- отдельные комментарии (`grade_comments`) с текстом, тоном, автором и датой;
- для старых комментариев без автора — «автор не указан»;
- новые комментарии сохраняют `author_account_id`.
Все попытки сдачи по каждой лабе:
- номер попытки, дата, список файлов;
- скачивание любой попытки, а не только последней;
- скачивание в рамках своего курса; чужой `submissionId` запрещён;
- для преподавателя — ZIP попытки и, при необходимости, отдельные файлы;
- для студента — только свои попытки.
### 2.3. API (предварительно)
- `GET /api/v1/courses/{courseId}/students/{studentId}/profile` — агрегированный
профиль студента курса.
- `GET /api/v1/courses/{courseId}/submissions/{submissionId}/download` — ZIP попытки
для преподавателя/админа.
- `GET /api/v1/courses/{courseId}/submissions/{submissionId}/files/{name}` —
отдельный файл попытки (если включаем отдельные файлы).
- `GET /api/v1/me/courses/{courseId}/profile` — собственный профиль студента
(только опубликованное), опционально; в обязательный объём не входит.
`courseId` в API — UUID. В браузерных URL используется `course_no`.
### 2.4. UI
- В ведомости группы ФИО студента становится ссылкой на профиль.
- В колонке действий появляется отдельная кнопка «Профиль».
- Страница преподавателя: `/teacher/courses/{courseNo}/students/{studentId}`.
- Админ открывает тот же профиль по `/admin/courses/{courseNo}/students/{studentId}`
или через общий маршрут с проверкой роли.
- Профиль не показывает чужие курсы и глобальные материалы.
- Для архивных/выпущенных студентов профиль открывается в режиме истории, если
студент был в курсе; иначе — `404`.
### 2.5. Данные и миграции
- `grade_comments` добавить `author_account_id uuid NULL`; индекс
`(student_id, lab_id)` уже существует.
- Для общего комментария оценки добавить `lab_grades.updated_by_account_id`
(автор последнего изменения) и индекс `lab_grades(lab_id)`.
- Все попытки уже хранятся в `submissions` (`student_id`, `lab_id`, `seq`);
отдельная таблица не нужна.
- Для производительности: один агрегирующий запрос/эндпоинт, пакетная выборка
комментариев, попыток, защит и публикаций по списку `lab_id`; без N+1.
- Аудит скачиваний: этап 6 (журнал `audit_events` или отдельная таблица).
### 2.6. Ограничения
- Преподаватель видит все лабы своего курса, включая черновики, но не может
видеть лабы других курсов.
- Студент видит только опубликованные оценки и комментарии.
- Скачивание файлов не даёт доступа к чужим сдачам.
- Профиль не заменяет ведомость: оценки по-прежнему выставляются в ведомости,
профиль — просмотр и скачивание.
---
## 3. Этапы
### Этап 0. Контракты и схема
- Обновить `07-groups-courses.md` и ADR под решения раздела 1.
- Описать API профиля студента и формат ответа.
- Миграции identity/academic/progress.
- Preflight: группы, не подходящие под шаблон имени; дубли номеров лаб;
материалы без курса; комментарии без автора.
- Проверить миграции на пустой БД и на копии текущей.
- Определить механизм `labs.course_id NOT NULL`: обычный Flyway-старт не может
дождаться bootstrap-курса. Варианты — seed курса C в миграции с временно
nullable владельцем либо отдельный post-bootstrap шаг. Решение — в ADR.
**Выход:** схема, контракты, отчёт о рисках; код бизнес-логики ещё не пишем.
### Этап 1. Identity
- Роли `ADMIN/TEACHER/STUDENT`.
- Поля групп и парсер имени.
- Статусы студентов и аккаунтов.
- `membership_history`.
- Импорт Excel v2 с построчным отчётом.
- Админ-UI групп и пользователей.
### Этап 2. Academic
- `courses`, `course_teachers`, `course_groups`.
- `course_no` и URL курса.
- `labs.course_id`, перенос лаб в курс C, копирование лаб.
- `chapters.course_id/audience`, перенос материалов в курс C.
- Частичные уникальные индексы slug.
- Активы: связь с главой/статьёй и наследуемая видимость.
### Этап 3. Доступ и профиль (backend)
- `TEACHER` в JWT и security-конфигах.
- Внутренние access-эндпоинты academic/identity.
- Проверки курса, группы, студента и публикации на сервисах.
- Профиль студента курса: агрегирующий endpoint, все попытки, комментарии,
скачивание файлов.
- Negative-тесты: чужой курс, чужой студент, чужая сдача, аноним.
### Этап 4. Lifecycle
- Повышение: dry-run, журнал, применение, откат, выпуск.
- Академический отпуск и возврат.
- Отчисление, выпуск, архив.
- Усиленное удаление и аудит.
### Этап 5. UX
- Кабинет преподавателя.
- Статус защиты в ведомости: «готова» / «кончилось время» (timeout),
с разным цветом и подсказкой; сейчас в матрице всегда показывается «готова».
- Страница профиля студента: ссылка с ФИО и отдельная кнопка.
- Студенческие страницы курсов.
- Админ-страницы курсов, повышения, отпусков, архива, импорта и копирования лаб.
- Публичные глобальные материалы `ALL`.
### Этап 6. Hardening
- Полная security-матрица.
- Upgrade-тест миграций.
- Индексы и пагинация.
- Аудит скачиваний и чувствительных действий.
- Проверка backup: создание, retention, `verify`, restore на staging.
- Документация, deploy/runbook, smoke.
---
## 4. Ориентировочные миграции
Номера уточняются при реализации, чтобы не конфликтовать с текущими максимумами
(identity V3, academic V5, progress V9).
| Сервис | Миграции | Содержание |
|---|---|---|
| identity | V4–V9 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status` |
| academic | V6–V9 + post-bootstrap V10 | курсы, преподаватели, группы курса, `labs.course_id` (nullable), материалы курса/аудитории, частичные индексы, активы; `SET NOT NULL` — отдельный шаг после bootstrap |
| progress | V10 (+ V11 при необходимости аудита) | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит скачиваний — этап 6 |
---
## 5. Агенты
| Агент | Зона | Границы |
|---|---|---|
| `lms-orchestrator` | контракты, ADR, merge, review | не пишет массово код |
| `lms-db` | миграции и тесты миграций | только `db/migration/**` и фикстуры |
| `lms-identity` | identity-service | только identity и его тесты |
| `lms-academic` | academic-service | только academic и его тесты |
| `lms-progress` | progress-service, профиль, сдачи | только progress и его тесты |
| `lms-web` | web, UI профиля, кабинеты | только `web/**` |
| `lms-security` | `libs/security-jwt`, gateway, security-review | без бизнес-логики |
| `lms-qa` | матрица ролей, upgrade, smoke | production-код не правит |
| `lms-docs` | docs, deploy/runbook | только документация |
---
## 6. Skills
- `lms-context` — инварианты и карта модулей.
- `lms-migrations` — Flyway, двухфазные миграции, preflight, legacy-БД.
- `lms-authz` — ролевая матрица, internal access, negative-тесты.
- `lms-identity-domain` — группы, статусы, история, импорт, повышение.
- `lms-academic-domain` — курсы, лабы, материалы, аудитории, активы.
- `lms-web-ui` — Thymeleaf/Bootstrap/JS, формы, навигация.
- `lms-testing` — Testcontainers, MockMvc с JWT, фикстуры.
- `lms-release` — lint/detekt/test, bootJar, `run.sh`, smoke.
---
## 7. Контроль качества
1. Миграции на пустой БД и на копии текущей; preflight-отчёт.
2. Security-матрица: аноним, студент свой/чужой, преподаватель свой/чужой,
админ, архивный/выпущенный.
3. Профиль: чужой курс/студент/сдача → `403/404`; все попытки скачиваются;
комментарии с автором; нет N+1.
4. `./gradlew ktlintCheck detekt test` после каждого этапа.
5. UI-smoke для четырёх ролей, старые и новые URL, публичные/приватные активы.
6. Аудит скачиваний и чувствительных действий.
7. Backup: обязательный перед деплоем, ежедневный, с проверкой restore.
8. Документация и runbook миграций.
---
## 8. Риски и открытые вопросы
- **Большой объём одного релиза.** Снижаем контрактами, merge gate и независимым QA.
- **Межсервисная авторизация.** Самый сложный участок; ADR до кода.
- **Перенос данных.** Нужны preflight и копия БД; группы с нестандартными именами
и дубли номеров лаб могут заблокировать `NOT NULL`.
- **Активы.** Сейчас публичны и не связаны со статьями; нужна миграция связей.
- **JWT 30 дней.** Средний вариант блокировки оставляет остаточный риск для
прямого API; фиксируем в документации.
- **Комментарии без автора.** Старые записи останутся с «автор не указан».
- **Архивные студенты.** Открывать ли профиль после отвязки группы — уточнить
на этапе 3; по умолчанию история курса доступна, если студент был в курсе.
---
## 9. Статус реализации
- **Этап 0 — выполнен.** Схема, contract API профиля, preflight, ADR,
проверка на копии БД. Отчёт: [10-stage0-report.md](10-stage0-report.md).
- **Этап 1 — выполнен.** Роли, группы-когорты, статусы, история, импорт v2,
админ-UI. Отчёт: [11-stage1-report.md](11-stage1-report.md).
- **Этап 2 — следующий.** Курсы, преподаватели, привязка групп, лабораторные
и материалы курса C, активы, копирование лаб.
- Этапы 3–6 — по плану.
## 10. Definition of Done
- Все решения раздела 1 реализованы.
- Текущие лабы и материалы работают в курсе C.
- Преподаватель видит только свои курсы и профили своих студентов.
- Профиль показывает статистику, комментарии и все попытки; файлы скачиваются.
- Повышение, академотпуск, выпуск и архив работают по ТЗ.
- Security-матрица и миграционный upgrade-тест зелёные.
- Backup перед деплоем обязателен; restore проверен на staging.
- Документация, deploy/runbook и smoke обновлены.