docs: этап 2 — курсы, bootstrap и лабораторные
This commit is contained in:
12
README.md
12
README.md
@@ -84,9 +84,10 @@ cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
|
|||||||
|
|
||||||
## Наполнение данными
|
## Наполнение данными
|
||||||
|
|
||||||
Порядок ввода данных (всё через админку): `/setup` → администратор; импорт групп
|
Порядок ввода данных (всё через админку): `/setup` → администратор; курс
|
||||||
(`.xls`, логин = студенческий номер, временный пароль со сменой при первом входе);
|
(bootstrap или «+ Новый курс»); импорт групп (`.xls`, логин = студенческий номер,
|
||||||
импорт лабораторной ZIP'ом (`lab.yaml` + `README.md` + `variants/*.md`); «Распределить»
|
временный пароль со сменой при первом входе); импорт лабораторной ZIP'ом с
|
||||||
|
выбором курса (`lab.yaml` + `README.md` + `variants/*.md`); «Распределить»
|
||||||
— случайная раздача по кругам в рамках уровня; оценки/защита — на странице группы;
|
— случайная раздача по кругам в рамках уровня; оценки/защита — на странице группы;
|
||||||
учебные материалы — статьи или ZIP (`chapters/…`, доступны гостям).
|
учебные материалы — статьи или ZIP (`chapters/…`, доступны гостям).
|
||||||
|
|
||||||
@@ -105,6 +106,11 @@ cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD
|
|||||||
ручное добавление студента (генерация пароля) и «Добавить существующего»
|
ручное добавление студента (генерация пароля) и «Добавить существующего»
|
||||||
(перевод из другой группы), «Отчислить из группы», выгрузка логинов XLSX/CSV,
|
(перевод из другой группы), «Отчислить из группы», выгрузка логинов XLSX/CSV,
|
||||||
сброс пароля, история членства.
|
сброс пароля, история членства.
|
||||||
|
- **Курсы**: админ-страницы курсов (`/admin/courses`), номер `course_no` для URL,
|
||||||
|
назначение преподавателей и привязка групп с историей; bootstrap переносит
|
||||||
|
существующие лабораторные, главы и активные группы в курс «Промышленное
|
||||||
|
программирование на языке C». Лабораторные привязаны к курсу: создание
|
||||||
|
и импорт требуют выбора курса, лабу можно скопировать в другой курс.
|
||||||
- **Лабораторные**: импорт ZIP (`lab.yaml` + `README.md` + `variants/*.md` +
|
- **Лабораторные**: импорт ZIP (`lab.yaml` + `README.md` + `variants/*.md` +
|
||||||
`assets/`); создание через UI с произвольным числом уровней; единая форма
|
`assets/`); создание через UI с произвольным числом уровней; единая форма
|
||||||
настроек с одной кнопкой «Сохранить всё»; статусы draft/published
|
настроек с одной кнопкой «Сохранить всё»; статусы draft/published
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Порядок наполнения LMS данными (через интерфейс)
|
# Порядок наполнения LMS данными (через интерфейс)
|
||||||
|
|
||||||
Всё делается в админке, порядок: администратор → группы и студенты → лабораторная →
|
Всё делается в админке, порядок: администратор → курс (bootstrap) → группы
|
||||||
раздача вариантов → защита → материалы.
|
и студенты → лабораторная → раздача вариантов → защита → материалы.
|
||||||
|
|
||||||
## Шаг 1. Деплой
|
## Шаг 1. Деплой
|
||||||
|
|
||||||
@@ -9,7 +9,25 @@
|
|||||||
откройте домен — попадёте на `/setup`, создайте администратора (логин и пароль
|
откройте домен — попадёте на `/setup`, создайте администратора (логин и пароль
|
||||||
храните в секрете, в репозиторий их не писать).
|
храните в секрете, в репозиторий их не писать).
|
||||||
|
|
||||||
## Шаг 2. Группы и студенты
|
## Шаг 2. Курс
|
||||||
|
|
||||||
|
Откройте **Курсы** (`/admin/courses`). Все лабораторные принадлежат курсу,
|
||||||
|
поэтому курс должен существовать до импорта лабораторных.
|
||||||
|
|
||||||
|
* **Существующая база:** если курсов ещё нет, нажмите «Создать курс и перенести
|
||||||
|
данные». Bootstrap создаст курс «Промышленное программирование на языке C»,
|
||||||
|
привяжет к нему все активные группы и перенесёт существующие лабораторные
|
||||||
|
и главы. Повторный запуск безопасен и не создаёт дублей.
|
||||||
|
* **Новая база:** создайте курс кнопкой «+ Новый курс» (номер присвоится
|
||||||
|
автоматически) либо тем же bootstrap.
|
||||||
|
* После импорта групп (шаг 3) привяжите их к курсу на странице курса, если их
|
||||||
|
не было в момент bootstrap: блок «Привязать группу». Там же назначаются
|
||||||
|
преподаватели — по UUID аккаунта со страницы «Пользователи».
|
||||||
|
|
||||||
|
Архивированный курс нельзя выбрать для новой лабораторной или копирования.
|
||||||
|
Без курса создание и импорт лабораторной недоступны: в форме нужно выбрать курс.
|
||||||
|
|
||||||
|
## Шаг 3. Группы и студенты
|
||||||
|
|
||||||
**Группы → Импорт** — загрузите `.xls`/`.xlsx`/`.csv` со списком группы.
|
**Группы → Импорт** — загрузите `.xls`/`.xlsx`/`.csv` со списком группы.
|
||||||
Обязательные столбцы:
|
Обязательные столбцы:
|
||||||
@@ -35,9 +53,10 @@
|
|||||||
|
|
||||||
Выгрузить логины: **«Логины XLSX» / «CSV»**.
|
Выгрузить логины: **«Логины XLSX» / «CSV»**.
|
||||||
|
|
||||||
## Шаг 3. Лабораторная
|
## Шаг 4. Лабораторная
|
||||||
|
|
||||||
**Лабораторные → Импорт** — ZIP со структурой:
|
**Лабораторные → Импорт** — сначала выберите **курс** (обязательно), затем
|
||||||
|
загрузите ZIP со структурой:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
lab.yaml # номер, название, points_max, уровни (code/title), сроки, статус
|
lab.yaml # номер, название, points_max, уровни (code/title), сроки, статус
|
||||||
@@ -49,9 +68,12 @@ variants/01.md # вариант: front-matter (level, number, title) + тек
|
|||||||
странице лабораторной:
|
странице лабораторной:
|
||||||
|
|
||||||
* «Настроить» — срок сдачи, дата защиты, максимум баллов, статус;
|
* «Настроить» — срок сдачи, дата защиты, максимум баллов, статус;
|
||||||
* уровни/варианты можно править и добавлять прямо там же.
|
* уровни/варианты можно править и добавлять прямо там же;
|
||||||
|
* «Копировать в курс» — создать копию лабораторной (с уровнями и вариантами)
|
||||||
|
в другом курсе; номер и название можно изменить. Прогресс студентов не
|
||||||
|
копируется, копия создаётся черновиком.
|
||||||
|
|
||||||
## Шаг 4. Раздача вариантов
|
## Шаг 5. Раздача вариантов
|
||||||
|
|
||||||
На странице лабораторной — **«Распределить»**: выбрать группы, при необходимости
|
На странице лабораторной — **«Распределить»**: выбрать группы, при необходимости
|
||||||
«пересоздать заново». Логика: случайная очередь по всем вариантам выбранного
|
«пересоздать заново». Логика: случайная очередь по всем вариантам выбранного
|
||||||
@@ -59,7 +81,7 @@ variants/01.md # вариант: front-matter (level, number, title) + тек
|
|||||||
вручную не трогаются. Частично/точно — в списке назначений (уровень и вариант по
|
вручную не трогаются. Частично/точно — в списке назначений (уровень и вариант по
|
||||||
каждому студенту) или массово чекбоксами.
|
каждому студенту) или массово чекбоксами.
|
||||||
|
|
||||||
## Шаг 5. Оценки и защита
|
## Шаг 6. Оценки и защита
|
||||||
|
|
||||||
На странице группы (матрица):
|
На странице группы (матрица):
|
||||||
|
|
||||||
@@ -69,7 +91,7 @@ variants/01.md # вариант: front-matter (level, number, title) + тек
|
|||||||
(`студент;ЛР;задание` или `студент;задание` при выбранной ЛР), «выдать код»,
|
(`студент;ЛР;задание` или `студент;задание` при выбранной ЛР), «выдать код»,
|
||||||
«изменить», «текст», после готовности — «доп. задание: начать заново».
|
«изменить», «текст», после готовности — «доп. задание: начать заново».
|
||||||
|
|
||||||
## Шаг 6. Учебные материалы
|
## Шаг 7. Учебные материалы
|
||||||
|
|
||||||
**Лабораторные → материалы (`/learn`)** доступны гостям. Для наполнения:
|
**Лабораторные → материалы (`/learn`)** доступны гостям. Для наполнения:
|
||||||
создание глав/разделов/статей в админке или **импорт ZIP** со структурой:
|
создание глав/разделов/статей в админке или **импорт ZIP** со структурой:
|
||||||
|
|||||||
@@ -272,10 +272,18 @@ CREATE UNIQUE INDEX chapters_slug_course_uidx ON chapters (course_id, slug) WHER
|
|||||||
Признак видимости актив наследует от статьи→раздела→главы (course/audience). Требуется проверка доступа в
|
Признак видимости актив наследует от статьи→раздела→главы (course/audience). Требуется проверка доступа в
|
||||||
маршруте выдачи активов (см. §6). Отдельных колонок достаточно опционально; минимум — корректная авторизация.
|
маршруте выдачи активов (см. §6). Отдельных колонок достаточно опционально; минимум — корректная авторизация.
|
||||||
|
|
||||||
**Post-bootstrap шаг academic (условный «V10», не файл `db/migration` и вне обычной цепочки Flyway) — курс C: завершение привязки лаб**
|
**V10 — уникальное название курса (реальный файл `V10__course_title_unique.sql`)**
|
||||||
```sql
|
```sql
|
||||||
-- Выполняется после bootstrap-сервиса, когда все лабы получили course_id.
|
-- Защита от дублей курса при конкурентном bootstrap и повторном создании.
|
||||||
-- Это отдельный шаг/миграция: обычный Flyway-старт применяет миграции раньше
|
CREATE UNIQUE INDEX courses_title_lower_uidx ON courses (lower(title));
|
||||||
|
```
|
||||||
|
Сервис дополнительно проверяет название без учёта регистра и возвращает `409`
|
||||||
|
на дубликат; индекс закрывает гонку между проверкой и вставкой.
|
||||||
|
|
||||||
|
**Post-bootstrap шаг academic (вне Flyway) — курс C: завершение привязки лаб**
|
||||||
|
```sql
|
||||||
|
-- Выполняется сервисом CourseBootstrapService после переноса данных, когда все
|
||||||
|
-- лабы получили course_id. Обычный Flyway-старт применяет миграции раньше
|
||||||
-- бизнес-логики и не может дождаться создания курса и назначения владельца.
|
-- бизнес-логики и не может дождаться создания курса и назначения владельца.
|
||||||
ALTER TABLE labs ALTER COLUMN course_id SET NOT NULL;
|
ALTER TABLE labs ALTER COLUMN course_id SET NOT NULL;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -201,13 +201,13 @@
|
|||||||
## 4. Ориентировочные миграции
|
## 4. Ориентировочные миграции
|
||||||
|
|
||||||
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master
|
Номера уточняются при реализации, чтобы не конфликтовать с максимумами master
|
||||||
**до этой ветки** (identity V3, academic V5, progress V12); максимумы этой ветки
|
(identity V3, **academic V5**, progress V12). Максимумы этой ветки:
|
||||||
больше.
|
identity V9, **academic V10**, progress V13 (+V14 при необходимости аудита).
|
||||||
|
|
||||||
| Сервис | Миграции | Содержание |
|
| Сервис | Миграции | Содержание |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| identity | V4–V9 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status` |
|
| identity | V4–V9 | роли, поля групп, статусы студентов, `membership_history`, `promotion_runs/items`, `accounts.status` |
|
||||||
| academic | V6–V9 + post-bootstrap V10 | курсы, преподаватели, группы курса, `labs.course_id` (nullable), материалы курса/аудитории, частичные индексы, активы; `SET NOT NULL` — отдельный шаг после bootstrap |
|
| 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 |
|
| progress | V13 (+ V14 при необходимости аудита) | `grade_comments.author_account_id`, `lab_grades.updated_by_account_id`, индекс `lab_grades(lab_id)`; аудит скачиваний — этап 6 |
|
||||||
|
|
||||||
### Переход со старой feature-миграции V10 на V13
|
### Переход со старой feature-миграции V10 на V13
|
||||||
@@ -300,9 +300,13 @@
|
|||||||
проверка на копии БД. Отчёт: [10-stage0-report.md](10-stage0-report.md).
|
проверка на копии БД. Отчёт: [10-stage0-report.md](10-stage0-report.md).
|
||||||
- **Этап 1 — выполнен.** Роли, группы-когорты, статусы, история, импорт v2,
|
- **Этап 1 — выполнен.** Роли, группы-когорты, статусы, история, импорт v2,
|
||||||
админ-UI. Отчёт: [11-stage1-report.md](11-stage1-report.md).
|
админ-UI. Отчёт: [11-stage1-report.md](11-stage1-report.md).
|
||||||
- **Этап 2 — следующий.** Курсы, преподаватели, привязка групп, лабораторные
|
- **Этап 2 — выполнен.** Курсы и `course_no`, история преподавателей и групп,
|
||||||
и материалы курса C, активы, копирование лаб.
|
bootstrap курса C, привязка лабораторных к курсу, копирование лаб,
|
||||||
- Этапы 3–6 — по плану.
|
админ-UI курсов и лабораторных. Отчёт: [12-stage2-report.md](12-stage2-report.md).
|
||||||
|
- **Этап 3 — следующий.** Доступ и профиль: `TEACHER` в security-конфигах,
|
||||||
|
внутренние access-эндпоинты, проверки курса/группы/студента, профиль студента
|
||||||
|
курса, материалы курса и аудитории, активы, кабинет преподавателя.
|
||||||
|
- Этапы 4–6 — по плану.
|
||||||
|
|
||||||
## 10. Definition of Done
|
## 10. Definition of Done
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
- Миграции:
|
- Миграции:
|
||||||
- identity `V4–V9`: роли, группы-когорты, статусы студентов, повышение,
|
- identity `V4–V9`: роли, группы-когорты, статусы студентов, повышение,
|
||||||
история членства, статусы аккаунтов;
|
история членства, статусы аккаунтов;
|
||||||
- academic `V6–V9`: курсы, преподаватели, группы курса, `labs.course_id`,
|
- academic `V6–V10`: курсы, преподаватели, группы курса, `labs.course_id`,
|
||||||
материалы курса/аудитории, индексы активов;
|
материалы курса/аудитории, индексы активов; `V10` (уникальный индекс
|
||||||
|
`lower(title)` по курсам) добавлена на этапе 2, после этого отчёта;
|
||||||
- progress `V13`: автор комментария и последнего изменения оценки.
|
- progress `V13`: автор комментария и последнего изменения оценки.
|
||||||
- Preflight-скрипты:
|
- Preflight-скрипты:
|
||||||
- `deploy/preflight/groups-courses.sql` — до миграций;
|
- `deploy/preflight/groups-courses.sql` — до миграций;
|
||||||
|
|||||||
150
docs/12-stage2-report.md
Normal file
150
docs/12-stage2-report.md
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# 12. Отчёт этапа 2: academic (курсы и лабораторные)
|
||||||
|
|
||||||
|
**Дата:** 2026-10-27.
|
||||||
|
**Статус:** этап 2 выполнен; этап 3 (доступ и профиль студента курса) — следующий.
|
||||||
|
|
||||||
|
## 1. Что сделано
|
||||||
|
|
||||||
|
### Курсы
|
||||||
|
|
||||||
|
- Добавлены таблицы `courses`, `course_teachers`, `course_groups` (миграция `V6`).
|
||||||
|
- `courses.course_no` — `BIGINT GENERATED ALWAYS AS IDENTITY`, уникальный номер
|
||||||
|
для человекочитаемых URL; UUID остаётся первичным ключом.
|
||||||
|
- CRUD курсов: создание, просмотр, переименование, перевод в архив и возврат
|
||||||
|
в работу. Название нормализуется (trim), пустое заменяется на «Новый курс».
|
||||||
|
- Название курса уникально без учёта регистра (миграция `V10`, индекс по
|
||||||
|
`lower(title)`); повторное создание возвращает `409`.
|
||||||
|
- Назначение и снятие преподавателей, привязка и отвязка групп хранятся
|
||||||
|
с историей: снятие/отвязка проставляют `removed_at`, запись не удаляется.
|
||||||
|
Повторное назначение после снятия создаёт новую активную запись.
|
||||||
|
- Для архивированного курса запрещены новые назначения преподавателей
|
||||||
|
и привязка групп.
|
||||||
|
|
||||||
|
### Bootstrap курса C
|
||||||
|
|
||||||
|
- `CourseBootstrapService` идемпотентно создаёт (или находит) курс
|
||||||
|
«Промышленное программирование на языке C», назначает владельцем текущего
|
||||||
|
админа и переносит в него существующие данные.
|
||||||
|
- Конкурентные запуски сериализуются advisory-локом
|
||||||
|
`pg_advisory_xact_lock(hashtext('academic.course.bootstrap'))`.
|
||||||
|
- Перед переносом проверяются дубли номеров лабораторных без курса. При дублях
|
||||||
|
bootstrap завершается `409`, транзакция откатывается, курс не создаётся.
|
||||||
|
- Все активные группы, переданные из web, привязываются к курсу (повторный
|
||||||
|
запуск не создаёт дублей).
|
||||||
|
- Всем лабораторным и главам без курса проставляется `course_id = C`;
|
||||||
|
у курсовых глав `audience` сбрасывается в `NULL`.
|
||||||
|
- После переноса, если лабораторных без курса не осталось, сервис в рантайме
|
||||||
|
выполняет `ALTER TABLE academic.labs ALTER COLUMN course_id SET NOT NULL`.
|
||||||
|
Это **post-bootstrap шаг, а не Flyway-файл**: обычный старт Flyway не может
|
||||||
|
дождаться создания курса и назначения владельца.
|
||||||
|
|
||||||
|
### Лабораторные
|
||||||
|
|
||||||
|
- `labs.course_id` добавлен nullable-миграцией `V7`; уникальность номера —
|
||||||
|
в рамках курса: `(number, course_id)`.
|
||||||
|
- Создание и импорт лабораторной требуют курс; архивированный курс отклоняется
|
||||||
|
с `400`.
|
||||||
|
- Список лабораторных фильтруется по `courseId`; API и UI показывают номер
|
||||||
|
и название курса у каждой лабораторной.
|
||||||
|
- Копирование лабораторной в другой курс (`LabCopyService`): копируются
|
||||||
|
описание, уровни и варианты с новыми id, копия создаётся черновиком.
|
||||||
|
Оценки, сдачи, защиты и назначения вариантов не копируются.
|
||||||
|
- Копирование запрещено в тот же курс и в архивированный курс; занятый номер
|
||||||
|
в целевом курсе даёт `409`.
|
||||||
|
|
||||||
|
### Админ-UI
|
||||||
|
|
||||||
|
- `/admin/courses` — список курсов с номером, названием, статусом и счётчиками
|
||||||
|
преподавателей, групп и лабораторных; создание курса; блок первичного
|
||||||
|
bootstrap, пока курсов нет.
|
||||||
|
- `/admin/courses/{id}` — карточка курса: архивирование/возврат, привязка
|
||||||
|
и отвязка групп, назначение и снятие преподавателей по UUID аккаунта,
|
||||||
|
список лабораторных курса.
|
||||||
|
- `/admin/labs` — фильтр по курсу, колонка курса, выбор курса (обязательный)
|
||||||
|
в формах импорта и создания.
|
||||||
|
- `/admin/labs/{id}` — карточка лабораторной показывает курс; кнопка
|
||||||
|
«Копировать в курс» открывает форму выбора целевого курса, номера и названия.
|
||||||
|
- В навигацию админа добавлен пункт «Курсы».
|
||||||
|
|
||||||
|
### Миграции
|
||||||
|
|
||||||
|
- `V6__courses.sql` — курсы, преподаватели, группы курса.
|
||||||
|
- `V7__labs_course.sql` — `labs.course_id`, уникальность `(number, course_id)`.
|
||||||
|
- `V8__chapters_course.sql` — `chapters.course_id/audience`, частичные индексы slug.
|
||||||
|
- `V9__article_assets_indexes.sql` — индекс `article_assets(article_id)`.
|
||||||
|
- `V10__course_title_unique.sql` — уникальный индекс `lower(title)` по курсам.
|
||||||
|
|
||||||
|
## 2. API
|
||||||
|
|
||||||
|
Все эндпоинты курсов доступны только роли `ADMIN`.
|
||||||
|
|
||||||
|
| Метод | Путь | Назначение |
|
||||||
|
|---|---|---|
|
||||||
|
| `GET` | `/api/v1/courses` | список курсов со счётчиками |
|
||||||
|
| `POST` | `/api/v1/courses` | создать курс, владелец — текущий админ |
|
||||||
|
| `GET` | `/api/v1/courses/{id}` | карточка курса с историей преподавателей и групп |
|
||||||
|
| `PUT` | `/api/v1/courses/{id}` | изменить название и/или статус |
|
||||||
|
| `POST` | `/api/v1/courses/{id}/teachers` | назначить преподавателя |
|
||||||
|
| `DELETE` | `/api/v1/courses/{id}/teachers/{accountId}` | снять преподавателя |
|
||||||
|
| `POST` | `/api/v1/courses/{id}/groups` | привязать группу |
|
||||||
|
| `DELETE` | `/api/v1/courses/{id}/groups/{groupId}` | отвязать группу |
|
||||||
|
| `POST` | `/api/v1/courses/bootstrap` | создать курс C и перенести данные |
|
||||||
|
| `GET` | `/api/v1/labs?courseId=…` | список лабораторных, опционально по курсу |
|
||||||
|
| `POST` | `/api/v1/labs` | создать лабораторную с обязательным `courseId` |
|
||||||
|
| `POST` | `/api/v1/labs/import?courseId=…` | импорт ZIP в указанный курс |
|
||||||
|
| `POST` | `/api/v1/labs/{id}/copy` | копировать лабораторную в другой курс |
|
||||||
|
|
||||||
|
`LabSummary` и `LabDetail` дополнены полями `courseId`, `courseNo`,
|
||||||
|
`courseTitle`. В браузерных URL курса используется `course_no`; API работает
|
||||||
|
с UUID.
|
||||||
|
|
||||||
|
## 3. Порядок bootstrap
|
||||||
|
|
||||||
|
1. Админ открывает `/admin/courses`. Пока курсов нет, показывается блок
|
||||||
|
первичной настройки.
|
||||||
|
2. Web передаёт в `POST /api/v1/courses/bootstrap` название (необязательно)
|
||||||
|
и id всех активных групп.
|
||||||
|
3. Сервис берёт advisory-лок, проверяет UUID групп, находит или создаёт курс
|
||||||
|
по названию без учёта регистра (владелец — текущий админ).
|
||||||
|
4. Привязывает активные группы, проверяет дубли номеров лабораторных без курса.
|
||||||
|
5. Проставляет `course_id` лабораторным и главам.
|
||||||
|
6. Если лабораторных без курса не осталось, выполняет `SET NOT NULL` для
|
||||||
|
`labs.course_id`.
|
||||||
|
7. Возвращает `courseId`, `courseNo`, название и счётчики: сколько групп
|
||||||
|
привязано, лабораторных и глав перенесено, сколько осталось без курса.
|
||||||
|
8. Повторный запуск безопасен: существующий курс переиспользуется, уже
|
||||||
|
привязанные группы и перенесённые данные не дублируются.
|
||||||
|
|
||||||
|
## 4. Тесты
|
||||||
|
|
||||||
|
Добавлены интеграционные тесты на PostgreSQL (Testcontainers):
|
||||||
|
|
||||||
|
- `CourseServiceIntegrationTest` (6 тестов): отказ bootstrap при дублях
|
||||||
|
номеров без создания курса, создание курса и перенос групп/лабораторных/глав,
|
||||||
|
идемпотентность bootstrap, генерация уникальных `course_no`, запрет
|
||||||
|
дубликата названия без учёта регистра, история назначений преподавателей
|
||||||
|
и групп, отказ при некорректном UUID группы.
|
||||||
|
- `LabCopyIntegrationTest` (5 тестов): копирование с уровнями и вариантами,
|
||||||
|
обязательный курс при создании, запрет копирования в тот же курс, в
|
||||||
|
архивированный курс и создание лабораторной в архивированном курсе.
|
||||||
|
|
||||||
|
Прогон 2026-10-27: `./gradlew :services:academic-service:test :web:test` —
|
||||||
|
успешно, в новых наборах 0 failures / 0 errors. `git diff --check` — успешно.
|
||||||
|
|
||||||
|
## 5. Известные пробелы (этап 3)
|
||||||
|
|
||||||
|
- **Материалы курса и аудитории.** Схема `chapters.course_id/audience` готова
|
||||||
|
(`V8`), но URL `/learn/course/{course_no}/...` и проверка аудитории ещё не
|
||||||
|
реализованы: читалка материалов пока работает по прежним глобальным правилам.
|
||||||
|
- **Активы.** `V9` добавляет только индекс; наследуемая видимость активов
|
||||||
|
от статьи → раздела → главы и проверка доступа при выдаче файлов — этап 3.
|
||||||
|
- **Проверка аккаунтов преподавателей.** Academic принимает UUID без обращения
|
||||||
|
к identity: не проверяются существование аккаунта, роль `TEACHER` и статус
|
||||||
|
`ACTIVE`. Нужна внутренняя проверка identity при назначении.
|
||||||
|
- **Кабинет преподавателя и скоупинг.** API курсов пока admin-only; кабинет
|
||||||
|
`/teacher/...`, доступ преподавателя только к своим курсам и студентам,
|
||||||
|
профиль студента курса и negative-тесты «чужой курс/студент/сдача» —
|
||||||
|
этап 3.
|
||||||
|
- **Повторный bootstrap из UI.** Кнопка bootstrap показывается только когда
|
||||||
|
курсов нет; повторный запуск возможен через API. Группы, появившиеся позже,
|
||||||
|
привязываются вручную на странице курса.
|
||||||
@@ -19,6 +19,7 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
|
|||||||
| [09-teacher-student-profile.md](09-teacher-student-profile.md) | Контракт API профиля студента курса для преподавателя |
|
| [09-teacher-student-profile.md](09-teacher-student-profile.md) | Контракт API профиля студента курса для преподавателя |
|
||||||
| [10-stage0-report.md](10-stage0-report.md) | Отчёт этапа 0: миграции, preflight и проверка на копии БД |
|
| [10-stage0-report.md](10-stage0-report.md) | Отчёт этапа 0: миграции, preflight и проверка на копии БД |
|
||||||
| [11-stage1-report.md](11-stage1-report.md) | Отчёт этапа 1: роли, группы-когорты, статусы, история, импорт v2 |
|
| [11-stage1-report.md](11-stage1-report.md) | Отчёт этапа 1: роли, группы-когорты, статусы, история, импорт v2 |
|
||||||
|
| [12-stage2-report.md](12-stage2-report.md) | Отчёт этапа 2: курсы, bootstrap курса C, привязка и копирование лабораторных, миграция V10 |
|
||||||
| [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