- .woodpecker/check.yaml, smoke.yaml, release.yaml - local-backend агент: Java 21, Docker Compose v2, Python 3, Node/Playwright - concurrency lms-ci, чтобы smoke/release не занимали порты одновременно - deploy/WOODPECKER.md: server, OAuth Gitea, systemd-агент, security note - удалён .gitlab-ci.yml
29 KiB
08. План реализации: группы, курсы, преподаватели
Статус: план согласован, этапы 0–6 выполнены. Этап 6 закрыт: права преподавателя на запись, аудит и CI добавлены; продуктовые риски (legacy-активы, срок жизни JWT/сессии, пагинация аудита) закрыты 2026-10-28. Оставшиеся ограничения — § 9.1 и 16-stage6-report.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 в
identity.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-матрица и negative-тесты «свой/чужой курс».
- Upgrade-тест миграций.
- Индексы; keyset-пагинация аудита, bulk-выдача групп и пакетные детали лабораторных — выполнены.
- Аудит скачиваний и чувствительных действий (
identity.audit_events,/admin/audit). - CI на Gitea + Woodpecker:
check(test ktlintCheck detekt evidenceCheck) иsmokeна каждом push/MR; полныйrelease— для тегов/вручную. - Проверка backup: создание, retention,
verify, restore на staging. - Документация, deploy/runbook, smoke.
4. Ориентировочные миграции
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master (identity V3, academic V5, progress V12). Максимумы этой ветки: identity V13, academic V12, progress V13.
| Сервис | Миграции | Содержание |
|---|---|---|
| identity | V4–V13 | роли, поля групп, статусы студентов, membership_history, promotion_runs/items, accounts.status; V10 — audit_events; V11 — refresh_tokens (хеш, срок, отзыв, цепочка замен); V12 — absolute_expires_at; V13 — keyset-индексы аудита |
| academic | V6–V12 + post-bootstrap step | курсы, преподаватели, группы курса, labs.course_id (nullable), материалы курса/аудитории, частичные индексы, активы; V10 — уникальный индекс lower(title) по курсам; V11 — backfill audience='ALL' для глобальных глав; V12 — backfill связей article_assets из markdown-ссылок; SET NOT NULL — post-bootstrap шаг вне Flyway |
| progress | V13 | grade_comments.author_account_id, lab_grades.updated_by_account_id, индекс lab_grades(lab_id); аудит реализован в identity V10 |
Переход со старой 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. Контроль качества
- Миграции на пустой БД и на копии текущей; preflight-отчёт.
- Security-матрица: аноним, студент свой/чужой, преподаватель свой/чужой, админ, архивный/выпущенный.
- Профиль: чужой курс/студент/сдача →
403/404; все попытки скачиваются; комментарии с автором; нет N+1. ./gradlew ktlintCheck detekt testпосле каждого этапа.- UI-smoke для четырёх ролей, старые и новые URL, публичные/приватные активы.
- Аудит скачиваний и чувствительных действий.
- Backup: обязательный перед деплоем, ежедневный, с проверкой restore.
- Документация и runbook миграций.
8. Риски и открытые вопросы
- Большой объём одного релиза. Снижаем контрактами, merge gate и независимым QA.
- Межсервисная авторизация. Самый сложный участок; ADR до кода.
- Перенос данных. Нужны preflight и копия БД; группы с нестандартными именами
и дубли номеров лаб могут заблокировать
NOT NULL. - Legacy-активы. Закрыто: связанные активы наследуют видимость статьи
(миграция
V12), а файлы без markdown-ссылки по умолчанию отдают404анониму/студенту/преподавателю. Админ перечисляет их черезGET /api/v1/content/legacy-assets, страницу/admin/legacy-assetsилиscripts/legacy-assets.shдля привязки или удаления. - JWT и сессии. Закрыто: access-токен 15 минут, refresh 7 дней с ротацией
и отзывом, web-сессия 7 дней sliding с абсолютным потолком 30 дней.
Прямые API-клиенты используют
POST /api/v1/auth/refresh; logout и смена пароля отзывают refresh; истёкшие токены чистит ночная задача. - Производительность. Аудит продолжается keyset-курсором, детали всех лабораторных курса и группы курса отдаются пакетно. Отдельный запрос сдач по выбранной лабораторной в ведомости остаётся, но это не N+1 по студентам.
- Комментарии без автора. Старые записи останутся с «автор не указан».
- Архивные студенты. Открывать ли профиль после отвязки группы — уточнить на этапе 3; по умолчанию история курса доступна, если студент был в курсе.
9. Статус реализации
- Этап 0 — выполнен. Схема, contract API профиля, preflight, ADR, проверка на копии БД. Отчёт: 10-stage0-report.md.
- Этап 1 — выполнен. Роли, группы-когорты, статусы, история, импорт v2, админ-UI. Отчёт: 11-stage1-report.md.
- Этап 2 — выполнен. Курсы и
course_no, история преподавателей и групп, bootstrap курса C, привязка лабораторных к курсу, копирование лаб, админ-UI курсов и лабораторных. Отчёт: 12-stage2-report.md. - Этап 3 — выполнен. Аккаунты преподавателей, курсовая область видимости,
внутренние API identity с
LMS_INTERNAL_TOKEN, кабинет и состав групп преподавателя, профиль студента курса и скачивание всех попыток. Отчёт: 13-stage3-report.md. - Этап 4 — выполнен. Повышение (dry-run/apply/rollback, планировщик 31 августа МСК и догон), жизненный цикл студента, архив группы, усиленное подтверждение удаления, блокировка входа для архивных и выпущенных групп. Отчёт: 14-stage4-report.md.
- Этап 5 — выполнен. Студенческие страницы курсов, материалы по
/learn/course/{course_no}/...и аудитории, публичные материалыALL, скрытые материалы для преподавателя, перепроверка статуса web-сессии. Отчёт: 15-stage5-course-materials.md. - Этап 6 — выполнен. Права преподавателя на запись (лабораторные,
ведомость, группы, материалы), центральный аудит
identity.audit_eventsс/admin/audit, CI merge gate и полный hardening. Проверены прогон тестов/линтеров (346, 0 failures), upgrade миграций с master-схемы (identityV4–V13, academicV6–V12, progressV13), security-матрица, gateway, видимость активов и deploy-runbook. LDD baseline: 51 claims (25 P0 / 26 P1), 51/51 proven, browser E2E 18/18 (3 браузера). Отчёт: 16-stage6-report.md.
9.1. Известные пробелы после этапа 6
- Импорт. Старый формат не поддерживается, принимается только шаблон v2. Номер группы — числовой; отдельного требования к ведущим нулям нет.
- Пагинация и N+1. Аудит продолжается keyset-курсором
(occurred_at, id)с offset-fallback для старых ссылок;GET /api/v1/labs/detailsотдаёт все лабораторные курса с уровнями и вариантами одним запросом; группы курса — одним вызовом. Ведомость по-прежнему запрашивает сдачи по выбранной лабораторной — это не N+1 по студентам. - Legacy-активы. Файлы без статьи уже не публичны; админская страница
/admin/legacy-assets, API иscripts/legacy-assets.shготовы. Остаётся прогнать инвентаризацию на production/staging и привязать или удалить файлы. - Сессии. Web-сессия скользящая 7 дней, абсолютный потолок 30 дней
(
LMS_SESSION_ABSOLUTE_TIMEOUT); истёкшие refresh-токены чистит ночная задача (LMS_REFRESH_TOKEN_RETENTION). - CI. Конфигурация
.woodpecker/иci-preflight.shготовы; Woodpecker server/agent и первый реальный pipeline ещё нужно развернуть и подтвердить (deploy/WOODPECKER.md). - Внешняя интеграция «Контингент»/
next.uniyar.ac.ruисключена из плана до появления нового ТЗ и доступа к API.
10. Definition of Done
- Все решения раздела 1 реализованы.
- Текущие лабы и материалы работают в курсе C.
- Преподаватель видит только свои курсы и профили своих студентов, пишет в лабы/ведомость/группы/материалы только своего курса.
- Профиль показывает статистику, комментарии и все попытки; файлы скачиваются.
- Повышение, академотпуск, выпуск и архив работают по ТЗ.
- Аудит чувствительных действий пишется в
identity.audit_eventsи доступен админу на/admin/auditс keyset-пагинацией. - Активы наследуют видимость материала; файлы без привязки не публичны и
доступны только админу через страницу
/admin/legacy-assets. - Access/refresh-токены и web-сессия работают по срокам 15 минут / 7 дней / 7 дней sliding с абсолютным потолком 30 дней; logout, смена пароля и закрытие доступа отзывают refresh.
- Security-матрица и миграционный upgrade-тест зелёные (identity до
V13). - CI (Gitea + Woodpecker) на каждом push/MR прогоняет
test ktlintCheck detekt evidenceCheckи smoke полного стека; полныйreleaseобязателен для тегов. - Backup перед деплоем обязателен; restore проверен на staging.
- Документация, deploy/runbook и smoke обновлены.