docs: актуализация под текущее состояние (Bootstrap вместо htmx, RestClient, интерцепторы, контроллеры, публикация по студентам, watch-опрос, автосейв без перезагрузки)
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
flowchart LR
|
||||
user["Преподаватель / студент<br/>(браузер)"]
|
||||
nginx["nginx<br/>lms.our-place.ru<br/>TLS, reverse proxy"]
|
||||
web["web BFF<br/>Thymeleaf + htmx<br/>:8090"]
|
||||
web["web BFF<br/>Thymeleaf + Bootstrap<br/>:8090"]
|
||||
gw["gateway<br/>Spring Cloud Gateway<br/>:8080"]
|
||||
id["identity-service<br/>:8081"]
|
||||
ac["academic-service<br/>:8082"]
|
||||
@@ -44,9 +44,9 @@ flowchart LR
|
||||
```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"]
|
||||
wc["@Controller: AuthPage, Learn, Student,<br/>AdminGroup/Lab/Student/Distribute/Defense/Users, ContentAdmin<br/>@RestController: ContentAssetProxy"]
|
||||
wi["Interceptor'ы:<br/>Csrf, NoCachePage, MustChangePassword, Auth, Admin"]
|
||||
wapi["ApiClient (RestClient)<br/>+ DTO"]
|
||||
wmd["MarkdownRenderer<br/>(libs:markdown)"]
|
||||
wc --> wapi
|
||||
wc --> wmd
|
||||
@@ -67,16 +67,16 @@ flowchart TB
|
||||
end
|
||||
|
||||
subgraph ac["academic-service (:8082)"]
|
||||
acc["LabController, ContentPublicController,<br/>ContentAdminController"]
|
||||
acc["LabController, ContentPublicController,<br/>ContentAdminController, ContentOrderController"]
|
||||
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"]
|
||||
prc["ProgressController, SubmissionController, DefenseAdminController,<br/>DefenseMyController, StudentPurgeController, GroupLabDataController"]
|
||||
prs["ProgressService, SubmissionService, DefenseService,<br/>GradePublicationService, StudentPurgeService"]
|
||||
prr["VariantAssignmentRepository, StudentLabLevelRepository,<br/>LabGradeRepository, GradeCommentRepository, CommentTemplateRepository,<br/>SubmissionRepository, DefenseSessionRepository,<br/>GradePublicationRepository, GradePublicationStudentRepository"]
|
||||
prcl["AcademicClient, IdentityClient"]
|
||||
prc --> prs
|
||||
prs --> prr
|
||||
@@ -150,7 +150,8 @@ flowchart TD
|
||||
```mermaid
|
||||
flowchart LR
|
||||
req["HTTP-запрос"] --> csrf["CsrfInterceptor /**<br/>GET: выдаёт token в сессию<br/>POST: сверяет _csrf"]
|
||||
csrf --> must["MustChangePasswordInterceptor /**<br/>(кроме /change-password, /logout, /login, статики)"]
|
||||
csrf --> nocahe["NoCachePageInterceptor /**<br/>(страницы без кэша; /css, /js исключены)"]
|
||||
nocahe --> must["MustChangePasswordInterceptor /**<br/>(кроме /change-password, /logout, /login, статики)"]
|
||||
must --> auth["AuthInterceptor<br/>/admin/**, /student/**, /change-password"]
|
||||
auth --> adm["AdminInterceptor /admin/**"]
|
||||
adm --> ctrl["Контроллер → ApiClient → gateway"]
|
||||
|
||||
@@ -47,6 +47,8 @@ flowchart TB
|
||||
| gateway | `target: gateway` | 8080 | `LMS_IDENTITY_URI`, `LMS_ACADEMIC_URI`, `LMS_PROGRESS_URI`, `LMS_JWT_JWK_SET_URI` | — |
|
||||
| web | `target: web` | 8090 | `LMS_GATEWAY_URL`, `SERVER_PORT` | — |
|
||||
|
||||
Общие для сервисов переменные БД: `LMS_DB_USER`, `LMS_DB_PASSWORD`, `LMS_DB_NAME`.
|
||||
|
||||
`LMS_JWT_JWK_SET_URI` обязателен для gateway, academic и progress —
|
||||
в контейнерах это `http://identity:8081/api/v1/auth/jwks.json` (иначе 401).
|
||||
|
||||
@@ -99,9 +101,10 @@ sequenceDiagram
|
||||
DC->>DC: docker build (5 стадий)
|
||||
DC->>PG: старт postgres (volume lms_pgdata)
|
||||
DC->>DC: старт identity → academic → progress → gateway → web
|
||||
Note over DC: Flyway применяет миграции<br/>V1..V5 автоматически при старте сервисов
|
||||
Note over DC: Flyway применяет миграции автоматически<br/>при старте сервисов (identity V1..V3, academic V1..V5, progress V1..V8; у web миграций нет)
|
||||
```
|
||||
|
||||
- Локально (`docker-compose.yml`) поднимается только `postgres`; прод-стек — `docker-compose.prod.yml`.
|
||||
- Резервные копии: `pg_dump` (том `lms_pgdata`) и `tar` томов `lms_assets`,
|
||||
`lms_submissions` — команды в [deploy/DEPLOY.md](../deploy/DEPLOY.md).
|
||||
- Порядок наполнения данными (админ → группы → лабораторная → варианты →
|
||||
|
||||
@@ -182,6 +182,7 @@ classDiagram
|
||||
class LabController
|
||||
class ContentPublicController
|
||||
class ContentAdminController
|
||||
class ContentOrderController
|
||||
class LabService {
|
||||
+importFromFile(String, ByteArray) LabImportResult
|
||||
+list() List~LabSummary~
|
||||
@@ -234,6 +235,7 @@ classDiagram
|
||||
LabController --> LabService
|
||||
ContentPublicController --> ContentService
|
||||
ContentAdminController --> ContentService
|
||||
ContentOrderController --> ContentService
|
||||
LabService --> LabRepository
|
||||
LabService --> LabLevelRepository
|
||||
LabService --> VariantRepository
|
||||
@@ -274,6 +276,12 @@ classDiagram
|
||||
+UUID labId
|
||||
+Instant publishedAt
|
||||
}
|
||||
class GradePublicationStudent {
|
||||
+UUID groupId
|
||||
+UUID labId
|
||||
+UUID studentId
|
||||
+Instant publishedAt
|
||||
}
|
||||
class GradeComment {
|
||||
+UUID id
|
||||
+UUID studentId
|
||||
@@ -313,6 +321,8 @@ classDiagram
|
||||
ready
|
||||
}
|
||||
|
||||
GradePublication "1" --> "*" GradePublicationStudent : легаси «вся группа» и персональные
|
||||
Note for GradePublicationStudent : персональная публикация оценки/комментариев студенту
|
||||
CommentTemplate "1" --> "*" GradeComment : подсказки (text/tone)
|
||||
GradeComment "*" --> "1" LabGrade : поясняет оценку
|
||||
LabGrade "1" --> "0..1" GradePublication : видна студенту после публикации
|
||||
@@ -324,8 +334,11 @@ classDiagram
|
||||
```mermaid
|
||||
classDiagram
|
||||
class ProgressController
|
||||
class SubmissionController
|
||||
class DefenseAdminController
|
||||
class DefenseMyController
|
||||
class StudentPurgeController
|
||||
class GroupLabDataController
|
||||
class ProgressService {
|
||||
+setLevel(LevelAssignmentRequest, String) LevelAssignmentResponse
|
||||
+getLevel(UUID, UUID) StudentLabLevel
|
||||
@@ -334,7 +347,7 @@ classDiagram
|
||||
+assignWithInfo(AssignRequest, String) VariantAssignmentResponse
|
||||
+assignmentsForLabEnriched(UUID, String) List~VariantAssignmentResponse~
|
||||
+setGrade(GradeSetRequest) GradeResponse
|
||||
+suggestComments(String) List~CommentOut~
|
||||
+suggestComments(String) List~CommentSuggestion~
|
||||
+myComments(UUID) List~GradeComment~
|
||||
+getStudentAssignment(UUID, UUID, String) MeAssignmentResponse
|
||||
}
|
||||
@@ -356,10 +369,12 @@ classDiagram
|
||||
+resetByStudentLab(UUID, UUID)
|
||||
}
|
||||
class GradePublicationService {
|
||||
+setPublished(UUID, UUID, Boolean)
|
||||
+isPublished(UUID, UUID) Boolean
|
||||
+publishedLabIdsForGroup(UUID) List~String~
|
||||
+publishedGroupIdsForLab(UUID) List~String~
|
||||
+state(UUID, UUID) PublicationState
|
||||
+setStudents(UUID, UUID, List~UUID~)
|
||||
+isPublished(UUID, UUID, UUID) Boolean
|
||||
}
|
||||
class StudentPurgeService {
|
||||
+purge(UUID)
|
||||
}
|
||||
class AcademicClient {
|
||||
+getLab(String, String) LabInfo
|
||||
@@ -383,12 +398,17 @@ classDiagram
|
||||
class SubmissionRepository
|
||||
class DefenseSessionRepository
|
||||
class GradePublicationRepository
|
||||
class GradePublicationStudentRepository
|
||||
|
||||
ProgressController --> ProgressService
|
||||
SubmissionController --> SubmissionService
|
||||
DefenseAdminController --> DefenseService
|
||||
DefenseMyController --> DefenseService
|
||||
StudentPurgeController --> StudentPurgeService
|
||||
GroupLabDataController --> ProgressService
|
||||
ProgressController --> SubmissionService
|
||||
ProgressController --> GradePublicationService
|
||||
StudentPurgeService --> SubmissionStorage
|
||||
ProgressService --> VariantAssignmentRepository
|
||||
ProgressService --> StudentLabLevelRepository
|
||||
ProgressService --> LabGradeRepository
|
||||
@@ -401,6 +421,7 @@ classDiagram
|
||||
SubmissionService --> AcademicClient
|
||||
DefenseService --> DefenseSessionRepository
|
||||
GradePublicationService --> GradePublicationRepository
|
||||
GradePublicationService --> GradePublicationStudentRepository
|
||||
```
|
||||
|
||||
## 4.web (BFF)
|
||||
@@ -410,13 +431,29 @@ classDiagram
|
||||
class AuthPageController
|
||||
class LearnController
|
||||
class StudentController
|
||||
class AdminController
|
||||
class AdminGroupController
|
||||
class AdminLabController
|
||||
class AdminStudentController
|
||||
class AdminDistributeController
|
||||
class AdminDefenseController
|
||||
class AdminUsersController
|
||||
class ContentAdminController
|
||||
class ContentAssetProxy
|
||||
class ApiClient {
|
||||
+login(String, String) AuthResponseDto
|
||||
+contentTree(String, Boolean) ContentTreeNodeDto
|
||||
+contentArticle(String, String, String, String, Boolean) ContentArticleDto
|
||||
+setContentPublished(String, String, String, Boolean)
|
||||
+suggestComments(String, String) List~CommentSuggestionDto~
|
||||
+deleteCommentTemplate(String, String)
|
||||
+publicationState(String, String, String) PublicationStateDto
|
||||
+setPublication(String, PublicationSetPayload)
|
||||
+purgeStudent(String, String)
|
||||
+searchStudents(String, String) List~AdminUserDto~
|
||||
+deleteStudent(String, String) DeleteUserDto
|
||||
+defenseWatch(String, List~String~) List~DefenseDto~
|
||||
+myDefenseStatus(String, String) Map
|
||||
+groupLabData(String, String) List~Map~
|
||||
+groupDetail(String, String) GroupDetailDto
|
||||
+distribute(String, DistributeRequestPayload) DistributeResultDto
|
||||
+setGrade(String, String, String, String, String) GradeResponseDto
|
||||
@@ -437,6 +474,7 @@ classDiagram
|
||||
+String USER
|
||||
}
|
||||
class CsrfInterceptor
|
||||
class NoCachePageInterceptor
|
||||
class AuthInterceptor
|
||||
class AdminInterceptor
|
||||
class MustChangePasswordInterceptor
|
||||
@@ -447,8 +485,14 @@ classDiagram
|
||||
AuthPageController --> ApiClient
|
||||
LearnController --> ApiClient
|
||||
StudentController --> ApiClient
|
||||
AdminController --> ApiClient
|
||||
AdminGroupController --> ApiClient
|
||||
AdminLabController --> ApiClient
|
||||
AdminStudentController --> ApiClient
|
||||
AdminDistributeController --> ApiClient
|
||||
AdminDefenseController --> ApiClient
|
||||
AdminUsersController --> ApiClient
|
||||
ContentAdminController --> ApiClient
|
||||
ContentAssetProxy --> ApiClient
|
||||
LearnController --> MarkdownRenderer
|
||||
AuthPageController --> SessionUser
|
||||
AuthPageController --> SessionKeys
|
||||
@@ -544,7 +588,7 @@ erDiagram
|
||||
}
|
||||
CHAPTERS {
|
||||
uuid id PK
|
||||
varchar title UK
|
||||
varchar title
|
||||
varchar slug UK
|
||||
int position
|
||||
boolean published
|
||||
@@ -598,6 +642,12 @@ erDiagram
|
||||
uuid lab_id PK
|
||||
timestamptz published_at
|
||||
}
|
||||
GRADE_PUBLICATION_STUDENTS {
|
||||
uuid group_id PK
|
||||
uuid lab_id PK
|
||||
uuid student_id PK
|
||||
timestamptz published_at
|
||||
}
|
||||
GRADE_COMMENTS {
|
||||
uuid id PK
|
||||
uuid student_id
|
||||
@@ -636,6 +686,7 @@ erDiagram
|
||||
STUDENT_LAB_LEVELS }o--|| LABS : "lab_id (не FK)"
|
||||
LAB_GRADES }o--|| LABS : "lab_id (не FK)"
|
||||
GRADE_PUBLICATIONS }o--|| LABS : "lab_id (не FK)"
|
||||
GRADE_PUBLICATION_STUDENTS }o--|| LABS : "lab_id (не FK)"
|
||||
GRADE_COMMENTS }o--|| LABS : "lab_id (не FK)"
|
||||
SUBMISSIONS }o--|| LABS : "lab_id (не FK)"
|
||||
DEFENSE_SESSIONS }o--|| LABS : "lab_id (не FK)"
|
||||
|
||||
@@ -146,6 +146,7 @@ sequenceDiagram
|
||||
P->>DB: INSERT/UPDATE variant_assignments (manual=false)
|
||||
P-->>W: DistributeResult(assigned, total)
|
||||
W-->>A: «Распределено: N из M»
|
||||
Note over A,W: UI — большая модалка «Выдача вариантов» на странице группы<br/>(фрагмент по fetch). Эндпоинт отвечает редиректом — это норма;<br/>модалка обновляется на месте, тост показывается поверх неё
|
||||
```
|
||||
|
||||
## 6. Сдача решения студентом
|
||||
@@ -214,13 +215,13 @@ sequenceDiagram
|
||||
P-->>W: DefenseDto
|
||||
W-->>S: «Работа сохранена»
|
||||
|
||||
Note over P,DB: DefenseService.syncTimeouts() переводит просроченные<br/>сессии в ready с ready_reason=timeout
|
||||
Note over P,DB: DefenseService.syncTimeouts() переводит просроченные<br/>сессии в ready с ready_reason=timeout; кнопка «Готов» даёт ready_reason=button.<br/>В UI: «Готов к сдаче» (button) / «Вышло время» (timeout)
|
||||
A->>W: «доп. задание: начать заново» → POST /admin/defenses/{id}/reset
|
||||
P->>DB: UPDATE status=waiting, code=NULL, started_at=NULL
|
||||
W-->>A: сессия снова ждёт кода
|
||||
```
|
||||
|
||||
## 8. Оценка, комментарии и публикация
|
||||
## 8. Оценка, комментарии и публикация (по студенту)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
@@ -235,19 +236,28 @@ sequenceDiagram
|
||||
W->>P: POST /api/v1/grades (GradeSetRequest)
|
||||
P->>DB: UPSERT lab_grades (student_id, lab_id, score)
|
||||
P->>DB: DELETE grade_comments по паре, затем INSERT новых
|
||||
P->>DB: UPSERT comment_templates (use_count++ для автоподсказок)
|
||||
Note over DB: comment_templates — подсказки; частота считается<br/>по актуальным grade_comments (не по use_count)
|
||||
P-->>W: GradeResponse
|
||||
Note over DB: Студент оценку пока НЕ видит
|
||||
|
||||
A->>W: «опубликовать оценки» → POST /admin/groups/{gid}/publish
|
||||
W->>P: POST /api/v1/grades/publish (labId, groupId, published=true)
|
||||
P->>DB: INSERT/DELETE grade_publications (group_id, lab_id)
|
||||
P-->>W: 200
|
||||
A->>W: чекбокс публикации в ячейке (student×lab) → POST /admin/groups/{gid}/publish-student
|
||||
W->>P: POST /api/v1/grades/publication {groupId, labId, studentIds[]} — заменяет набор
|
||||
P->>DB: DELETE/INSERT grade_publication_students<br/>(легаси grade_publications снимается)
|
||||
W-->>A: тост «Опубликовано: N из M», счётчик «глаза» обновлён
|
||||
|
||||
A->>W: «глаз» в шапке ЛР (всем / скрыть у всех) → POST /admin/groups/{gid}/publish (published=true|false)
|
||||
W->>P: POST /api/v1/grades/publication {studentIds = все | []}
|
||||
|
||||
A->>W: поиск подсказок → GET /admin/comment-templates?q=
|
||||
W->>P: GET /api/v1/comment-templates?q=
|
||||
P-->>W: [{text, tone, count}] — частота по текущим оценкам
|
||||
A->>W: удалить подсказку с частотой 0 → POST /admin/comment-templates/delete (text)
|
||||
W->>P: DELETE /api/v1/comment-templates?text= (400, если где-то используется)
|
||||
|
||||
S->>W: GET /student/labs/{labId}
|
||||
W->>P: GET /api/v1/me/grades?groupId=... (Bearer)
|
||||
P->>DB: SELECT lab_grades + grade_comments + grade_publications
|
||||
P-->>W: только опубликованные оценки группы
|
||||
P->>DB: SELECT lab_grades + grade_comments; фильтр isPublished(group, lab, student)
|
||||
P-->>W: только опубликованные этому студенту оценки
|
||||
W-->>S: оценка и комментарии списками: позитивный / негативный / нейтральный
|
||||
```
|
||||
|
||||
@@ -314,16 +324,18 @@ sequenceDiagram
|
||||
G->>ID: GroupService.detail (студенты по ФИО)
|
||||
W->>G: GET /api/v1/labs
|
||||
G->>AC: LabService.list (опубликованные ЛР)
|
||||
W->>G: GET /api/v1/assignments/group/{gid}
|
||||
G->>P: уровни и варианты всех студентов группы (bulk)
|
||||
loop по каждой лабораторной
|
||||
W->>G: GET /api/v1/assignments?labId
|
||||
G->>P: варианты студентов
|
||||
W->>G: GET /api/v1/submissions?labId
|
||||
G->>P: последние сдачи
|
||||
W->>G: GET /api/v1/defenses?labId
|
||||
G->>P: сессии защиты
|
||||
W->>G: GET /api/v1/grades/publication?groupId&labId
|
||||
G->>P: состояние публикации {all, studentIds}
|
||||
end
|
||||
W->>G: GET /api/v1/grades + /grades/publications?groupId
|
||||
G->>P: оценки
|
||||
W->>G: GET /api/v1/grades
|
||||
G->>P: все оценки (матрица)
|
||||
W->>W: сборка матрицы «студент × лабораторная»
|
||||
W-->>A: страница группы (оценки, защиты, поиск, фильтры, сортировка)
|
||||
```
|
||||
@@ -351,6 +363,32 @@ sequenceDiagram
|
||||
AC->>DB: UPDATE chapters.position,<br/>sections.chapter_id/position,<br/>articles.section_id/position
|
||||
AC-->>W: 200
|
||||
W-->>UI: {"ok": true}
|
||||
UI->>UI: location.reload() — страница из серверного состояния
|
||||
UI->>UI: без перезагрузки — DOM уже перестроен, статус «сохранено»
|
||||
Note over AC,DB: один запрос применяет и порядок, и перенос между родителями
|
||||
Note over UI: раскрытые разделы и позиция скролла сохраняются (localStorage / ui-state.js)
|
||||
```
|
||||
|
||||
## 12. Синхронизация таймеров защиты (опрос раз в 10 секунд)
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
actor A as Преподаватель
|
||||
actor S as Студент
|
||||
participant W as web
|
||||
participant P as progress
|
||||
|
||||
loop каждые 10 c (страница группы)
|
||||
A->>W: GET /admin/defense-watch?labs=...
|
||||
W->>P: GET /api/v1/defenses/watch?labs=...
|
||||
P-->>W: статусы, secondsLeft, readyReason
|
||||
W-->>A: обновление ячеек; переход в ready → тост «ФИО — ЛР: Готов к сдаче / Вышло время»
|
||||
end
|
||||
|
||||
loop каждые 10 c (страница лабы студента)
|
||||
S->>W: GET /student/labs/{id}/defense-status
|
||||
W->>P: GET /api/v1/me/defenses/{labId}/status
|
||||
P-->>W: status / secondsLeft / readyReason
|
||||
W-->>S: подстройка таймера; при status != started — перезагрузка страницы
|
||||
end
|
||||
```
|
||||
|
||||
@@ -17,23 +17,32 @@ stateDiagram-v2
|
||||
end note
|
||||
```
|
||||
|
||||
## 2. Публикация оценок (пара «группа + лабораторная»)
|
||||
## 2. Публикация оценок (пара «студент + лабораторная»)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> hidden : оценка выставлена (lab_grades)
|
||||
hidden --> published : «опубликовать оценки» (grade_publications)
|
||||
published --> hidden : «скрыть оценки»
|
||||
hidden --> published : чекбокс публикации в ячейке студент×ЛР (grade_publication_students)
|
||||
published --> hidden : снять чекбокс
|
||||
hidden --> published : «опубликовать всем» (глаз в шапке ЛР)
|
||||
published --> hidden : «скрыть у всех»
|
||||
note right of hidden
|
||||
студент не видит ни балл,
|
||||
ни комментарии
|
||||
студент не видит ни балл, ни комментарии.
|
||||
Проверка доступа — по (student, lab):
|
||||
групповая публикация (легаси, grade_publications)
|
||||
открывает всем, иначе — по списку студентов.
|
||||
end note
|
||||
note right of published
|
||||
студенты группы видят балл и
|
||||
комментарии, сгруппированные по тону
|
||||
видно только выбранным студентам; подсказки
|
||||
комментариев показывают частоту и позволяют
|
||||
удалять шаблоны с частотой 0
|
||||
end note
|
||||
```
|
||||
|
||||
Кнопки: чекбокс в ячейке публикует/скрывает оценку конкретному студенту (AJAX, без перезагрузки,
|
||||
тост с «N из M»); «глаз» в шапке лабораторной — массово «всем/скрыть у всех» (частичное состояние —
|
||||
жёлтый со счётчиком).
|
||||
|
||||
## 3. Статус лабораторной (`labs.status`)
|
||||
|
||||
```mermaid
|
||||
@@ -113,7 +122,7 @@ stateDiagram-v2
|
||||
stateDiagram-v2
|
||||
[*] --> temporary : аккаунт создан / «Сбросить пароль»
|
||||
temporary --> own : смена на /change-password
|
||||
own --> temporary : «Сбросить пароль» преподавателем
|
||||
own --> temporary : «Сбросить пароль» администратором
|
||||
note right of temporary
|
||||
must_change_password = true,
|
||||
temp_password хранится;
|
||||
|
||||
@@ -89,16 +89,20 @@ printf("Ответ: 42\n");
|
||||
|
||||
Администратору доступны действия не только на главной `/learn`, но и на каждой странице:
|
||||
|
||||
* **статья** — «Править» (правка прямо на странице: текст статьи открывается в редакторе
|
||||
* **статья** — иконка-карандаш (правка прямо на странице: текст статьи открывается в редакторе
|
||||
с панелью форматирования — жирный, курсив, заголовки, списки, ссылки, код, таблицы,
|
||||
картинки, видео, спойлер), «Скрыть/Опубликовать», «Удалить», а также навигация
|
||||
**← предыдущая / следующая →** (работает и между разделами) и **боковое содержание**
|
||||
со всеми главами, разделами и статьями;
|
||||
* **раздел** — «+ Статья», «Переименовать раздел», «Скрыть/Опубликовать»,
|
||||
«Перенести статьи и удалить», «Удалить раздел»; у каждой статьи в списке — «Править»
|
||||
«Перенести статьи и удалить», «Удалить раздел»; у каждой статьи в списке — иконка-карандаш
|
||||
(открывает статью с готовым окном правки), скрытие и удаление;
|
||||
* **глава** — «+ Раздел», «Переименовать главу», «Скрыть/Опубликовать», «Удалить главу»;
|
||||
у каждого раздела — переименование, скрытие, перенос статей и удаление, у каждой статьи — «Править».
|
||||
у каждого раздела — переименование, скрытие, перенос статей и удаление, у каждой статьи — иконка-карандаш.
|
||||
|
||||
На странице статьи боковое содержание: на десктопе закреплено (своя прокрутка), на мобильных
|
||||
открывается кнопкой «Содержание» справа. На главной `/learn` на мобильных три кнопки
|
||||
(«+ Статья», «+ Глава», «Импорт zip») идут под заголовком в один ряд.
|
||||
|
||||
Порядок меняется перетаскиванием за значок **⠿** (сохраняется сразу, без перезагрузки страницы):
|
||||
на главной `/learn` — главы, разделы и статьи (в том числе перенос раздела в другую главу и
|
||||
|
||||
@@ -19,13 +19,14 @@ Preview Mermaid Support) и в IntelliJ IDEA (плагин Mermaid).
|
||||
## Кратко о системе
|
||||
|
||||
LMS — микросервисное приложение на Kotlin/Spring Boot 3 (Java 21) с
|
||||
server-side-rendered UI на Thymeleaf + htmx:
|
||||
server-side-rendered UI: Thymeleaf + Bootstrap 5 (CSS и иконки вендорены локально)
|
||||
и собственный JS (htmx не используется):
|
||||
|
||||
- **gateway** (`:8080`) — единая точка входа для REST API: маршрутизация и проверка JWT;
|
||||
- **identity-service** (`:8081`) — аккаунты, группы, студенты, аутентификация (JWT + JWKS);
|
||||
- **academic-service** (`:8082`) — семестры, лабораторные (уровни, варианты), учебные материалы;
|
||||
- **progress-service** (`:8083`) — варианты/уровни студентов, сдачи, оценки и комментарии, защиты;
|
||||
- **web** (`:8090`) — BFF: сессии, CSRF, страницы админа и студента, markdown-рендер;
|
||||
- **web** (`:8090`) — BFF: сессии, CSRF, страницы админа и студента, markdown-рендер, Static (Bootstrap/Icons);
|
||||
- **PostgreSQL 18** — одна БД `lms` со схемами `identity`, `academic`, `progress`.
|
||||
|
||||
Поток запроса: браузер → nginx → web → gateway → сервис → PostgreSQL.
|
||||
|
||||
Reference in New Issue
Block a user