docs: права преподавателя, аудит, CI и итоговые проверки
This commit is contained in:
12
README.md
12
README.md
@@ -61,6 +61,8 @@ progress `:8083`, web `:8090`. Интерфейс: `http://localhost:8090`.
|
|||||||
- Gradle daemon фиксируется на JDK 21 (`gradle/gradle-daemon-jvm.properties`): detekt 1.23
|
- Gradle daemon фиксируется на JDK 21 (`gradle/gradle-daemon-jvm.properties`): detekt 1.23
|
||||||
не работает на JDK 25.
|
не работает на JDK 25.
|
||||||
- Тестовая инфраструктура — `libs:test-support` (JUnit 5, MockK, Testcontainers `postgres:18`).
|
- Тестовая инфраструктура — `libs:test-support` (JUnit 5, MockK, Testcontainers `postgres:18`).
|
||||||
|
- CI merge gate — `.gitlab-ci.yml`: `./gradlew --no-daemon test ktlintCheck detekt`
|
||||||
|
на push/MR. Runner должен разрешать privileged `docker:dind` для Testcontainers.
|
||||||
|
|
||||||
## Запуск в Docker (для сервера)
|
## Запуск в Docker (для сервера)
|
||||||
|
|
||||||
@@ -119,6 +121,16 @@ cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
|
|||||||
маршрут показывают сводку, оценки, комментарии с авторами, защиты и все
|
маршрут показывают сводку, оценки, комментарии с авторами, защиты и все
|
||||||
попытки сдачи со скачиванием ZIP; студенту API отдаёт только его профиль
|
попытки сдачи со скачиванием ZIP; студенту API отдаёт только его профиль
|
||||||
и опубликованные данные.
|
и опубликованные данные.
|
||||||
|
- **Работа преподавателя**: ведомость группы переиспользует
|
||||||
|
`/admin/groups/{groupId}?courseId=...` без админских контролов — оценки и
|
||||||
|
комментарии, публикация, уровни/варианты и раздача, защиты (код/reset),
|
||||||
|
скачивание попыток и ZIP группы; лабораторные/уровни/варианты своего курса,
|
||||||
|
привязка/отвязка групп на `/teacher/courses/{id}`, авторские материалы на
|
||||||
|
`/teacher/courses/{id}/content`. Чужой курс отклоняется на сервисах.
|
||||||
|
- **Аудит**: чувствительные действия (профили, скачивания, оценки, защиты,
|
||||||
|
курсы, группы, материалы, преподаватели, жизненный цикл) пишутся в
|
||||||
|
`identity.audit_events`; админ смотрит `/admin/audit` с фильтром по действию
|
||||||
|
и временем МСК.
|
||||||
- **Повышение и жизненный цикл**: `/admin/promotion` — dry-run, применение
|
- **Повышение и жизненный цикл**: `/admin/promotion` — dry-run, применение
|
||||||
и откат повышения (автоматический dry-run 31 августа МСК); на странице
|
и откат повышения (автоматический dry-run 31 августа МСК); на странице
|
||||||
группы — академотпуск, возврат, отчисление, архив и восстановление;
|
группы — академотпуск, возврат, отчисление, архив и восстановление;
|
||||||
|
|||||||
@@ -109,7 +109,16 @@ git pull
|
|||||||
|
|
||||||
`deploy.sh up` **обязательно** делает backup перед обновлением. Если backup
|
`deploy.sh up` **обязательно** делает backup перед обновлением. Если backup
|
||||||
завершился ошибкой, деплой прерывается. Миграции БД (Flyway) применяются
|
завершился ошибкой, деплой прерывается. Миграции БД (Flyway) применяются
|
||||||
автоматически при старте сервисов.
|
автоматически при старте сервисов: identity `V4–V10` (`V10` создаёт
|
||||||
|
`identity.audit_events`), academic `V6–V12`, progress `V13`.
|
||||||
|
|
||||||
|
После обновления проверьте, что админ открывает `/admin/audit` и видит события
|
||||||
|
(например, после входа в профиль студента — `PROFILE_VIEW`); преподаватель и
|
||||||
|
студент на эту страницу не попадают.
|
||||||
|
|
||||||
|
CI (`.gitlab-ci.yml`) на каждом push/MR выполняет
|
||||||
|
`./gradlew --no-daemon test ktlintCheck detekt`; для Testcontainers runner
|
||||||
|
должен разрешать privileged `docker:dind`.
|
||||||
|
|
||||||
## 8. Резервное копирование
|
## 8. Резервное копирование
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ chapters/01-глава/01-раздел/01-статья.md # front-matter: titl
|
|||||||
assets/ # картинки для md
|
assets/ # картинки для md
|
||||||
```
|
```
|
||||||
|
|
||||||
Материалы видны всем; для редактирования — «Материалы» с правами администратора.
|
Материалы видны всем; глобальные материалы редактирует только администратор
|
||||||
|
(«Материалы»). Преподаватель редактирует материалы своего курса на
|
||||||
|
`/teacher/courses/{id}/content`.
|
||||||
|
|
||||||
Импорт бывает в двух режимах: с галочкой «Удалять разделы и статьи, которых нет в архиве»
|
Импорт бывает в двух режимах: с галочкой «Удалять разделы и статьи, которых нет в архиве»
|
||||||
(синхронизация — архив считается источником истины) и без неё (merge — только добавить и
|
(синхронизация — архив считается источником истины) и без неё (merge — только добавить и
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ sequenceDiagram
|
|||||||
DC->>DC: docker build (5 стадий)
|
DC->>DC: docker build (5 стадий)
|
||||||
DC->>PG: старт postgres (volume lms_pgdata)
|
DC->>PG: старт postgres (volume lms_pgdata)
|
||||||
DC->>DC: старт identity → academic → progress → gateway → web
|
DC->>DC: старт identity → academic → progress → gateway → web
|
||||||
Note over DC: Flyway применяет миграции автоматически<br/>при старте сервисов (identity V1..V3, academic V1..V5, progress V1..V9; у web миграций нет)
|
Note over DC: Flyway применяет миграции автоматически<br/>при старте сервисов (identity V1..V10, academic V1..V12, progress V1..V9 и V11..V13; у web миграций нет)
|
||||||
```
|
```
|
||||||
|
|
||||||
- Локально (`docker-compose.yml`) поднимается только `postgres`; прод-стек — `docker-compose.prod.yml`.
|
- Локально (`docker-compose.yml`) поднимается только `postgres`; прод-стек — `docker-compose.prod.yml`.
|
||||||
|
|||||||
@@ -205,6 +205,30 @@ INSERT INTO membership_history(student_id, to_group_id, action, at)
|
|||||||
SELECT student_id, group_id, 'ENROLL', enrolled_at FROM enrollments;
|
SELECT student_id, group_id, 'ENROLL', enrolled_at FROM enrollments;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**V8–V9 — история членства и статусы аккаунтов** реализованы на этапе 1
|
||||||
|
(`membership_history`, `accounts.status`).
|
||||||
|
|
||||||
|
**V10 — журнал аудита (добавлен на этапе 6, `V10__audit_events.sql`)**
|
||||||
|
```sql
|
||||||
|
CREATE TABLE identity.audit_events (
|
||||||
|
id uuid PRIMARY KEY,
|
||||||
|
occurred_at timestamptz NOT NULL DEFAULT now(),
|
||||||
|
actor_account_id uuid,
|
||||||
|
actor_login varchar(100),
|
||||||
|
action varchar(64) NOT NULL,
|
||||||
|
entity_type varchar(64) NOT NULL,
|
||||||
|
entity_id varchar(128),
|
||||||
|
course_id uuid,
|
||||||
|
student_id uuid,
|
||||||
|
details text NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
CREATE INDEX audit_events_occurred_idx ON identity.audit_events (occurred_at DESC);
|
||||||
|
CREATE INDEX audit_events_action_idx ON identity.audit_events (action);
|
||||||
|
```
|
||||||
|
Внешних ключей нет: событие должно переживать удаление субъекта. Запись идёт
|
||||||
|
из сервисов через `POST /api/v1/internal/audit` (`X-Internal-Token`), чтение —
|
||||||
|
`GET /api/v1/audit` и `/admin/audit` только для админа.
|
||||||
|
|
||||||
### academic
|
### academic
|
||||||
|
|
||||||
**V6 — курс (пересоздание)**
|
**V6 — курс (пересоздание)**
|
||||||
@@ -365,7 +389,17 @@ V13 идемпотентна: если старая feature-миграция V10
|
|||||||
- скачивание файлов не даёт доступа к чужим сдачам;
|
- скачивание файлов не даёт доступа к чужим сдачам;
|
||||||
- профиль не заменяет ведомость: оценки по-прежнему выставляются в ведомости;
|
- профиль не заменяет ведомость: оценки по-прежнему выставляются в ведомости;
|
||||||
- старые комментарии без автора отображаются как «автор не указан»;
|
- старые комментарии без автора отображаются как «автор не указан»;
|
||||||
- аудит скачиваний — этап 6.
|
- аудит скачиваний реализован на этапе 6 (см. §5.2).
|
||||||
|
|
||||||
|
### 5.2. Аудит (реализовано на этапе 6)
|
||||||
|
|
||||||
|
Чувствительные действия пишутся в `identity.audit_events` (`V10`): создание и
|
||||||
|
сброс преподавателя, жизненный цикл и архив групп, повышение и удаление,
|
||||||
|
действия с курсами/группами/материалами/лабораториями, просмотр профиля,
|
||||||
|
скачивания, оценки/публикация, раздача вариантов и защиты. Админ смотрит
|
||||||
|
журнал на `/admin/audit` с фильтром по действию; время отображается в МСК.
|
||||||
|
`membership_history` и `promotion_runs/items` остаются доменной историей и
|
||||||
|
дополняют общий журнал.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -465,7 +499,9 @@ V13 идемпотентна: если старая feature-миграция V10
|
|||||||
старые ссылки на текущие материалы не поддерживаются.
|
старые ссылки на текущие материалы не поддерживаются.
|
||||||
7. Миграция существующих лаб/материалов в курс C; «общих» лаб нет.
|
7. Миграция существующих лаб/материалов в курс C; «общих» лаб нет.
|
||||||
8. Немедленный отзыв доступа (преподаватель/группа/публикация материала).
|
8. Немедленный отзыв доступа (преподаватель/группа/публикация материала).
|
||||||
9. Аудит-лог: переводы, академ, повышение, назначения преподавателей, публикации, архив/удаление.
|
9. Аудит-лог: переводы, академ, повышение, назначения преподавателей, публикации,
|
||||||
|
архив/удаление — реализован в `identity.audit_events` (`V10`), просмотр
|
||||||
|
`/admin/audit`.
|
||||||
10. Производительность: индексы `course_id`/`group_id`, пагинация материалов и групп.
|
10. Производительность: индексы `course_id`/`group_id`, пагинация материалов и групп.
|
||||||
11. Бэкфилл `enrollment_year` для существующих групп — приблизительный, требует подтверждения админом.
|
11. Бэкфилл `enrollment_year` для существующих групп — приблизительный, требует подтверждения админом.
|
||||||
12. Лабы: уникальность номера — в рамках курса (`(number, course_id)`), прежний ключ по семестру снимается.
|
12. Лабы: уникальность номера — в рамках курса (`(number, course_id)`), прежний ключ по семестру снимается.
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
# 08. План реализации: группы, курсы, преподаватели
|
# 08. План реализации: группы, курсы, преподаватели
|
||||||
|
|
||||||
**Статус:** план согласован, этапы 0–5 выполнены. Проверка этапа 6 выполнена
|
**Статус:** план согласован, этапы 0–6 выполнены. Этап 6 закрыт: права
|
||||||
(тесты, upgrade миграций, security, deploy-runbook), но этап 6 закрыт не
|
преподавателя на запись, аудит и CI добавлены; остаточные риски — § 9.1 и
|
||||||
полностью: остаются права преподавателя на запись и CI —
|
[16-stage6-report.md](16-stage6-report.md).
|
||||||
см. § 9.1 и [16-stage6-report.md](16-stage6-report.md).
|
|
||||||
**Основа:** [07-groups-courses.md](07-groups-courses.md).
|
**Основа:** [07-groups-courses.md](07-groups-courses.md).
|
||||||
**Дата решений:** 2026-10-23.
|
**Дата решений:** 2026-10-23.
|
||||||
|
|
||||||
@@ -117,7 +116,7 @@
|
|||||||
отдельная таблица не нужна.
|
отдельная таблица не нужна.
|
||||||
- Для производительности: один агрегирующий запрос/эндпоинт, пакетная выборка
|
- Для производительности: один агрегирующий запрос/эндпоинт, пакетная выборка
|
||||||
комментариев, попыток, защит и публикаций по списку `lab_id`; без N+1.
|
комментариев, попыток, защит и публикаций по списку `lab_id`; без N+1.
|
||||||
- Аудит скачиваний: этап 6 (журнал `audit_events` или отдельная таблица).
|
- Аудит скачиваний реализован на этапе 6 в `identity.audit_events`.
|
||||||
|
|
||||||
### 2.6. Ограничения
|
### 2.6. Ограничения
|
||||||
|
|
||||||
@@ -192,10 +191,14 @@
|
|||||||
|
|
||||||
### Этап 6. Hardening
|
### Этап 6. Hardening
|
||||||
|
|
||||||
- Полная security-матрица.
|
- Права преподавателя на запись: лабораторные/уровни/варианты, ведомость,
|
||||||
|
привязка групп, авторские материалы курса.
|
||||||
|
- Полная security-матрица и negative-тесты «свой/чужой курс».
|
||||||
- Upgrade-тест миграций.
|
- Upgrade-тест миграций.
|
||||||
- Индексы и пагинация.
|
- Индексы; пагинация и N+1 — follow-up (§ 9.1).
|
||||||
- Аудит скачиваний и чувствительных действий.
|
- Аудит скачиваний и чувствительных действий (`identity.audit_events`,
|
||||||
|
`/admin/audit`).
|
||||||
|
- CI merge gate: `test ktlintCheck detekt` на каждом push/MR.
|
||||||
- Проверка backup: создание, retention, `verify`, restore на staging.
|
- Проверка backup: создание, retention, `verify`, restore на staging.
|
||||||
- Документация, deploy/runbook, smoke.
|
- Документация, deploy/runbook, smoke.
|
||||||
|
|
||||||
@@ -205,13 +208,13 @@
|
|||||||
|
|
||||||
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master
|
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master
|
||||||
(identity V3, **academic V5**, progress V12). Максимумы этой ветки:
|
(identity V3, **academic V5**, progress V12). Максимумы этой ветки:
|
||||||
identity V9, **academic V12**, progress V13 (+V14 при необходимости аудита).
|
identity V10, **academic V12**, progress V13.
|
||||||
|
|
||||||
| Сервис | Миграции | Содержание |
|
| Сервис | Миграции | Содержание |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| identity | V4–V9 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status` |
|
| identity | V4–V10 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status`; `V10` — `audit_events` без внешних ключей и индексы по `occurred_at`/`action` |
|
||||||
| 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 |
|
| 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 (+ V14 при необходимости аудита) | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит скачиваний — этап 6 |
|
| progress | V13 | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит реализован в identity `V10` |
|
||||||
|
|
||||||
### Переход со старой feature-миграции V10 на V13
|
### Переход со старой feature-миграции V10 на V13
|
||||||
|
|
||||||
@@ -320,12 +323,12 @@ identity V9, **academic V12**, progress V13 (+V14 при необходимос
|
|||||||
`/learn/course/{course_no}/...` и аудитории, публичные материалы `ALL`,
|
`/learn/course/{course_no}/...` и аудитории, публичные материалы `ALL`,
|
||||||
скрытые материалы для преподавателя, перепроверка статуса web-сессии.
|
скрытые материалы для преподавателя, перепроверка статуса web-сессии.
|
||||||
Отчёт: [15-stage5-course-materials.md](15-stage5-course-materials.md).
|
Отчёт: [15-stage5-course-materials.md](15-stage5-course-materials.md).
|
||||||
- **Этап 6 — проверка выполнена, закрыт не полностью.** Проверены полный
|
- **Этап 6 — выполнен.** Права преподавателя на запись (лабораторные,
|
||||||
прогон тестов/линтеров, upgrade миграций с master-схемы, security-матрица,
|
ведомость, группы, материалы), центральный аудит `identity.audit_events`
|
||||||
gateway, видимость активов и deploy-runbook. Отчёт: [16-stage6-report.md](16-stage6-report.md).
|
с `/admin/audit`, CI merge gate и полный hardening. Проверены прогон
|
||||||
Не закрыты: права преподавателя на запись, аудит скачиваний и CI. Остальные
|
тестов/линтеров (**267**), upgrade миграций с master-схемы (identity
|
||||||
пункты hardening (индексы, backup/restore, документация, smoke) описаны в
|
`V4–V10`, academic `V6–V12`, progress `V13`), security-матрица, gateway,
|
||||||
отчёте и чек-листе.
|
видимость активов и deploy-runbook. Отчёт: [16-stage6-report.md](16-stage6-report.md).
|
||||||
|
|
||||||
### 9.1. Известные пробелы после этапа 6
|
### 9.1. Известные пробелы после этапа 6
|
||||||
|
|
||||||
@@ -333,27 +336,26 @@ identity V9, **academic V12**, progress V13 (+V14 при необходимос
|
|||||||
проверяет `article_assets → article → section → chapter`, а `V12`
|
проверяет `article_assets → article → section → chapter`, а `V12`
|
||||||
восстанавливает связи из markdown. Файлы без ссылок в статьях остаются
|
восстанавливает связи из markdown. Файлы без ссылок в статьях остаются
|
||||||
публичными; нужна инвентаризация и привязка или удаление.
|
публичными; нужна инвентаризация и привязка или удаление.
|
||||||
- **Преподаватель — в основном read-only.** Авторство лабораторных/материалов,
|
|
||||||
распределение, оценки, комментарии и защита остаются admin-only в UI/API;
|
|
||||||
ТЗ ожидает преподавательский self-service.
|
|
||||||
- **Имена групп в кабинете преподавателя.** На странице курса показывается
|
|
||||||
UUID группы, batch-разрешения имён нет.
|
|
||||||
- **Аудит.** Есть история членства и журнал повышения, но нет audit-log
|
|
||||||
скачиваний, просмотров профилей, назначений и публикаций.
|
|
||||||
- **Импорт.** Старый формат не поддерживается; ведущие нули номера группы
|
- **Импорт.** Старый формат не поддерживается; ведущие нули номера группы
|
||||||
нормализуются (`01` → `1`).
|
нормализуются (`01` → `1`).
|
||||||
- **JWT 30 дней.** Web перепроверяет статус каждые 45 секунд, но прямой API
|
- **JWT 30 дней.** Web перепроверяет статус каждые 45 секунд, но прямой API
|
||||||
с уже выданным токеном живёт до истечения.
|
с уже выданным токеном живёт до истечения; нужны короткий TTL + refresh
|
||||||
- **CI отсутствует.** Гейт — локальный `./gradlew test ktlintCheck detekt`;
|
или revocation.
|
||||||
автоматического pipeline и upgrade-теста в CI нет.
|
- **Пагинация и N+1.** Журнал аудита ограничен `limit` без offset/keyset;
|
||||||
|
часть списков групп, материалов и ведомости отдаётся целиком; на больших
|
||||||
|
объёмах нужны пагинация и пакетные выборки.
|
||||||
|
|
||||||
## 10. Definition of Done
|
## 10. Definition of Done
|
||||||
|
|
||||||
- Все решения раздела 1 реализованы.
|
- Все решения раздела 1 реализованы.
|
||||||
- Текущие лабы и материалы работают в курсе C.
|
- Текущие лабы и материалы работают в курсе C.
|
||||||
- Преподаватель видит только свои курсы и профили своих студентов.
|
- Преподаватель видит только свои курсы и профили своих студентов, пишет
|
||||||
|
в лабы/ведомость/группы/материалы только своего курса.
|
||||||
- Профиль показывает статистику, комментарии и все попытки; файлы скачиваются.
|
- Профиль показывает статистику, комментарии и все попытки; файлы скачиваются.
|
||||||
- Повышение, академотпуск, выпуск и архив работают по ТЗ.
|
- Повышение, академотпуск, выпуск и архив работают по ТЗ.
|
||||||
|
- Аудит чувствительных действий пишется в `identity.audit_events` и доступен
|
||||||
|
админу на `/admin/audit`.
|
||||||
- Security-матрица и миграционный upgrade-тест зелёные.
|
- Security-матрица и миграционный upgrade-тест зелёные.
|
||||||
|
- CI на каждом push/MR прогоняет `test ktlintCheck detekt`.
|
||||||
- Backup перед деплоем обязателен; restore проверен на staging.
|
- Backup перед деплоем обязателен; restore проверен на staging.
|
||||||
- Документация, deploy/runbook и smoke обновлены.
|
- Документация, deploy/runbook и smoke обновлены.
|
||||||
|
|||||||
@@ -281,5 +281,6 @@ GET /api/v1/internal/courses/{courseId}/labs
|
|||||||
|
|
||||||
- Показывать ли профиль для архивных/отвязанных студентов (история или `404`).
|
- Показывать ли профиль для архивных/отвязанных студентов (история или `404`).
|
||||||
- Нужен ли студенческий просмотр своего профиля в первом релизе.
|
- Нужен ли студенческий просмотр своего профиля в первом релизе.
|
||||||
- Формат аудита скачиваний: общий `audit_events` или отдельная таблица.
|
- ~~Формат аудита скачиваний: общий `audit_events` или отдельная таблица.~~
|
||||||
- Ограничение количества попыток в ответе и пагинация.
|
Решено на этапе 6: общий `identity.audit_events` (`V10`).
|
||||||
|
- Ограничение количества попыток в ответе и пагинация — follow-up.
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
- ADR с ключевыми решениями: [adr/0001-groups-courses-foundation.md](adr/0001-groups-courses-foundation.md).
|
- ADR с ключевыми решениями: [adr/0001-groups-courses-foundation.md](adr/0001-groups-courses-foundation.md).
|
||||||
- Контракт API профиля студента: [09-teacher-student-profile.md](09-teacher-student-profile.md).
|
- Контракт API профиля студента: [09-teacher-student-profile.md](09-teacher-student-profile.md).
|
||||||
- Миграции:
|
- Миграции:
|
||||||
- identity `V4–V9`: роли, группы-когорты, статусы студентов, повышение,
|
- identity `V4–V10`: роли, группы-когорты, статусы студентов, повышение,
|
||||||
история членства, статусы аккаунтов;
|
история членства, статусы аккаунтов; `V10` (`audit_events`) добавлена
|
||||||
|
позже, на этапе 6;
|
||||||
- academic `V6–V12`: курсы, преподаватели, группы курса, `labs.course_id`,
|
- academic `V6–V12`: курсы, преподаватели, группы курса, `labs.course_id`,
|
||||||
материалы курса/аудитории, индексы активов; `V10` (уникальный индекс
|
материалы курса/аудитории, индексы активов; `V10` (уникальный индекс
|
||||||
`lower(title)` по курсам) добавлена на этапе 2, после этого отчёта;
|
`lower(title)` по курсам) добавлена на этапе 2, после этого отчёта;
|
||||||
|
|||||||
@@ -154,7 +154,10 @@ constant-time. Gateway не маршрутизирует `/api/v1/internal/**`,
|
|||||||
По результатам последнего прогона (2026-10-27) перечисленные наборы прошли
|
По результатам последнего прогона (2026-10-27) перечисленные наборы прошли
|
||||||
с `0 failures / 0 errors`.
|
с `0 failures / 0 errors`.
|
||||||
|
|
||||||
## 6. Известные пробелы
|
## 6. Известные пробелы (исторические, на момент этапа 3)
|
||||||
|
|
||||||
|
> Список ниже сохранён как история; актуальный статус — в разделе 7 и
|
||||||
|
> [16-stage6-report.md](16-stage6-report.md).
|
||||||
|
|
||||||
- **Контент курса.** URL материалов вида `/learn/course/{course_no}/...`,
|
- **Контент курса.** URL материалов вида `/learn/course/{course_no}/...`,
|
||||||
проверка `chapters.course_id/audience`, наследуемая видимость активов
|
проверка `chapters.course_id/audience`, наследуемая видимость активов
|
||||||
@@ -170,3 +173,21 @@ constant-time. Gateway не маршрутизирует `/api/v1/internal/**`,
|
|||||||
страницы профиля — преподавательская и админская.
|
страницы профиля — преподавательская и админская.
|
||||||
- **Аудит скачиваний.** Скачивание попыток пока не пишется в аудит;
|
- **Аудит скачиваний.** Скачивание попыток пока не пишется в аудит;
|
||||||
это этап 6.
|
это этап 6.
|
||||||
|
|
||||||
|
## 7. Обновление после этапа 6
|
||||||
|
|
||||||
|
- **Права преподавателя на запись** закрыты: преподаватель своего курса
|
||||||
|
управляет лабораторными/уровнями/вариантами, выставляет оценки и
|
||||||
|
комментарии, публикует их, раздаёт варианты, назначает/запускает защиты,
|
||||||
|
привязывает группы и авторит материалы курса. Ведомость переиспользует
|
||||||
|
`/admin/groups/{groupId}?courseId=...` без админских контролов; сервисы
|
||||||
|
проверяют курс и отклоняют чужие лабы/студентов/группы.
|
||||||
|
- **Имена групп в кабинете преподавателя** разрешены: страница курса
|
||||||
|
показывает название, при недоступности — id.
|
||||||
|
- **Контент курса и активы** закрыты на этапе 5: URL
|
||||||
|
`/learn/course/{course_no}/...`, проверка `chapters.course_id/audience`,
|
||||||
|
наследуемая видимость активов; на этапе 6 добавлено авторство материалов
|
||||||
|
преподавателем (`/teacher/courses/{id}/content`), глобальные материалы
|
||||||
|
остаются admin-only.
|
||||||
|
- **Аудит скачиваний** реализован: `identity.audit_events`, внутренний
|
||||||
|
`POST /api/v1/internal/audit` и админский `/admin/audit`.
|
||||||
|
|||||||
@@ -53,3 +53,13 @@
|
|||||||
публичным. Нужна инвентаризация таких файлов и привязка к статье либо удаление;
|
публичным. Нужна инвентаризация таких файлов и привязка к статье либо удаление;
|
||||||
до этого поведение сохраняется осознанно, чтобы не ломать старые публичные
|
до этого поведение сохраняется осознанно, чтобы не ломать старые публичные
|
||||||
ссылки.
|
ссылки.
|
||||||
|
|
||||||
|
## Авторство материалов преподавателем (добавлено на этапе 6)
|
||||||
|
|
||||||
|
Преподаватель своего курса редактирует главы, разделы и статьи на
|
||||||
|
`/teacher/courses/{id}/content`: создание/переименование/удаление, markdown-
|
||||||
|
редактор с предпросмотром, публикация/снятие с публикации. API —
|
||||||
|
`/api/v1/courses/{courseId}/content/**`; любая изменяемая сущность проверяется
|
||||||
|
на принадлежность курсу. Глобальные материалы (`course_id IS NULL`,
|
||||||
|
аудитории `ALL/STUDENTS/TEACHERS`) по-прежнему создаёт и публикует только
|
||||||
|
админ.
|
||||||
|
|||||||
@@ -2,23 +2,22 @@
|
|||||||
|
|
||||||
**Дата:** 2026-10-27.
|
**Дата:** 2026-10-27.
|
||||||
**Ветка:** `feature/groups-courses`.
|
**Ветка:** `feature/groups-courses`.
|
||||||
**Статус:** проверка этапа 6 выполнена; этап 6 закрыт **не полностью**.
|
**Статус:** этап 6 закрыт. Остаточные риски перечислены в разделе 4 и не
|
||||||
Миграции, security-матрица, upgrade-тест, видимость активов и deploy-runbook
|
блокируют передачу.
|
||||||
проверены. Открытыми остаются права преподавателя на запись, аудит и CI — см.
|
|
||||||
раздел 4.
|
|
||||||
|
|
||||||
## 1. Итоговый объём этапов 0–5
|
## 1. Итоговый объём этапов 0–6
|
||||||
|
|
||||||
| Этап | Что реализовано | Отчёт |
|
| Этап | Что реализовано | Отчёт |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 0 | Схема и контракты: identity `V4–V9`, academic `V6–V12`, progress `V13`; preflight-скрипты; ADR; контракт профиля студента курса | [10](10-stage0-report.md) |
|
| 0 | Схема и контракты: identity `V4–V10` (`V10` — аудит, добавлена на этапе 6), academic `V6–V12`, progress `V13`; preflight-скрипты; ADR; контракт профиля студента курса | [10](10-stage0-report.md) |
|
||||||
| 1 | Роли `ADMIN/TEACHER/STUDENT`, группы-когорты и парсер имени, статусы студентов и аккаунтов, `membership_history`, импорт v2 | [11](11-stage1-report.md) |
|
| 1 | Роли `ADMIN/TEACHER/STUDENT`, группы-когорты и парсер имени, статусы студентов и аккаунтов, `membership_history`, импорт v2 | [11](11-stage1-report.md) |
|
||||||
| 2 | Курсы, `course_no` для браузерных URL, история преподавателей и групп, bootstrap курса C, привязка лабораторных к курсу, копирование лабораторных, админ-UI | [12](12-stage2-report.md) |
|
| 2 | Курсы, `course_no` для браузерных URL, история преподавателей и групп, bootstrap курса C, привязка лабораторных к курсу, копирование лабораторных, админ-UI | [12](12-stage2-report.md) |
|
||||||
| 3 | Аккаунты преподавателей, курсовая область видимости, внутренние API identity с `LMS_INTERNAL_TOKEN`, кабинет преподавателя, профиль студента курса и скачивание всех попыток | [13](13-stage3-report.md) |
|
| 3 | Аккаунты преподавателей, курсовая область видимости, внутренние API identity с `LMS_INTERNAL_TOKEN`, кабинет преподавателя, профиль студента курса и скачивание всех попыток | [13](13-stage3-report.md) |
|
||||||
| 4 | Повышение (dry-run/apply/rollback, планировщик 31 августа МСК), академотпуск, отчисление, выпуск, архив/восстановление, усиленное удаление, блокировка входа | [14](14-stage4-report.md) |
|
| 4 | Повышение (dry-run/apply/rollback, планировщик 31 августа МСК), академотпуск, отчисление, выпуск, архив/восстановление, усиленное удаление, блокировка входа | [14](14-stage4-report.md) |
|
||||||
| 5 | Курсовые материалы `/learn/course/{course_no}/...`, публичные глобальные материалы `ALL`, скрытые материалы для преподавателя, перепроверка статуса web-сессии | [15](15-stage5-course-materials.md) |
|
| 5 | Курсовые материалы `/learn/course/{course_no}/...`, публичные глобальные материалы `ALL`, скрытые материалы для преподавателя, перепроверка статуса web-сессии | [15](15-stage5-course-materials.md) |
|
||||||
|
| 6 | Права преподавателя на запись (лабораторные, ведомость, группы, материалы), центральный аудит `identity.audit_events`, CI merge gate, hardening и deploy-runbook | этот отчёт |
|
||||||
|
|
||||||
Ключевые инварианты, действующие после этапов 0–5:
|
Ключевые инварианты, действующие после этапов 0–6:
|
||||||
|
|
||||||
- все лабораторные принадлежат курсу; существующие лабораторные, главы и
|
- все лабораторные принадлежат курсу; существующие лабораторные, главы и
|
||||||
активные группы перенесены в курс C;
|
активные группы перенесены в курс C;
|
||||||
@@ -29,9 +28,15 @@
|
|||||||
- активы наследуют видимость статьи → раздела → главы: курсовые требуют доступа
|
- активы наследуют видимость статьи → раздела → главы: курсовые требуют доступа
|
||||||
к курсу, глобальные — аудитории, а старые файлы без привязки остаются
|
к курсу, глобальные — аудитории, а старые файлы без привязки остаются
|
||||||
публичными (см. раздел 4);
|
публичными (см. раздел 4);
|
||||||
|
- преподаватель своего курса может писать: лабораторные/уровни/варианты,
|
||||||
|
ведомость, привязка групп и авторские материалы курса; чужие курс, лаба,
|
||||||
|
студент и группа отклоняются на сервисах;
|
||||||
- вход закрыт для отчисленных, выпущенных и архивных студентов, а также для
|
- вход закрыт для отчисленных, выпущенных и архивных студентов, а также для
|
||||||
архивных аккаунтов; web дополнительно перепроверяет статус;
|
архивных аккаунтов; web дополнительно перепроверяет статус;
|
||||||
- повышение и жизненный цикл пишут историю; применение повышения идемпотентно.
|
- повышение и жизненный цикл пишут историю; применение повышения идемпотентно;
|
||||||
|
- чувствительные действия пишутся в центральный `identity.audit_events`,
|
||||||
|
админ смотрит журнал на `/admin/audit`;
|
||||||
|
- CI на каждый push/MR прогоняет `test ktlintCheck detekt`.
|
||||||
|
|
||||||
## 2. Проверка
|
## 2. Проверка
|
||||||
|
|
||||||
@@ -40,15 +45,11 @@
|
|||||||
Выполнено 2026-10-27:
|
Выполнено 2026-10-27:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./gradlew test ktlintCheck detekt
|
./gradlew --no-daemon test ktlintCheck detekt
|
||||||
```
|
```
|
||||||
|
|
||||||
Результат: `BUILD SUCCESSFUL`, код возврата `0`, все модули зелёные.
|
Результат: `BUILD SUCCESSFUL`, код возврата `0`, все модули зелёные.
|
||||||
|
|
||||||
Счётчики включают новые тесты видимости активов: `academic-service` +9
|
|
||||||
(`ContentAssetAccessIntegrationTest`, импорт активов), `web` +2
|
|
||||||
(`ApiClientContentAssetTest`). Итог — **183 теста, 0 падений**.
|
|
||||||
|
|
||||||
Для получения актуальных счётчиков тесты запущены принудительно:
|
Для получения актуальных счётчиков тесты запущены принудительно:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -57,16 +58,17 @@
|
|||||||
|
|
||||||
| Модуль | Тестов | Failures | Errors | Skipped |
|
| Модуль | Тестов | Failures | Errors | Skipped |
|
||||||
|---|---:|---:|---:|---:|
|
|---|---:|---:|---:|---:|
|
||||||
| identity-service | 62 | 0 | 0 | 0 |
|
| identity-service | 72 | 0 | 0 | 0 |
|
||||||
| academic-service | 62 | 0 | 0 | 0 |
|
| academic-service | 88 | 0 | 0 | 0 |
|
||||||
| web | 42 | 0 | 0 | 0 |
|
| progress-service | 33 | 0 | 0 | 0 |
|
||||||
| progress-service | 12 | 0 | 0 | 0 |
|
| web | 69 | 0 | 0 | 0 |
|
||||||
| markdown | 5 | 0 | 0 | 0 |
|
| markdown | 5 | 0 | 0 | 0 |
|
||||||
| **Итого** | **183** | **0** | **0** | **0** |
|
| **Итого** | **267** | **0** | **0** | **0** |
|
||||||
|
|
||||||
`ktlintCheck` и `detekt` прошли без замечаний. Отдельных unit-тестов у gateway
|
`ktlintCheck` и `detekt` прошли без замечаний. Отдельных unit-тестов у gateway
|
||||||
и `common-http` в текущем дереве нет; маршрутизация gateway проверена
|
и `common-http` в текущем дереве нет; маршрутизация gateway проверена
|
||||||
конфигурацией и security-матрицей ниже.
|
конфигурацией и security-матрицей ниже. Тот же набор команд выполняет
|
||||||
|
`.gitlab-ci.yml`.
|
||||||
|
|
||||||
### 2.2. Upgrade-тест миграций
|
### 2.2. Upgrade-тест миграций
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@
|
|||||||
(`ПИ-11БО`, `ПИ-21БО`), зачисления, семестр, лабораторная, глава/раздел/
|
(`ПИ-11БО`, `ПИ-21БО`), зачисления, семестр, лабораторная, глава/раздел/
|
||||||
статья/актив, оценка, комментарий и попытка сдачи.
|
статья/актив, оценка, комментарий и попытка сдачи.
|
||||||
3. Поверх master применены миграции ветки:
|
3. Поверх master применены миграции ветки:
|
||||||
- identity `V4–V9`;
|
- identity `V4–V10`;
|
||||||
- academic `V6–V12`;
|
- academic `V6–V12`;
|
||||||
- progress `V13`.
|
- progress `V13`.
|
||||||
4. Выполнены postflight-запросы и повторное применение `V13`.
|
4. Выполнены postflight-запросы и повторное применение `V13`.
|
||||||
@@ -90,7 +92,8 @@
|
|||||||
(`ПИ-11БО` → поток `ПИ`, курс 1, номер 1, `БО`, год набора 2026; `ПИ-21БО` →
|
(`ПИ-11БО` → поток `ПИ`, курс 1, номер 1, `БО`, год набора 2026; `ПИ-21БО` →
|
||||||
курс 2, год 2025); статус `ACTIVE`; созданы `promotion_runs`,
|
курс 2, год 2025); статус `ACTIVE`; созданы `promotion_runs`,
|
||||||
`promotion_run_items`; зачисления перенесены в `membership_history`
|
`promotion_run_items`; зачисления перенесены в `membership_history`
|
||||||
действием `ENROLL`; добавлен `accounts.status`.
|
действием `ENROLL`; добавлен `accounts.status`; `V10` создаёт
|
||||||
|
`identity.audit_events` без внешних ключей и индексы по `occurred_at`/`action`.
|
||||||
- **academic:** добавлены `labs.course_id`, `chapters.course_id/audience`,
|
- **academic:** добавлены `labs.course_id`, `chapters.course_id/audience`,
|
||||||
индексы `labs_number_course_uidx`, `chapters_slug_global_uidx`,
|
индексы `labs_number_course_uidx`, `chapters_slug_global_uidx`,
|
||||||
`chapters_slug_course_uidx`, `article_assets_article_idx`,
|
`chapters_slug_course_uidx`, `article_assets_article_idx`,
|
||||||
@@ -116,8 +119,9 @@
|
|||||||
|
|
||||||
### 2.3. Security-проверки
|
### 2.3. Security-проверки
|
||||||
|
|
||||||
Проверено автоматическими тестами (полный прогон 183 теста) и разбором
|
Проверено автоматическими тестами (полный прогон 267 тестов) и разбором
|
||||||
конфигурации gateway.
|
конфигурации gateway. Запись преподавателя дополнительно покрыта
|
||||||
|
negative-тестами «свой/чужой курс».
|
||||||
|
|
||||||
| Сценарий | Ожидание | Факт |
|
| Сценарий | Ожидание | Факт |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
@@ -131,8 +135,11 @@
|
|||||||
| Глобальный `STUDENTS`-актив → преподаватель | `403` | `403`; студент получает `200` |
|
| Глобальный `STUDENTS`-актив → преподаватель | `403` | `403`; студент получает `200` |
|
||||||
| Legacy-актив без связи со статьёй | публично | `200`; остаточный риск, раздел 4 |
|
| Legacy-актив без связи со статьёй | публично | `200`; остаточный риск, раздел 4 |
|
||||||
| Админ → курс, профиль, скачивание | полный доступ | доступ к любому курсу и студенту, скачивание любой попытки курса |
|
| Админ → курс, профиль, скачивание | полный доступ | доступ к любому курсу и студенту, скачивание любой попытки курса |
|
||||||
| Преподаватель своего курса | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки |
|
| Преподаватель своего курса → чтение | курс, группы, профиль, материалы | видит опубликованные и скрытые материалы, всех студентов активной группы курса, неопубликованные оценки и все попытки |
|
||||||
| Преподаватель чужого курса | `403` | `403` на материалы, курс по `course_no`, состав группы и профиль |
|
| Преподаватель своего курса → запись | лабы/уровни/варианты, ведомость, группы, материалы | оценки/комментарии, публикация, уровни/варианты и раздача, защиты (код/reset), привязка групп, авторство материалов; проверки в progress `CourseScopeService` и academic course/content authoring |
|
||||||
|
| Преподаватель чужого курса | `403` | `403` на чтение и запись: материалы, курс по `course_no`, состав группы, профиль, лабы, оценки, группы |
|
||||||
|
| Аудит → админ | чтение журнала | `GET /api/v1/audit?limit=&action=` и `/admin/audit` доступны только `ADMIN` |
|
||||||
|
| Аудит → запись из сервисов | только по токену | `POST /api/v1/internal/audit` с `X-Internal-Token`; без/неверный токен — `403` |
|
||||||
| Студент → свой профиль | только своё и опубликованное | скрытые оценки/комментарии отсутствуют; чужой `studentId` — `403` |
|
| Студент → свой профиль | только своё и опубликованное | скрытые оценки/комментарии отсутствуют; чужой `studentId` — `403` |
|
||||||
| Студент → чужой курс/группа | `403` | доступ только к курсам активной группы; архивный курс — `403` |
|
| Студент → чужой курс/группа | `403` | доступ только к курсам активной группы; архивный курс — `403` |
|
||||||
| Чужая сдача / лаба другого курса | `403` | скачивание отклоняется; своя попытка курса скачивается ZIP |
|
| Чужая сдача / лаба другого курса | `403` | скачивание отклоняется; своя попытка курса скачивается ZIP |
|
||||||
@@ -142,7 +149,8 @@
|
|||||||
| Web-сессия | быстрый отзыв доступа | статус перепроверяется не реже чем раз в 45 секунд; `active=false` или `401/403` инвалидируют сессию; недоступность identity не разлогинивает всех |
|
| Web-сессия | быстрый отзыв доступа | статус перепроверяется не реже чем раз в 45 секунд; `active=false` или `401/403` инвалидируют сессию; недоступность identity не разлогинивает всех |
|
||||||
|
|
||||||
Остаточные риски перечислены в разделе 4: legacy-активы без привязки,
|
Остаточные риски перечислены в разделе 4: legacy-активы без привязки,
|
||||||
права преподавателя на запись и 30-дневный JWT для прямого API.
|
30-дневный JWT для прямого API, ведущие нули номера группы и отсутствие
|
||||||
|
пагинации/N+1 на больших объёмах.
|
||||||
|
|
||||||
## 3. Чек-лист деплоя
|
## 3. Чек-лист деплоя
|
||||||
|
|
||||||
@@ -158,14 +166,18 @@
|
|||||||
Там же задан `LMS_DB_PASSWORD`; `.env.prod` не коммитится.
|
Там же задан `LMS_DB_PASSWORD`; `.env.prod` не коммитится.
|
||||||
- [ ] Проверить, что на сервере нет незакоммиченных изменений и выполнен
|
- [ ] Проверить, что на сервере нет незакоммиченных изменений и выполнен
|
||||||
`git pull` нужной ветки/тега.
|
`git pull` нужной ветки/тега.
|
||||||
|
- [ ] **CI зелёный.** `.gitlab-ci.yml` выполняет
|
||||||
|
`./gradlew --no-daemon test ktlintCheck detekt`. Runner должен разрешать
|
||||||
|
privileged-сервисы `docker:dind`: Testcontainers поднимает PostgreSQL 18
|
||||||
|
через Docker-in-Docker.
|
||||||
|
|
||||||
### 3.2. Порядок выкладки
|
### 3.2. Порядок выкладки
|
||||||
|
|
||||||
1. **Flyway.** `./deploy/deploy.sh up` собирает образы и запускает
|
1. **Flyway.** `./deploy/deploy.sh up` собирает образы и запускает
|
||||||
`postgres → identity → academic → progress → gateway → web`. Миграции
|
`postgres → identity → academic → progress → gateway → web`. Миграции
|
||||||
применяются автоматически при старте сервисов:
|
применяются автоматически при старте сервисов:
|
||||||
identity `V4–V9`, academic `V6–V12`, progress `V13`.
|
identity `V4–V10` (`V10` — `identity.audit_events`), academic `V6–V12`,
|
||||||
Отдельный ручной запуск Flyway не нужен.
|
progress `V13`. Отдельный ручной запуск Flyway не нужен.
|
||||||
2. **Bootstrap курса C.** После входа админа открыть `/admin/courses`.
|
2. **Bootstrap курса C.** После входа админа открыть `/admin/courses`.
|
||||||
Пока курсов нет, показан блок первичной настройки. Bootstrap идемпотентно
|
Пока курсов нет, показан блок первичной настройки. Bootstrap идемпотентно
|
||||||
создаёт/находит курс «Промышленное программирование на языке C»,
|
создаёт/находит курс «Промышленное программирование на языке C»,
|
||||||
@@ -181,7 +193,8 @@
|
|||||||
- `chapters_without_course = 0` (если все текущие материалы курсовые);
|
- `chapters_without_course = 0` (если все текущие материалы курсовые);
|
||||||
- ровно один курс C;
|
- ровно один курс C;
|
||||||
- `comments_without_author` соответствует старым комментариям;
|
- `comments_without_author` соответствует старым комментариям;
|
||||||
- группы без полного разбора отсутствуют.
|
- группы без полного разбора отсутствуют;
|
||||||
|
- таблица `identity.audit_events` существует (`V10`).
|
||||||
4. **V10 → V13 — только dev/staging.** На production/master записи
|
4. **V10 → V13 — только dev/staging.** На production/master записи
|
||||||
`progress V10__profile_authors.sql` нет, ничего удалять не нужно. Не путать
|
`progress V10__profile_authors.sql` нет, ничего удалять не нужно. Не путать
|
||||||
с academic `V10__course_title_unique.sql` — он относится к другой схеме и
|
с academic `V10__course_title_unique.sql` — он относится к другой схеме и
|
||||||
@@ -204,8 +217,12 @@
|
|||||||
отдаёт `UP`.
|
отдаёт `UP`.
|
||||||
- [ ] **Админ:** вход, `/admin/courses` (курс C и счётчики), `/admin/groups`,
|
- [ ] **Админ:** вход, `/admin/courses` (курс C и счётчики), `/admin/groups`,
|
||||||
`/admin/labs`, `/admin/teachers`, `/admin/promotion`.
|
`/admin/labs`, `/admin/teachers`, `/admin/promotion`.
|
||||||
- [ ] **Преподаватель:** вход, `/teacher`, карточка своего курса, состав группы,
|
- [ ] **Преподаватель:** вход, `/teacher`, карточка своего курса (имена групп,
|
||||||
профиль студента, скачивание попытки, курсовые материалы.
|
привязка/отвязка группы по `available-groups`), ведомость
|
||||||
|
`/admin/groups/{groupId}?courseId=...` без админских контролов, оценка и
|
||||||
|
публикация, уровень/вариант и раздача, защита (код/reset), профиль студента,
|
||||||
|
скачивание попытки и ZIP группы, авторство материалов
|
||||||
|
`/teacher/courses/{id}/content`.
|
||||||
- [ ] **Студент:** вход, дашборд, лабораторная, сдача/история, курсовые
|
- [ ] **Студент:** вход, дашборд, лабораторная, сдача/история, курсовые
|
||||||
материалы; видны только опубликованные оценки и комментарии.
|
материалы; видны только опубликованные оценки и комментарии.
|
||||||
- [ ] **Материалы курса:** админ и преподаватель видят скрытые статьи, студент —
|
- [ ] **Материалы курса:** админ и преподаватель видят скрытые статьи, студент —
|
||||||
@@ -221,6 +238,10 @@
|
|||||||
- [ ] **Повышение:** `/admin/promotion` строит dry-run за год, данные не
|
- [ ] **Повышение:** `/admin/promotion` строит dry-run за год, данные не
|
||||||
меняются; применение — только после проверки плана; откат доступен для
|
меняются; применение — только после проверки плана; откат доступен для
|
||||||
завершённого запуска. На production не применять план «на всякий случай».
|
завершённого запуска. На production не применять план «на всякий случай».
|
||||||
|
- [ ] **Аудит:** `/admin/audit` открывается у админа, показывает события
|
||||||
|
(например, `PROFILE_VIEW`, `SUBMISSION_DOWNLOAD`, `GRADE_SET`,
|
||||||
|
`CONTENT_*`, `TEACHER_*`) с фильтром по действию и временем МСК;
|
||||||
|
преподаватель/студент получают `403`.
|
||||||
- [ ] Проверить логи `web`, `gateway`, `identity`, `academic`, `progress` на
|
- [ ] Проверить логи `web`, `gateway`, `identity`, `academic`, `progress` на
|
||||||
`ERROR`/`WARN` после smoke.
|
`ERROR`/`WARN` после smoke.
|
||||||
|
|
||||||
@@ -233,59 +254,39 @@
|
|||||||
остаются публичными. Нужна инвентаризация и привязка либо удаление таких
|
остаются публичными. Нужна инвентаризация и привязка либо удаление таких
|
||||||
файлов; до этого fallback сохраняется, чтобы не ломать старые ссылки.
|
файлов; до этого fallback сохраняется, чтобы не ломать старые ссылки.
|
||||||
Детали — в [15](15-stage5-course-materials.md).
|
Детали — в [15](15-stage5-course-materials.md).
|
||||||
2. **Права преподавателя в основном read-only.** Кабинет преподавателя даёт
|
2. **Старый формат импорта не поддерживается.** Импорт принимает только
|
||||||
обзор курса, групп, лабораторных и профилей. Авторство лабораторных и
|
|
||||||
материалов, распределение вариантов, выставление оценок, комментарии,
|
|
||||||
коды защиты и действия защиты остаются admin-only в UI/API. ТЗ
|
|
||||||
([07](07-groups-courses.md), раздел ролей) ожидает преподавательский
|
|
||||||
self-service; это главный функциональный разрыв.
|
|
||||||
3. **Названия групп в кабинете преподавателя.** На странице курса группа
|
|
||||||
показывается UUID: academic отдаёт `groupId`, а имя группы не входит в
|
|
||||||
контракт карточки курса. Нужен batch-запрос имён в identity или расширение
|
|
||||||
DTO. Тест `TeacherGroupTemplateTest` фиксирует fallback на id.
|
|
||||||
4. **Аудит чувствительных действий.** Есть `membership_history` (переводы,
|
|
||||||
академ, отчисление, архив, повышение) и журнал `promotion_runs/items`.
|
|
||||||
Отдельного audit-log для скачиваний файлов, просмотра профилей,
|
|
||||||
назначений преподавателей и публикаций нет. Нужна таблица `audit_events`
|
|
||||||
и запись из сервисов/BFF.
|
|
||||||
5. **Старый формат импорта не поддерживается.** Импорт принимает только
|
|
||||||
шаблон v2 (поток/курс/номер/степень/год/ФИО/№/база). Ведущие нули номера
|
шаблон v2 (поток/курс/номер/степень/год/ФИО/№/база). Ведущие нули номера
|
||||||
группы нормализуются к числу (`01` → `1`), поэтому `ПИ-01БО` и `ПИ-1БО`
|
группы нормализуются к числу (`01` → `1`), поэтому `ПИ-01БО` и `ПИ-1БО`
|
||||||
неразличимы; для разных когорт нужен строковый номер. Ограничение
|
неразличимы; для разных когорт нужен строковый номер. Ограничение
|
||||||
зафиксировано в [11](11-stage1-report.md).
|
зафиксировано в [11](11-stage1-report.md).
|
||||||
6. **JWT живёт до 30 дней.** Web-сессия перепроверяет статус каждые 45 секунд
|
3. **JWT живёт до 30 дней.** Web-сессия перепроверяет статус каждые 45 секунд
|
||||||
и закрывает UI при отчислении/архиве, но уже выданный JWT для прямого
|
и закрывает UI при отчислении/архиве, но уже выданный JWT для прямого
|
||||||
вызова API действует до истечения. Нужны короткий TTL + refresh,
|
вызова API действует до истечения. Нужны короткий TTL + refresh,
|
||||||
denylist/revocation или проверка статуса на каждом API-вызове.
|
denylist/revocation или проверка статуса на каждом API-вызове.
|
||||||
7. **Нет CI.** В репозитории нет pipeline (`.github` и аналогов); гейтом
|
4. **Пагинация и N+1.** Журнал аудита ограничен `limit` (максимум 500), но
|
||||||
служит локальный `./gradlew test ktlintCheck detekt`. Это не защищает
|
без offset/keyset; часть списков групп, материалов и ведомости отдаётся
|
||||||
ветку от merge без прогона и не проверяет upgrade-тест автоматически.
|
целиком. На больших курсах нужны пагинация и пакетные выборки вместо
|
||||||
|
запросов в цикле.
|
||||||
|
|
||||||
## 5. Рекомендуемые следующие шаги
|
## 5. Рекомендуемые следующие шаги
|
||||||
|
|
||||||
Приоритет — сверху вниз:
|
Приоритет — сверху вниз:
|
||||||
|
|
||||||
1. **Права преподавателя на запись.** Дать преподавателю своего курса
|
1. **Инвентаризация legacy-активов.** Найти файлы без строк в `article_assets`,
|
||||||
authoring лабораторных/материалов, распределение вариантов, выставление
|
|
||||||
оценок и комментариев, коды и действия защиты; закрыть negative-тестами
|
|
||||||
«свой/чужой курс». Это закрывает основной разрыв с ТЗ.
|
|
||||||
2. **Инвентаризация legacy-активов.** Найти файлы без строк в `article_assets`,
|
|
||||||
привязать их к статьям или удалить; после этого можно убрать публичный
|
привязать их к статьям или удалить; после этого можно убрать публичный
|
||||||
fallback для несвязанных файлов.
|
fallback для несвязанных файлов.
|
||||||
3. **UI авторства контента.** Выбор курса и аудитории при создании глав/
|
2. **JWT/refresh.** Короткий TTL access-токена + refresh, denylist/revocation
|
||||||
статей, чтобы преподаватель и админ не работали только через импорт;
|
или проверка статуса на каждом API-вызове; закрыть остаточный риск
|
||||||
сейчас глобальные главы создаются с `ALL`, курсовые — без аудитории.
|
отчисленного/архивного пользователя с уже выданным токеном.
|
||||||
4. **Аудит.** Ввести `audit_events` для скачиваний, профилей, назначений,
|
3. **Пагинация и устранение N+1.** Keyset/offset для аудита, групп, материалов
|
||||||
публикаций, повышения и жизненного цикла; экран просмотра для админа.
|
и ведомости; пакетные выборки профилей, комментариев и сдач.
|
||||||
5. **CI.** Пайплайн на каждый push/MR: `test ktlintCheck detekt`, отдельный
|
4. **Внешняя интеграция «Контингент»/next.uniyar.ac.ru.** Отдельный
|
||||||
шаг upgrade-теста миграций на PostgreSQL 18, сборка `bootJar`; запрет
|
anti-corruption слой, идемпотентная синхронизация, журнал и отчёт дрифта;
|
||||||
merge при красном статусе.
|
внешние идентификаторы добавляются миграцией.
|
||||||
|
|
||||||
## 6. Итог
|
## 6. Итог
|
||||||
|
|
||||||
Этапы 0–5 реализованы и проверены. Security-матрица, upgrade-тест миграций,
|
Этап 6 закрыт: права преподавателя на запись, аудит, CI, security-матрица,
|
||||||
видимость активов и deploy-runbook этапа 6 зелёные; отчёт и чек-лист передачи
|
upgrade-тест миграций, видимость активов и deploy-runbook проверены.
|
||||||
готовы. Этап 6 **нельзя считать полностью завершённым** до закрытия прав
|
Остаточные риски (legacy-активы, 30-дневный JWT, ведущие нули номера группы,
|
||||||
преподавателя и CI. Видимость связанных активов реализована; публичными
|
пагинация/N+1) задокументированы в разделе 4 и не блокируют передачу.
|
||||||
остаются только старые файлы без привязки к статье (см. раздел 4), а
|
|
||||||
преподаватели пока работают в режиме просмотра.
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
|
|||||||
| [13-stage3-report.md](13-stage3-report.md) | Отчёт этапа 3: преподаватели, доступ к курсам, внутренние API, кабинет и профиль студента курса |
|
| [13-stage3-report.md](13-stage3-report.md) | Отчёт этапа 3: преподаватели, доступ к курсам, внутренние API, кабинет и профиль студента курса |
|
||||||
| [14-stage4-report.md](14-stage4-report.md) | Отчёт этапа 4: повышение, академотпуск, отчисление, архив, восстановление и удаление |
|
| [14-stage4-report.md](14-stage4-report.md) | Отчёт этапа 4: повышение, академотпуск, отчисление, архив, восстановление и удаление |
|
||||||
| [15-stage5-course-materials.md](15-stage5-course-materials.md) | Отчёт этапа 5: курсовые материалы, публичные `ALL`-главы, видимость активов и legacy-файлы без привязки |
|
| [15-stage5-course-materials.md](15-stage5-course-materials.md) | Отчёт этапа 5: курсовые материалы, публичные `ALL`-главы, видимость активов и legacy-файлы без привязки |
|
||||||
| [16-stage6-report.md](16-stage6-report.md) | Отчёт этапа 6: проверка, upgrade-тест миграций, security-матрица, чек-лист деплоя и оставшиеся пробелы |
|
| [16-stage6-report.md](16-stage6-report.md) | Отчёт этапа 6: права преподавателя на запись, аудит, CI, upgrade-тест миграций, security-матрица, чек-лист деплоя и остаточные риски |
|
||||||
| [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |
|
| [ADR 0001](adr/0001-groups-courses-foundation.md) | Ключевые архитектурные решения: курс C, миграции, доступ, статусы, повышение |
|
||||||
|
|
||||||
## Кратко о системе
|
## Кратко о системе
|
||||||
|
|||||||
Reference in New Issue
Block a user