# LMS Система для проведения практических занятий на основе микросервисной архитектуры (Kotlin + Spring Boot + PostgreSQL 18). ## Документация UML- и архитектурные диаграммы (Mermaid) — в каталоге [`docs/`](docs/README.md): - [Архитектура](docs/01-architecture.md) — контекст, компоненты, модули, аутентификация; - [Развёртывание](docs/02-deployment.md) — Docker, nginx, тома, обновление; - [Модель данных и классов](docs/03-domain-model.md) — диаграммы классов по сервисам и ER-модель; - [Диаграммы последовательностей](docs/04-sequences.md) — вход, импорт ЛР, раздача вариантов, сдача, защита, оценки, материалы; - [Диаграммы состояний](docs/05-states.md) — защита, публикация оценок и материалов, сдача, пароль. ## Модули - `libs/common-http` — общий формат ошибок и обработчик исключений. - `libs/security-jwt` — выпуск/проверка JWT (RSA) и `PasswordEncoder`. - `libs/markdown` — серверный рендер markdown со спойлерами и санитизацией. - `libs/test-support` — общая тестовая инфраструктура (Testcontainers, `postgres:18`). - `services/identity-service` — аккаунты, студенты, группы, зачисления (схема `identity`). - `services/academic-service` — курсы, статьи, лабораторные, уровни, варианты (схема `academic`). - `services/progress-service` — уровни студентов, распределение вариантов, работы, оценки (схема `progress`). - `services/gateway` — Spring Cloud Gateway, маршрутизация и проверка JWT. - `web` — серверный веб-интерфейс (Thymeleaf + htmx), BFF. ## Требования - JDK 21 (runtime) + Docker (для PostgreSQL 18). Gradle сам скачает toolchain 21 для сборки. - **Не запускать сервисы на JAVA_HOME=Java 25**: на 25 ломается logback внутри fat-jar (NoClassDefFoundError `ThrowableProxy`), что роняет потоки Tomcat. Скрипт `run.sh` использует JDK 21 автоматически. ## Запуск ```bash ./gradlew bootJar -x test # собрать все сервисы ./run.sh start # поднять БД + все 5 сервисов на JDK 21 ./run.sh status # проверить здоровье ./run.sh logs web # посмотреть лог сервиса ./run.sh stop | restart ``` Можно и без скрипта: `docker compose up -d postgres`, затем `/usr/lib/jvm/java-21-openjdk/bin/java -jar ` для каждого сервиса, или `./gradlew :services:identity-service:bootRun` и т.д. по одному. Порты по умолчанию: gateway `:8080`, identity `:8081`, academic `:8082`, progress `:8083`, web `:8090`. Интерфейс: `http://localhost:8090`. ## Качество кода и тесты ```bash ./gradlew ktlintCheck detekt test # линтеры (строгий режим) + тесты ./gradlew evidenceCheck # LDD: P0-claims должны иметь зелёные свидетельства ./gradlew ktlintFormat # автоформат Kotlin ./scripts/ldd-runtime.sh # runtime-smoke полного стека на одноразовой БД ./scripts/ldd-migration-upgrade.sh # upgrade с master-схемы ./scripts/ldd-mutation.sh # PIT: mutation strength критичных P0-компонентов ./scripts/ldd-browser.sh # Playwright E2E (нужен cd e2e && npm install) ``` - `ktlint` отвечает за формат, `detekt` — за качество (сложность, вложенность, ошибки). Оба подключены во всех Kotlin-модулях конвенцией `lms.lint` и **ломают сборку** при замечаниях. - Конфигурация: `.editorconfig`, `config/detekt/detekt.yml`. - Gradle daemon фиксируется на JDK 21 (`gradle/gradle-daemon-jvm.properties`): detekt 1.23 не работает на JDK 25. - Тестовая инфраструктура — `libs:test-support` (JUnit 5, MockK, Testcontainers `postgres:18`). - **LDD** — реестр проверяемых утверждений `docs/evidence/claims.json`; P0 без зелёного теста валит `./gradlew evidenceCheck`. Runtime, migration, mutation и browser E2E — отдельные скрипты из `scripts/`; baseline — `docs/evidence/baseline-2026-10-27.md`. - CI merge gate — `.gitlab-ci.yml`: `./gradlew --no-daemon test ktlintCheck detekt evidenceCheck` на push/MR. Runner должен разрешать privileged `docker:dind` для Testcontainers. ## Запуск в Docker (для сервера) На сервере Java не нужна: всё собирается и работает в контейнерах (multi-stage `Dockerfile`, сборка на JDK 21, запуск на JRE 21). ```bash cp .env.prod.example .env.prod # заполнить LMS_DB_PASSWORD ./deploy/deploy.sh up # backup + сборка + запуск (postgres, 5 сервисов) ./deploy/deploy.sh logs web ./deploy/deploy.sh ps ./deploy/deploy.sh down ``` Перед обновлением `deploy.sh up` обязательно делает backup БД и файлов. Ежедневное расписание, retention и восстановление — в [`deploy/BACKUP.md`](deploy/BACKUP.md). Наружу публикуется только web на `127.0.0.1:8090`; reverse proxy — nginx (конфиг и полная инструкция по деплою на `lms.our-place.ru` в [`deploy/DEPLOY.md`](deploy/DEPLOY.md) и `deploy/nginx/lms.our-place.ru.conf`). ## Наполнение данными Порядок ввода данных (всё через админку): `/setup` → администратор; курс (bootstrap или «+ Новый курс»); импорт групп (`.xls`, логин = студенческий номер, временный пароль со сменой при первом входе); импорт лабораторной ZIP'ом с выбором курса (`lab.yaml` + `README.md` + `variants/*.md`); «Распределить» — случайная раздача по кругам в рамках уровня; оценки/защита — на странице группы; учебные материалы — статьи или ZIP (`chapters/…`, доступны гостям). Подробный пошаговый порядок — в [`deploy/IMPORT.md`](deploy/IMPORT.md). ## Текущий функционал Открыть `http://localhost:8090`. При первом запуске создаётся аккаунт администратора (`/setup`); далее — вход по логину и паролю. - **Доступ**: JWT (RSA, ключи identity генерирует при первом старте и хранит в БД), проверка токена на gateway, роли ADMIN/TEACHER/STUDENT, CSRF на всех POST-формах, session fixation-защита; вход студента с обязательной сменой пароля. - **Группы и студенты**: группы-когорты (поток, курс, номер, степень, год набора), импорт `.xls/.xlsx/.csv` по шаблону `поток/курс/номер/степень/год набора/ФИО/№/база`, ручное добавление студента (генерация пароля) и «Добавить существующего» (перевод из другой группы), «Отчислить из группы», выгрузка логинов XLSX/CSV, сброс пароля, история членства. - **Курсы**: админ-страницы курсов (`/admin/courses`), номер `course_no` для URL, назначение преподавателей и привязка групп с историей; bootstrap переносит существующие лабораторные, главы и активные группы в курс «Промышленное программирование на языке C». Лабораторные привязаны к курсу: создание и импорт требуют выбора курса, лабу можно скопировать в другой курс. - **Преподаватели и доступ**: `/admin/teachers` создаёт аккаунты `TEACHER` с одноразовым паролем; преподаватель видит только назначенные курсы (`/teacher`), их группы, студентов и профили. Курсы и лабораторные фильтруются по роли, внутренние вызовы identity защищены `LMS_INTERNAL_TOKEN`. - **Профиль студента курса**: `/teacher/courses/{id}/students/{id}` и админский маршрут показывают сводку, оценки, комментарии с авторами, защиты и все попытки сдачи со скачиванием ZIP; студенту API отдаёт только его профиль и опубликованные данные. - **Работа преподавателя**: ведомость группы переиспользует `/admin/groups/{groupId}?courseId=...` без админских контролов — оценки и комментарии, публикация, уровни/варианты и раздача, защиты (код/reset), скачивание попыток и ZIP группы; лабораторные/уровни/варианты своего курса, привязка/отвязка групп на `/teacher/courses/{id}`, авторские материалы на `/teacher/courses/{id}/content`. Чужой курс отклоняется на сервисах. - **Аудит**: чувствительные действия (профили, скачивания, оценки, защиты, курсы, группы, материалы, преподаватели, жизненный цикл) пишутся в `identity.audit_events`; админ смотрит `/admin/audit` с фильтром по действию и временем МСК. - **Повышение и жизненный цикл**: `/admin/promotion` — dry-run, применение и откат повышения (автоматический dry-run 31 августа МСК); на странице группы — академотпуск, возврат, отчисление, архив и восстановление; архив группы и подтверждение удаления логином/ФИО. Вход закрыт для выпущенных и архивных групп. - **Лабораторные**: импорт ZIP (`lab.yaml` + `README.md` + `variants/*.md` + `assets/`); создание через UI с произвольным числом уровней; единая форма настроек с одной кнопкой «Сохранить всё»; статусы draft/published (клик по бейджу меняет); даты в московском времени. - **Распределение вариантов** (только на странице лабы): «Распределить» с выбором групп; раздача случайная и равномерная — студенты выбранных групп идут общей очередью, вариант берётся со своего уровня, пока в уровне есть невыданные — без повторов, затем выдаются наименее занятые; ручные закрепления сохраняются всегда. На странице лабы — список назначений по группам (уровень/вариант каждому), массовые операции чекбоксами, индикаторы «выдано/всего». Матрица на странице группы — просмотр (вариант, сдача) + оценки/защита, управление раздачей оттуда убрано. - **Индивидуальные задания (защита)**: на странице группы — назначение/изменение задания, импорт CSV (2 или 3 колонки, с кавычками и `\n`), выдача кода, «доп. задание: начать заново» после завершения защиты. - **Материалы** (`/learn`, публичная читалка): главы → разделы → статьи в markdown (код, спойлеры, картинки; санитизация OWASP); импорт ZIP (`chapters/N-глава/N-раздел/N-статья.md` + `assets/`); создание/переименование/ удаление глав, разделов, статей; перенос статей между разделами. - **Студент**: дашборд успеваемости (сдано, средний балл, лучшая/слабая оценка), карточки опубликованных лаб, страница лабы: условие, свой вариант, сдача файлов (zip/.c, переотправка до дедлайна, история, скачивание последней), оценка и комментарии отдельным блоком, защита по одноразовому коду (30 мин). - **Публикация оценок**: в группе по каждой лабе — кнопка «опубликовать»; студент видит оценку и комментарии только после публикации (публикуется вся группа). - **Комментарии к оценкам**: общий (итоговый) комментарий по лабораторной + комментарии с тональностью (positive/negative/neutral); на странице студента — ненумерованными списками в трёх колонках, хранятся в БД, автоподсказки при вводе ранжируются по частоте использования. - **Архив сдач группы**: одна кнопка «Скачать все работы» — ZIP со всеми последними решениями группы, разложенными по лабам и студентам. - **Материалы** `/learn` доступны без входа; аккордеон глав: свёрнуты все, открыта последняя. - UI: светлая/тёмная тема (сохраняется в localStorage), модальные окна вместо отдельных страниц, аккордеоны глав/разделов в `/learn`, фавиконка, адаптивная вёрстка без inline-стилей. ## Примечания - **JDK 21 обязателен для runtime**. В связке Spring Boot 4.0.x + fat-jar logback не находит `ThrowableProxy`, и это падает при любом исключении в потоке ответа. Если после деплоя сервис вдруг «висит» на страницах (а `/actuator/health` даёт 200) — лечится перезапуском (`./run.sh restart`). - Даты хранятся в UTC (instant), а в UI конвертируются в Europe/Moscow (`DateUtils`); формы datetime-local отправляются в московском времени. - Капча/rate-limit на входе отсутствует — для учебного стендома оставлено; пароли студентов показываются один раз при выдаче. - `lms.submissions.dir` (default ./data/submissions) и `lms.content.assets-dir` держите на постоянном томе при деплое. ## Сборка и тесты ```bash ./gradlew build # сборка всех модулей ./gradlew check # тесты и проверки ``` Переменные окружения для БД: `LMS_DB_URL`, `LMS_DB_USER`, `LMS_DB_PASSWORD`.