# 08. План реализации: группы, курсы, преподаватели **Статус:** план согласован, этапы 0–4 выполнены, этапы 5–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. Ориентировочные миграции Номера уточняются при реализации, чтобы не конфликтовать с максимумами master (identity V3, **academic V5**, progress V12). Максимумы этой ветки: identity V9, **academic V10**, progress V13 (+V14 при необходимости аудита). | Сервис | Миграции | Содержание | |---|---|---| | identity | V4–V9 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status` | | academic | V6–V10 + post-bootstrap step | курсы, преподаватели, группы курса, `labs.course_id` (nullable), материалы курса/аудитории, частичные индексы, активы; `V10` — уникальный индекс `lower(title)` по курсам; `SET NOT NULL` — post-bootstrap шаг вне Flyway | | progress | V13 (+ V14 при необходимости аудита) | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит скачиваний — этап 6 | ### Переход со старой feature-миграции V10 на V13 Старая `progress V10__profile_authors.sql` — временная feature-миграция, её нет в master. - **Production / master:** есть только V11 и V12, записи V10 нет — V13 применяется обычным порядком, ничего делать не нужно. - **Dev/feature-БД, где `V10__profile_authors.sql` уже применена и имеет `success=true`:** удалить только эту запись истории, ничего не удаляя из схемы; V13 идемпотентна и сама создаст недостающие колонки и индекс. Используйте `flyway repair` или точечно: `DELETE FROM progress.flyway_schema_history WHERE version = '10' AND script = 'V10__profile_authors.sql' AND success;`. - **Staging-БД**, которые ненадолго запускали master-коммит `bfdae41` (out-of-order + V10), тоже содержат запись V10 — для них действует тот же порядок. - **Если V10 осталась с `success=false`:** сначала убедитесь, что её частичные изменения откатились на уровне объектов, затем `flyway repair` или удаление неуспешной строки; V13 досоздаст недостающее. - Перед очисткой и деплоем проверьте состояние через `deploy/preflight/progress-v10-v13.sql`. - Переход **forward-only**: после V13 откат на сборку без V13 не пройдёт валидацию Flyway. Не оставляйте `out-of-order` включённым. - **Нельзя** переставлять V11/V12. --- ## 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 — выполнен.** Курсы и `course_no`, история преподавателей и групп, bootstrap курса C, привязка лабораторных к курсу, копирование лаб, админ-UI курсов и лабораторных. Отчёт: [12-stage2-report.md](12-stage2-report.md). - **Этап 3 — выполнен.** Аккаунты преподавателей, курсовая область видимости, внутренние API identity с `LMS_INTERNAL_TOKEN`, кабинет и состав групп преподавателя, профиль студента курса и скачивание всех попыток. Отчёт: [13-stage3-report.md](13-stage3-report.md). - **Этап 4 — выполнен.** Повышение (dry-run/apply/rollback, планировщик 31 августа МСК и догон), жизненный цикл студента, архив группы, усиленное подтверждение удаления, блокировка входа для архивных и выпущенных групп. Отчёт: [14-stage4-report.md](14-stage4-report.md). - **Этап 5 — остаётся.** UX и контент: студенческие страницы курсов, материалы по `/learn/course/{course_no}/...` и аудитории, наследуемая видимость активов, публичные материалы `ALL`. - **Этап 6 — остаётся.** Hardening: полная security-матрица, upgrade-тест миграций, аудит, индексы/пагинация, backup/restore, документация и smoke. ## 10. Definition of Done - Все решения раздела 1 реализованы. - Текущие лабы и материалы работают в курсе C. - Преподаватель видит только свои курсы и профили своих студентов. - Профиль показывает статистику, комментарии и все попытки; файлы скачиваются. - Повышение, академотпуск, выпуск и архив работают по ТЗ. - Security-матрица и миграционный upgrade-тест зелёные. - Backup перед деплоем обязателен; restore проверен на staging. - Документация, deploy/runbook и smoke обновлены.