docs: add UML and architecture documentation (Mermaid)

- docs/README.md — index and short system overview
- 01-architecture.md — context, per-service components, Gradle modules,
  authentication/authorization, web interceptors (flowcharts)
- 02-deployment.md — deployment diagram, containers/env/volumes, build stages,
  nginx, update sequence
- 03-domain-model.md — class diagrams for identity/academic/progress and web,
  ER model of all schemas (cross-schema references noted)
- 04-sequences.md — 10 sequence diagrams (login, JWT check, forced password
  change, lab import, distribution, submission, defense, grades+publication,
  content publishing, group page aggregation)
- 05-states.md — state diagrams for defense session, grade publication, lab
  status, content visibility, submission lifecycle, variant assignment, password
MS
git log --oneline -3
This commit is contained in:
2026-09-16 18:44:14 +03:00
parent 30b8064718
commit 025c937f21
7 changed files with 1398 additions and 0 deletions

160
docs/01-architecture.md Normal file
View File

@@ -0,0 +1,160 @@
# Архитектура системы
## 1. Контекст (какие внешние участники и системы)
```mermaid
flowchart LR
user["Преподаватель / студент<br/>(браузер)"]
nginx["nginx<br/>lms.our-place.ru<br/>TLS, reverse proxy"]
web["web BFF<br/>Thymeleaf + htmx<br/>:8090"]
gw["gateway<br/>Spring Cloud Gateway<br/>:8080"]
id["identity-service<br/>:8081"]
ac["academic-service<br/>:8082"]
pr["progress-service<br/>:8083"]
db[("PostgreSQL 18<br/>схемы identity / academic / progress")]
fs[("Файловое хранилище<br/>content-assets, submissions")]
user -->|HTTPS| nginx
nginx -->|HTTP 127.0.0.1:8090| web
web -->|Bearer JWT| gw
gw --> id
gw --> ac
gw --> pr
id --> db
ac --> db
pr --> db
ac --> fs
pr --> fs
pr -->|REST, внутренняя сеть| ac
pr -->|REST, внутренняя сеть| id
id -.->|JWKS: /api/v1/auth/jwks.json| gw
id -.->|JWKS| ac
id -.->|JWKS| pr
```
- Браузер никогда не получает JWT: токен живёт в HTTP-сессии web-сервиса
(`lms.token`), а в браузер уходит только cookie сессии (`HttpOnly`, 30 дней).
- Все REST-запросы web проксирует на gateway, добавляя `Authorization: Bearer`.
- Gateway проверяет подпись токена по JWKS identity и передаёт вниз заголовки
`X-Account-Id`, `X-Account-Login`, `X-Account-Role`.
- Каждый сервис дополнительно валидирует JWT самостоятельно (resource server).
## 2. Компоненты по сервисам
```mermaid
flowchart TB
subgraph web["web (BFF, :8090)"]
wc["@Controller<br/>AuthPage, Learn, Student, Admin, ContentAdmin"]
wi["Interceptor'ы:<br/>Csrf, Auth, Admin, MustChangePassword"]
wapi["ApiClient (WebClient)<br/>+ DTO"]
wmd["MarkdownRenderer<br/>(libs:markdown)"]
wc --> wapi
wc --> wmd
wi --> wc
end
subgraph gw["gateway (:8080)"]
routes["Маршруты<br/>identity / academic / progress"]
filter["JwtAuthenticationFilter<br/>order -100"]
filter --> routes
end
subgraph id["identity-service (:8081)"]
idc["AuthController, GroupController, StudentController"]
ids["AuthService, GroupService, Passwords,<br/>StudentImportParser, CredentialsExporter"]
idr["AccountRepository, GroupRepository,<br/>EnrollmentRepository, StudentRepository, SettingRepository"]
idc --> ids --> idr
end
subgraph ac["academic-service (:8082)"]
acc["LabController, ContentPublicController,<br/>ContentAdminController"]
acs["LabService, ContentService,<br/>LabImportParser, AssetStorage"]
acr["LabRepository, LabLevelRepository, VariantRepository,<br/>SemesterRepository, Content*Repository"]
acc --> acs --> acr
end
subgraph pr["progress-service (:8083)"]
prc["ProgressController, DefenseAdminController,<br/>DefenseMyController"]
prs["ProgressService, SubmissionService,<br/>DefenseService, GradePublicationService"]
prr["VariantAssignmentRepository, StudentLabLevelRepository,<br/>LabGradeRepository, GradeCommentRepository,<br/>CommentTemplateRepository, SubmissionRepository,<br/>DefenseSessionRepository, GradePublicationRepository"]
prcl["AcademicClient, IdentityClient"]
prc --> prs
prs --> prr
prs --> prcl
end
wapi -->|HTTP| routes
routes --> idc
routes --> acc
routes --> prc
prcl -->|GET /api/v1/labs/{id}| acc
prcl -->|GET /api/v1/groups и /{id}| idc
```
## 3. Модули Gradle
```mermaid
flowchart LR
web[":web"] --> common[":libs:common-http"]
web --> md[":libs:markdown"]
gw[":services:gateway"] --> sec[":libs:security-jwt"]
gw --> common
id[":services:identity-service"] --> common
id --> sec
ac[":services:academic-service"] --> common
ac --> sec
ac --> md
pr[":services:progress-service"] --> common
pr --> sec
tests[":libs:test-support"] -.-> id
tests -.-> ac
tests -.-> pr
```
| Модуль | Назначение |
|---|---|
| `:libs:common-http` | единый формат ошибок (`ApiError`), иерархия `LmsException`, `LmsExceptionHandler`, генератор UUIDv7 (`Ids`) |
| `:libs:security-jwt` | свойства JWT, парсинг RSA-ключей, автоконфигурация `JwtEncoder`/`JwtDecoder`/`PasswordEncoder`, enum `LmsRole` |
| `:libs:markdown` | markdown → безопасный HTML (OWASP-санитайзер), таблицы, спойлеры `:::spoiler` |
| `:libs:test-support` | константы для интеграционных тестов с PostgreSQL |
## 4. Аутентификация и авторизация
```mermaid
flowchart TD
a["Клиент: POST /api/v1/auth/login"] --> b["gateway: путь публичный<br/>(login/setup/health/jwks)"]
b --> c["identity: AuthService.login"]
c --> d{"BCrypt совпал?"}
d -->|нет| e["401 + ApiError"]
d -->|да| f["JwtEncoder (RSA-2048, kid=lms)<br/>claims: sub, login, role, exp"]
f --> g["AuthResponse{token,...}"]
h["Клиент: запрос к защищённому API<br/>Authorization: Bearer ..."] --> i["gateway JwtAuthenticationFilter"]
i --> j{"Токен валиден по JWKS?"}
j -->|нет| k["401 Unauthorized"]
j -->|да| l["Добавляет X-Account-Id/Login/Role"]
l --> m["Сервис: SecurityConfig<br/>oauth2ResourceServer(jwt)"]
m --> n{"@PreAuthorize<br/>ROLE_ADMIN / isAuthenticated"}
n -->|нет| o["403 Forbidden"]
n -->|да| p["Контроллер → сервис → БД"]
```
- Роли: `ADMIN`, `STUDENT`; конвертер ролей читает claim `role` как `ROLE_<role>`.
- Публичные пути: `/actuator/**`, `/api/v1/auth/{health,setup,login,jwks.json}`,
`/api/v1/content/tree`, `/api/v1/content/assets/**`,
`/api/v1/content/{chapter}/{section}/{article}`.
- RSA-ключи identity хранятся в таблице `settings` и публикуются в JWKS.
## 5. Рендеринг страниц и защита форм (web)
```mermaid
flowchart LR
req["HTTP-запрос"] --> csrf["CsrfInterceptor /**<br/>GET: выдаёт token в сессию<br/>POST: сверяет _csrf"]
csrf --> must["MustChangePasswordInterceptor /**<br/>(кроме /change-password, /logout, /login, статики)"]
must --> auth["AuthInterceptor<br/>/admin/**, /student/**, /change-password"]
auth --> adm["AdminInterceptor /admin/**"]
adm --> ctrl["Контроллер → ApiClient → gateway"]
```
- `GlobalModelAdvice` кладёт `currentUser` (SessionUser) во все шаблоны.
- Скрытые материалы и полное дерево материалов доступны только администратору.