Phase 7: deployment, data import, UI hardening and security fixes

Docker/prod deployment:
- multi-stage Dockerfile (JDK21 build, JRE21 run), docker-compose.prod.yml,
  nginx config for lms.our-place.ru, deploy.sh; DEPLOY/IMPORT guides (UI flow)
- Fix Docker 401: LMS_JWT_JWK_SET_URI for academic/progress; forward-headers

Variant distribution (lab page only):
- round-robin by student level, shared random queue across selected groups,
  least-used variants, manual pins always preserved; occupied/dup labels
- setup page: single form + one «Сохранить всё», batch level/variant for
  selected students (checkboxes)

Grades, defense, materials:
- publication gate + comment lists; defense CSV import (2/3 cols, quotes)
- additional task after defense ("начать заново"); enroll/unenroll students
  from group page; temporary-password lock (no navigation until changed) and
  persistent "Смена пароля"; forced change once
- chapter page /learn/{c}; header: LMS home, Войти, user -> dashboard,
  compact theme toggle; responsive mobile header
- markdown: single newlines are soft breaks; highlight.js with light/dark themes
- lab/content imports ZIP-only (lab.yaml+README+variants/assets,
  chapters tree+assets); duplicate lab numbers rejected; variant ordering
  and date defaults honored

Fixes:
- 500 on student create (@MapsId set before persist); broken dialogs/JS refs in group page
This commit is contained in:
2026-09-14 22:01:46 +03:00
parent 5e60cc3819
commit cdf4fb0ee0
58 changed files with 3967 additions and 884 deletions

116
docker-compose.prod.yml Normal file
View File

@@ -0,0 +1,116 @@
# Production-стек LMS: PostgreSQL 18 + 5 сервисов.
# Java на сервере не нужна — всё собирается и запускается в контейнерах.
#
# Запуск:
# cp .env.prod.example .env.prod # и заполнить секреты
# docker compose --env-file .env.prod -f docker-compose.prod.yml up -d --build
#
# Наружу публикуется только web на 127.0.0.1:8090; его проксирует nginx (deploy/nginx).
name: lms
x-db-env: &db-env
LMS_DB_USER: ${LMS_DB_USER:-lms}
LMS_DB_PASSWORD: ${LMS_DB_PASSWORD:?Задайте LMS_DB_PASSWORD в .env.prod}
# Все backend-сервисы проверяют JWT по JWKS identity. В контейнерах это
# http://identity:8081, а не localhost (иначе 401 на авторизованных запросах).
x-jwt-env: &jwt-env
LMS_JWT_JWK_SET_URI: http://identity:8081/api/v1/auth/jwks.json
services:
postgres:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_DB: ${LMS_DB_NAME:-lms}
POSTGRES_USER: ${LMS_DB_USER:-lms}
POSTGRES_PASSWORD: ${LMS_DB_PASSWORD:?Задайте LMS_DB_PASSWORD в .env.prod}
volumes:
- lms_pgdata:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${LMS_DB_USER:-lms} -d ${LMS_DB_NAME:-lms}"]
interval: 5s
timeout: 3s
retries: 20
networks: [lms]
identity:
build: { context: ., target: identity }
image: lms-identity:latest
restart: unless-stopped
environment:
<<: *db-env
LMS_DB_URL: jdbc:postgresql://postgres:5432/${LMS_DB_NAME:-lms}?currentSchema=identity
depends_on:
postgres: { condition: service_healthy }
networks: [lms]
academic:
build: { context: ., target: academic }
image: lms-academic:latest
restart: unless-stopped
environment:
<<: [*db-env, *jwt-env]
LMS_DB_URL: jdbc:postgresql://postgres:5432/${LMS_DB_NAME:-lms}?currentSchema=academic
LMS_CONTENT_ASSETS_DIR: /data/content-assets
volumes:
- lms_assets:/data/content-assets
depends_on:
postgres: { condition: service_healthy }
identity: { condition: service_started }
networks: [lms]
progress:
build: { context: ., target: progress }
image: lms-progress:latest
restart: unless-stopped
environment:
<<: [*db-env, *jwt-env]
LMS_DB_URL: jdbc:postgresql://postgres:5432/${LMS_DB_NAME:-lms}?currentSchema=progress
LMS_SUBMISSIONS_DIR: /data/submissions
LMS_IDENTITY_URI: http://identity:8081
LMS_ACADEMIC_URI: http://academic:8082
volumes:
- lms_submissions:/data/submissions
depends_on:
postgres: { condition: service_healthy }
identity: { condition: service_started }
academic: { condition: service_started }
networks: [lms]
gateway:
build: { context: ., target: gateway }
image: lms-gateway:latest
restart: unless-stopped
environment:
LMS_IDENTITY_URI: http://identity:8081
LMS_ACADEMIC_URI: http://academic:8082
LMS_PROGRESS_URI: http://progress:8083
LMS_JWT_JWK_SET_URI: http://identity:8081/api/v1/auth/jwks.json
depends_on:
identity: { condition: service_started }
academic: { condition: service_started }
progress: { condition: service_started }
networks: [lms]
web:
build: { context: ., target: web }
image: lms-web:latest
restart: unless-stopped
environment:
LMS_GATEWAY_URL: http://gateway:8080
SERVER_PORT: 8090
ports:
- "127.0.0.1:${LMS_WEB_PORT:-8090}:8090"
depends_on:
gateway: { condition: service_started }
networks: [lms]
networks:
lms:
volumes:
lms_pgdata:
lms_submissions:
lms_assets: