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:
639
docs/03-domain-model.md
Normal file
639
docs/03-domain-model.md
Normal file
@@ -0,0 +1,639 @@
|
||||
# Модель данных и классов
|
||||
|
||||
Ниже — диаграммы классов по сервисам (JPA-сущности + репозитории + сервисы),
|
||||
ER-модель БД и модель web-слоя.
|
||||
|
||||
## 1. identity-service
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Account {
|
||||
+UUID id
|
||||
+String login
|
||||
+String passwordHash
|
||||
+LmsRole role
|
||||
+Boolean mustChangePassword
|
||||
+String tempPassword
|
||||
+Instant createdAt
|
||||
+Instant updatedAt
|
||||
}
|
||||
class Student {
|
||||
+UUID accountId
|
||||
+String studentNo
|
||||
+String lastName
|
||||
+String firstName
|
||||
+String middleName
|
||||
+String basis
|
||||
+String note
|
||||
+String fullName
|
||||
}
|
||||
class GroupEntity {
|
||||
+UUID id
|
||||
+String name
|
||||
+Int taskDurationMin
|
||||
+Instant createdAt
|
||||
}
|
||||
class Enrollment {
|
||||
+UUID id
|
||||
+Instant enrolledAt
|
||||
}
|
||||
class Setting {
|
||||
+String key
|
||||
+String value
|
||||
}
|
||||
class LmsRole {
|
||||
<<enumeration>>
|
||||
ADMIN
|
||||
STUDENT
|
||||
}
|
||||
|
||||
Account "1" --> "1" Student : @MapsId account_id
|
||||
GroupEntity "1" --> "*" Enrollment : group_id
|
||||
Student "1" --> "*" Enrollment : student_id
|
||||
Account --> LmsRole : role
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AuthController
|
||||
class GroupController
|
||||
class StudentController
|
||||
class AuthService {
|
||||
+adminExists() Boolean
|
||||
+setup(SetupRequest) AuthResponse
|
||||
+login(LoginRequest) AuthResponse
|
||||
+me(UUID) MeResponse
|
||||
+changePassword(UUID, ChangePasswordRequest)
|
||||
}
|
||||
class GroupService {
|
||||
+list() List~GroupSummary~
|
||||
+create(CreateGroupRequest) GroupSummary
|
||||
+createStudent(UUID, CreateStudentRequest) Credential
|
||||
+candidates(UUID) List~StudentCandidate~
|
||||
+enroll(UUID, UUID) GroupDetail
|
||||
+unenroll(UUID, UUID) GroupDetail
|
||||
+import(String, String, ByteArray) ImportResult
|
||||
+groupOfStudent(UUID) GroupSummary
|
||||
+detail(UUID) GroupDetail
|
||||
+resetPassword(UUID) ResetPasswordResponse
|
||||
+credentials(UUID, String) ExportFile
|
||||
}
|
||||
class AccountRepository
|
||||
class GroupRepository
|
||||
class EnrollmentRepository
|
||||
class StudentRepository
|
||||
class SettingRepository
|
||||
class StudentImportParser {
|
||||
+parse(String, ByteArray) ParsedGroup
|
||||
}
|
||||
class CredentialsExporter {
|
||||
+csv(List~StudentRow~) ByteArray
|
||||
+xlsx(List~StudentRow~) ByteArray
|
||||
}
|
||||
class Passwords {
|
||||
+generate(Int) String
|
||||
}
|
||||
|
||||
AuthController --> AuthService
|
||||
GroupController --> GroupService
|
||||
StudentController --> GroupService
|
||||
AuthService --> AccountRepository
|
||||
AuthService --> StudentRepository
|
||||
GroupService --> GroupRepository
|
||||
GroupService --> EnrollmentRepository
|
||||
GroupService --> StudentRepository
|
||||
GroupService --> AccountRepository
|
||||
GroupService --> StudentImportParser
|
||||
GroupService --> CredentialsExporter
|
||||
GroupService --> Passwords
|
||||
```
|
||||
|
||||
## 2. academic-service
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Semester {
|
||||
+UUID id
|
||||
+String title
|
||||
+Int year
|
||||
+Boolean active
|
||||
}
|
||||
class Lab {
|
||||
+UUID id
|
||||
+Int number
|
||||
+String title
|
||||
+String descriptionMd
|
||||
+Instant deadlineAt
|
||||
+Instant defenseAt
|
||||
+BigDecimal pointsMax
|
||||
+String status
|
||||
+Instant createdAt
|
||||
}
|
||||
class LabLevel {
|
||||
+UUID id
|
||||
+String code
|
||||
+String title
|
||||
+String description
|
||||
+Int position
|
||||
+BigDecimal pointsMax
|
||||
}
|
||||
class Variant {
|
||||
+UUID id
|
||||
+String number
|
||||
+String title
|
||||
+String statementMd
|
||||
+Int position
|
||||
}
|
||||
class ContentChapter {
|
||||
+UUID id
|
||||
+String title
|
||||
+String slug
|
||||
+Int position
|
||||
+Boolean published
|
||||
}
|
||||
class ContentSection {
|
||||
+UUID id
|
||||
+UUID chapterId
|
||||
+String title
|
||||
+String slug
|
||||
+Int position
|
||||
+Boolean published
|
||||
}
|
||||
class ContentArticle {
|
||||
+UUID id
|
||||
+UUID sectionId
|
||||
+String title
|
||||
+String slug
|
||||
+String bodyMd
|
||||
+Int position
|
||||
+Boolean published
|
||||
}
|
||||
|
||||
Semester "1" --> "*" Lab : semester_id
|
||||
Lab "1" --> "*" LabLevel : lab_id
|
||||
LabLevel "1" --> "*" Variant : lab_level_id
|
||||
Lab "1" --> "*" Variant : lab_id
|
||||
ContentChapter "1" --> "*" ContentSection : chapter_id
|
||||
ContentSection "1" --> "*" ContentArticle : section_id
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class LabController
|
||||
class ContentPublicController
|
||||
class ContentAdminController
|
||||
class LabService {
|
||||
+importFromFile(String, ByteArray) LabImportResult
|
||||
+list() List~LabSummary~
|
||||
+detail(UUID) LabDetail
|
||||
+create(LabCreateRequest) LabDetail
|
||||
+update(UUID, LabUpdateRequest) LabDetail
|
||||
+delete(UUID)
|
||||
+toggleStatus(UUID)
|
||||
+addLevel(UUID, LevelSaveRequest)
|
||||
+renameLevel(UUID, UUID, LevelRenameRequest)
|
||||
+deleteLevel(UUID, UUID)
|
||||
+addVariant(UUID, VariantSaveRequest)
|
||||
+updateVariant(UUID, UUID, VariantSaveRequest)
|
||||
+deleteVariant(UUID, UUID)
|
||||
}
|
||||
class ContentService {
|
||||
+tree(Boolean) ContentTree
|
||||
+treeAll() ContentTree
|
||||
+findArticle(String, String, String, Boolean) ContentArticleView
|
||||
+setChapterPublished(UUID, Boolean)
|
||||
+setSectionPublished(UUID, Boolean)
|
||||
+setArticlePublished(UUID, Boolean)
|
||||
+listAll() List~ContentArticleView~
|
||||
+createChapter(String, String) ChapterTreeNode
|
||||
+createSection(UUID, String, String) SectionTreeNode
|
||||
+createArticlePlaced(ArticleCreatePayload) ContentArticleView
|
||||
+updateArticle(UUID, String, String)
|
||||
+moveArticle(UUID, UUID) ContentArticleView
|
||||
+deleteArticle(UUID)
|
||||
+importZip(String, ByteArray) ContentImportResult
|
||||
}
|
||||
class LabImportParser {
|
||||
+parse(String, ByteArray) LabImportBundle
|
||||
}
|
||||
class AssetStorage {
|
||||
+store(String, ByteArray) StoredAsset
|
||||
+read(String) Pair
|
||||
}
|
||||
class LabRepository
|
||||
class LabLevelRepository
|
||||
class VariantRepository
|
||||
class SemesterRepository
|
||||
class ContentChapterRepository
|
||||
class ContentSectionRepository
|
||||
class ContentArticleRepository
|
||||
|
||||
LabController --> LabService
|
||||
ContentPublicController --> ContentService
|
||||
ContentAdminController --> ContentService
|
||||
LabService --> LabRepository
|
||||
LabService --> LabLevelRepository
|
||||
LabService --> VariantRepository
|
||||
LabService --> SemesterRepository
|
||||
LabService --> LabImportParser
|
||||
ContentService --> ContentChapterRepository
|
||||
ContentService --> ContentSectionRepository
|
||||
ContentService --> ContentArticleRepository
|
||||
ContentService --> AssetStorage
|
||||
```
|
||||
|
||||
## 3. progress-service
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class VariantAssignment {
|
||||
+UUID id
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+UUID variantId
|
||||
+Boolean manual
|
||||
+Instant assignedAt
|
||||
}
|
||||
class StudentLabLevel {
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+UUID labLevelId
|
||||
+Boolean manual
|
||||
}
|
||||
class LabGrade {
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+BigDecimal score
|
||||
+Instant updatedAt
|
||||
}
|
||||
class GradePublication {
|
||||
+UUID groupId
|
||||
+UUID labId
|
||||
+Instant publishedAt
|
||||
}
|
||||
class GradeComment {
|
||||
+UUID id
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+String text
|
||||
+String tone
|
||||
+Instant createdAt
|
||||
}
|
||||
class CommentTemplate {
|
||||
+UUID id
|
||||
+String text
|
||||
+String tone
|
||||
+Int useCount
|
||||
}
|
||||
class Submission {
|
||||
+UUID id
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+Int seq
|
||||
+Instant createdAt
|
||||
}
|
||||
class DefenseSession {
|
||||
+UUID id
|
||||
+UUID studentId
|
||||
+UUID labId
|
||||
+String assignmentMd
|
||||
+String status
|
||||
+String code
|
||||
+Instant startedAt
|
||||
+Instant readyAt
|
||||
+String readyReason
|
||||
}
|
||||
class DefenseStatus {
|
||||
<<enumeration>>
|
||||
waiting
|
||||
started
|
||||
ready
|
||||
}
|
||||
|
||||
CommentTemplate "1" --> "*" GradeComment : подсказки (text/tone)
|
||||
GradeComment "*" --> "1" LabGrade : поясняет оценку
|
||||
LabGrade "1" --> "0..1" GradePublication : видна студенту после публикации
|
||||
DefenseSession --> DefenseStatus : status
|
||||
Note for VariantAssignment : studentId / labId / variantId — идентификаторы других сервисов (FK на уровне БД нет)
|
||||
Note for LabGrade : groupId для публикации берётся из identity
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class ProgressController
|
||||
class DefenseAdminController
|
||||
class DefenseMyController
|
||||
class ProgressService {
|
||||
+setLevel(LevelAssignmentRequest, String) LevelAssignmentResponse
|
||||
+getLevel(UUID, UUID) StudentLabLevel
|
||||
+getLevelsForLab(UUID) List~StudentLabLevel~
|
||||
+distribute(DistributeRequest, String) DistributeResult
|
||||
+assignWithInfo(AssignRequest, String) VariantAssignmentResponse
|
||||
+assignmentsForLabEnriched(UUID, String) List~VariantAssignmentResponse~
|
||||
+setGrade(GradeSetRequest) GradeResponse
|
||||
+suggestComments(String) List~CommentOut~
|
||||
+myComments(UUID) List~GradeComment~
|
||||
+getStudentAssignment(UUID, UUID, String) MeAssignmentResponse
|
||||
}
|
||||
class SubmissionService {
|
||||
+submit(UUID, UUID, List~MultipartFile~, String) SubmissionDto
|
||||
+mySubmissions(UUID, UUID) List~SubmissionDto~
|
||||
+download(UUID, UUID, UUID, Boolean) Pair
|
||||
+latestPerStudent(UUID) List~SubmissionDto~
|
||||
}
|
||||
class DefenseService {
|
||||
+syncTimeouts() Int
|
||||
+assign(UUID, UUID, String) DefenseDto
|
||||
+issueCode(UUID) String
|
||||
+reset(UUID)
|
||||
+listForLab(UUID) List~DefenseDto~
|
||||
+startByCode(UUID, String) DefenseDto
|
||||
+finish(UUID, UUID) DefenseDto
|
||||
+mySessions(UUID) List~DefenseDto~
|
||||
+resetByStudentLab(UUID, UUID)
|
||||
}
|
||||
class GradePublicationService {
|
||||
+setPublished(UUID, UUID, Boolean)
|
||||
+isPublished(UUID, UUID) Boolean
|
||||
+publishedLabIdsForGroup(UUID) List~String~
|
||||
+publishedGroupIdsForLab(UUID) List~String~
|
||||
}
|
||||
class AcademicClient {
|
||||
+getLab(String, String) LabInfo
|
||||
+getLabSummary(String, String) LabInfo
|
||||
}
|
||||
class IdentityClient {
|
||||
+getGroup(String, String) GroupInfo
|
||||
+listGroups(String) List~Map~
|
||||
}
|
||||
class SubmissionStorage {
|
||||
+store(UUID, UUID, Int, List~MultipartFile~) StoredSubmission
|
||||
+zipOf(Path) ByteArray
|
||||
+listFiles(Path) List~String~
|
||||
+dirFor(UUID, UUID, Int) Path
|
||||
}
|
||||
class VariantAssignmentRepository
|
||||
class StudentLabLevelRepository
|
||||
class LabGradeRepository
|
||||
class GradeCommentRepository
|
||||
class CommentTemplateRepository
|
||||
class SubmissionRepository
|
||||
class DefenseSessionRepository
|
||||
class GradePublicationRepository
|
||||
|
||||
ProgressController --> ProgressService
|
||||
DefenseAdminController --> DefenseService
|
||||
DefenseMyController --> DefenseService
|
||||
ProgressController --> SubmissionService
|
||||
ProgressController --> GradePublicationService
|
||||
ProgressService --> VariantAssignmentRepository
|
||||
ProgressService --> StudentLabLevelRepository
|
||||
ProgressService --> LabGradeRepository
|
||||
ProgressService --> GradeCommentRepository
|
||||
ProgressService --> CommentTemplateRepository
|
||||
ProgressService --> AcademicClient
|
||||
ProgressService --> IdentityClient
|
||||
SubmissionService --> SubmissionRepository
|
||||
SubmissionService --> SubmissionStorage
|
||||
SubmissionService --> AcademicClient
|
||||
DefenseService --> DefenseSessionRepository
|
||||
GradePublicationService --> GradePublicationRepository
|
||||
```
|
||||
|
||||
## 4.web (BFF)
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class AuthPageController
|
||||
class LearnController
|
||||
class StudentController
|
||||
class AdminController
|
||||
class ContentAdminController
|
||||
class ApiClient {
|
||||
+login(String, String) AuthResponseDto
|
||||
+contentTree(String, Boolean) ContentTreeNodeDto
|
||||
+contentArticle(String, String, String, String, Boolean) ContentArticleDto
|
||||
+setContentPublished(String, String, String, Boolean)
|
||||
+groupDetail(String, String) GroupDetailDto
|
||||
+distribute(String, DistributeRequestPayload) DistributeResultDto
|
||||
+setGrade(String, String, String, String, String) GradeResponseDto
|
||||
+submit(String, String, List~MultipartFile~, String) List~SubmissionDto~
|
||||
+myGrades(String, String) List~GradeResponseDto~
|
||||
}
|
||||
class SessionUser {
|
||||
+String id
|
||||
+String login
|
||||
+String role
|
||||
+String name
|
||||
+Boolean mustChangePassword
|
||||
+isAdmin() Boolean
|
||||
+isStudent() Boolean
|
||||
}
|
||||
class SessionKeys {
|
||||
+String TOKEN
|
||||
+String USER
|
||||
}
|
||||
class CsrfInterceptor
|
||||
class AuthInterceptor
|
||||
class AdminInterceptor
|
||||
class MustChangePasswordInterceptor
|
||||
class MarkdownRenderer {
|
||||
+render(String) String
|
||||
}
|
||||
|
||||
AuthPageController --> ApiClient
|
||||
LearnController --> ApiClient
|
||||
StudentController --> ApiClient
|
||||
AdminController --> ApiClient
|
||||
ContentAdminController --> ApiClient
|
||||
LearnController --> MarkdownRenderer
|
||||
AuthPageController --> SessionUser
|
||||
AuthPageController --> SessionKeys
|
||||
CsrfInterceptor ..> SessionKeys
|
||||
AuthInterceptor ..> SessionUser
|
||||
AdminInterceptor ..> SessionUser
|
||||
MustChangePasswordInterceptor ..> SessionUser
|
||||
```
|
||||
|
||||
## 5. ER-модель БД
|
||||
|
||||
Схемы `identity`, `academic`, `progress` живут в одной БД `lms`.
|
||||
Между схемами внешних ключей нет: progress хранит `studentId`/`labId`/`groupId`
|
||||
как UUID и получает связанные данные по REST.
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
ACCOUNTS {
|
||||
uuid id PK
|
||||
varchar login UK
|
||||
varchar password_hash
|
||||
varchar role
|
||||
boolean must_change_password
|
||||
varchar temp_password
|
||||
timestamptz created_at
|
||||
timestamptz updated_at
|
||||
}
|
||||
STUDENTS {
|
||||
uuid account_id PK
|
||||
varchar student_no UK
|
||||
varchar last_name
|
||||
varchar first_name
|
||||
varchar middle_name
|
||||
varchar basis
|
||||
text note
|
||||
}
|
||||
GROUPS {
|
||||
uuid id PK
|
||||
varchar name UK
|
||||
int task_duration_min
|
||||
timestamptz created_at
|
||||
}
|
||||
ENROLLMENTS {
|
||||
uuid id PK
|
||||
uuid group_id FK
|
||||
uuid student_id FK
|
||||
timestamptz enrolled_at
|
||||
}
|
||||
SETTINGS {
|
||||
varchar key PK
|
||||
varchar value
|
||||
}
|
||||
|
||||
ACCOUNTS ||--|| STUDENTS : "account_id"
|
||||
GROUPS ||--o{ ENROLLMENTS : "group_id"
|
||||
STUDENTS ||--o{ ENROLLMENTS : "student_id"
|
||||
|
||||
SEMESTERS {
|
||||
uuid id PK
|
||||
varchar title
|
||||
int year
|
||||
boolean active
|
||||
}
|
||||
LABS {
|
||||
uuid id PK
|
||||
int number
|
||||
varchar title
|
||||
uuid semester_id FK
|
||||
text description_md
|
||||
timestamptz deadline_at
|
||||
timestamptz defense_at
|
||||
numeric points_max
|
||||
varchar status
|
||||
timestamptz created_at
|
||||
}
|
||||
LAB_LEVELS {
|
||||
uuid id PK
|
||||
uuid lab_id FK
|
||||
varchar code
|
||||
varchar title
|
||||
text description
|
||||
int position
|
||||
numeric points_max
|
||||
}
|
||||
VARIANTS {
|
||||
uuid id PK
|
||||
uuid lab_id FK
|
||||
uuid lab_level_id FK
|
||||
varchar number
|
||||
varchar title
|
||||
text statement_md
|
||||
int position
|
||||
}
|
||||
CHAPTERS {
|
||||
uuid id PK
|
||||
varchar title UK
|
||||
varchar slug UK
|
||||
int position
|
||||
boolean published
|
||||
}
|
||||
SECTIONS {
|
||||
uuid id PK
|
||||
uuid chapter_id FK
|
||||
varchar title
|
||||
varchar slug
|
||||
int position
|
||||
boolean published
|
||||
}
|
||||
ARTICLES {
|
||||
uuid id PK
|
||||
uuid section_id FK
|
||||
varchar title
|
||||
varchar slug
|
||||
text body_md
|
||||
int position
|
||||
boolean published
|
||||
}
|
||||
|
||||
SEMESTERS ||--o{ LABS : "semester_id"
|
||||
LABS ||--o{ LAB_LEVELS : "lab_id"
|
||||
LAB_LEVELS ||--o{ VARIANTS : "lab_level_id"
|
||||
CHAPTERS ||--o{ SECTIONS : "chapter_id"
|
||||
SECTIONS ||--o{ ARTICLES : "section_id"
|
||||
|
||||
VARIANT_ASSIGNMENTS {
|
||||
uuid id PK
|
||||
uuid student_id
|
||||
uuid lab_id
|
||||
uuid variant_id
|
||||
boolean manual
|
||||
timestamptz assigned_at
|
||||
}
|
||||
STUDENT_LAB_LEVELS {
|
||||
uuid student_id PK
|
||||
uuid lab_id PK
|
||||
uuid lab_level_id
|
||||
boolean manual
|
||||
}
|
||||
LAB_GRADES {
|
||||
uuid student_id PK
|
||||
uuid lab_id PK
|
||||
numeric score
|
||||
timestamptz updated_at
|
||||
}
|
||||
GRADE_PUBLICATIONS {
|
||||
uuid group_id PK
|
||||
uuid lab_id PK
|
||||
timestamptz published_at
|
||||
}
|
||||
GRADE_COMMENTS {
|
||||
uuid id PK
|
||||
uuid student_id
|
||||
uuid lab_id
|
||||
text text
|
||||
varchar tone
|
||||
timestamptz created_at
|
||||
}
|
||||
COMMENT_TEMPLATES {
|
||||
uuid id PK
|
||||
varchar text UK
|
||||
varchar tone
|
||||
int use_count
|
||||
}
|
||||
SUBMISSIONS {
|
||||
uuid id PK
|
||||
uuid student_id
|
||||
uuid lab_id
|
||||
int seq
|
||||
timestamptz created_at
|
||||
}
|
||||
DEFENSE_SESSIONS {
|
||||
uuid id PK
|
||||
uuid student_id
|
||||
uuid lab_id
|
||||
text assignment_md
|
||||
varchar status
|
||||
varchar code
|
||||
timestamptz started_at
|
||||
timestamptz ready_at
|
||||
varchar ready_reason
|
||||
}
|
||||
|
||||
VARIANT_ASSIGNMENTS }o--|| VARIANTS : "variant_id (не FK)"
|
||||
VARIANT_ASSIGNMENTS }o--|| LABS : "lab_id (не FK)"
|
||||
STUDENT_LAB_LEVELS }o--|| LABS : "lab_id (не FK)"
|
||||
LAB_GRADES }o--|| LABS : "lab_id (не FK)"
|
||||
GRADE_PUBLICATIONS }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)"
|
||||
```
|
||||
Reference in New Issue
Block a user