mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 06:46:24 +00:00
외부/내부 시퀀스 설계 일관성 개선 및 표준화
주요 변경사항:
[Critical]
- API 엔드포인트 통일: POST /api/minutes/{minutesId}/finalize
- 이벤트 이름 표준화: MinutesFinalized
[Warning]
- API Gateway 라우팅 규칙 문서화 (외부 시퀀스 7개 파일)
- 대시보드 API 경로 통일: GET /api/dashboard
- AI 제안 병합 프로세스 상세 문서화
- 회의록 확정 검증 로직 5단계 상세화
[Minor]
- Redis 캐시 TTL 명시 (7개 파일, TTL 정책 표준화)
- 대시보드 페이지네이션 파라미터 추가
- 에러 응답 포맷 표준화 (14개 에러 응답)
총 31개 파일 수정, 34건의 개선 사항 적용
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f86973c93b
commit
715add4dbc
1
claude/sample-logical-architecture.mmd
Normal file
1
claude/sample-logical-architecture.mmd
Normal file
@ -0,0 +1 @@
|
||||
404: Not Found
|
||||
@ -11,6 +11,8 @@
|
||||
- **외부시퀀스설계서에서 설계한 플로우와 일치**해야 함
|
||||
- UI/UX설계서의 '사용자 플로우'참조하여 설계
|
||||
- 마이크로서비스 내부의 처리 흐름을 표시
|
||||
- 요청/응답을 **한글로 표시**
|
||||
- Repository CRUD 처리를 한글로 설명하고 SQL은 사용하지 말것
|
||||
- **각 서비스-시나리오별로 분리하여 각각 작성**
|
||||
- 각 서비스별 주요 시나리오마다 독립적인 시퀀스 설계 수행
|
||||
- 프론트엔드와 백엔드 책임 분리: 프론트엔드에서 할 수 있는 것은 백엔드로 요청 안하게 함
|
||||
|
||||
@ -27,7 +27,7 @@ activate Service
|
||||
Service -> Repo: getFinalTranscript(meetingId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT content FROM ai_transcripts\nWHERE meeting_id = {meetingId}\nORDER BY created_at DESC LIMIT 1
|
||||
Repo -> DB: 최종 회의록 조회
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 최종 회의록 내용
|
||||
@ -41,7 +41,7 @@ Service -> Service: 참석자 정보 조회 준비
|
||||
Service -> Repo: getMeetingParticipants(meetingId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT participants FROM meeting_context
|
||||
Repo -> DB: 참석자 정보 조회
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 참석자 목록
|
||||
@ -126,7 +126,7 @@ loop 각 Todo 항목마다
|
||||
Service -> Repo: saveTodo(meetingId, todoData)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: INSERT INTO ai_extracted_todos
|
||||
Repo -> DB: Todo 정보 저장
|
||||
activate DB
|
||||
note right
|
||||
저장 데이터:
|
||||
@ -195,7 +195,7 @@ deactivate MeetingClient
|
||||
Service -> Repo: updateExtractionStatus(meetingId, "COMPLETED")
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: UPDATE ai_task_status\nSET status = 'COMPLETED',\ncompleted_at = NOW()
|
||||
Repo -> DB: 추출 상태 업데이트
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 업데이트 완료
|
||||
|
||||
@ -26,7 +26,7 @@ activate Service
|
||||
Service -> TranscriptRepo: getMeetingContext(meetingId)
|
||||
activate TranscriptRepo
|
||||
|
||||
TranscriptRepo -> DB: SELECT meeting_info, participants\nFROM meeting_context
|
||||
TranscriptRepo -> DB: 회의 맥락 조회\n(회의정보, 참석자)
|
||||
activate DB
|
||||
|
||||
DB --> TranscriptRepo: 회의 정보
|
||||
|
||||
@ -28,7 +28,7 @@ activate Service
|
||||
Service -> Repo: getTranscriptInfo(transcriptId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT content, meeting_id, created_at\nFROM ai_transcripts\nWHERE id = {transcriptId}
|
||||
Repo -> DB: 회의록 정보 조회
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 회의록 정보
|
||||
@ -153,7 +153,7 @@ loop 각 후보 회의록마다
|
||||
Service -> Repo: getTranscriptDetails(candidateId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT participants, keywords, created_at
|
||||
Repo -> DB: 회의록 상세 정보 조회
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 상세 정보
|
||||
@ -188,7 +188,7 @@ loop 선택된 상위 5개
|
||||
Service -> Repo: saveRelatedTranscript(transcriptId, relatedId, score, keywords)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: INSERT INTO related_transcripts
|
||||
Repo -> DB: 관련 회의록 연결 저장
|
||||
activate DB
|
||||
note right
|
||||
저장 데이터:
|
||||
|
||||
@ -26,7 +26,7 @@ activate Service
|
||||
Service -> TranscriptRepo: getMeetingContext(meetingId)
|
||||
activate TranscriptRepo
|
||||
|
||||
TranscriptRepo -> DB: SELECT meeting_info, agenda, participants\nFROM meeting_context
|
||||
TranscriptRepo -> DB: 회의 맥락 정보 조회\n(회의정보, 안건, 참석자)
|
||||
activate DB
|
||||
|
||||
DB --> TranscriptRepo: 회의 정보
|
||||
@ -38,7 +38,7 @@ deactivate TranscriptRepo
|
||||
Service -> TranscriptRepo: getPreviousDiscussions(meetingId)
|
||||
activate TranscriptRepo
|
||||
|
||||
TranscriptRepo -> DB: SELECT discussed_topics\nFROM ai_transcripts\nWHERE meeting_id = {meetingId}
|
||||
TranscriptRepo -> DB: 이미 논의한 주제 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
|
||||
DB --> TranscriptRepo: 이미 논의한 주제 목록
|
||||
|
||||
@ -33,7 +33,7 @@ activate Service
|
||||
Service -> Repo: getTermInfo(term)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT definition, category\nFROM term_dictionary\nWHERE term = {term}
|
||||
Repo -> DB: 용어 정보 조회\n(용어사전에서 정의 및 카테고리)
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 기본 용어 정의
|
||||
|
||||
@ -30,7 +30,7 @@ par "조직별 용어 사전"
|
||||
Service -> Repo: getOrganizationTerms(organizationId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT term, definition, category\nFROM term_dictionary\nWHERE org_id = {organizationId}
|
||||
Repo -> DB: 조직 전문용어 조회\n(조직ID 기준, 용어/정의/카테고리)
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 조직 전문용어 목록
|
||||
@ -43,7 +43,7 @@ else
|
||||
Service -> Repo: getIndustryTerms(industry)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT term, definition, category\nFROM standard_terms\nWHERE industry = {industry}
|
||||
Repo -> DB: 산업 표준용어 조회\n(산업분류 기준, 용어/정의/카테고리)
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 산업 표준용어 목록
|
||||
|
||||
@ -30,7 +30,7 @@ activate Service
|
||||
Service -> Repo: getOriginalTranscript(meetingId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: SELECT content, version\nFROM ai_transcripts\nWHERE meeting_id = {meetingId}\nAND status = 'FINALIZED'\nORDER BY created_at DESC LIMIT 1
|
||||
Repo -> DB: 원본 회의록 조회
|
||||
activate DB
|
||||
|
||||
DB --> Repo: 원본 회의록 내용
|
||||
@ -146,7 +146,7 @@ end note
|
||||
Service -> Repo: saveImprovedTranscript(meetingId, improvedContent, metadata)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: INSERT INTO ai_transcripts
|
||||
Repo -> DB: 개선된 회의록 저장
|
||||
activate DB
|
||||
note right
|
||||
저장 데이터:
|
||||
@ -172,7 +172,7 @@ deactivate Repo
|
||||
Service -> Repo: linkVersions(originalId, improvedId)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: INSERT INTO transcript_versions
|
||||
Repo -> DB: 버전 연결 정보 저장
|
||||
activate DB
|
||||
note right
|
||||
버전 연결 정보:
|
||||
|
||||
@ -30,7 +30,7 @@ Service -> Service: 회의 맥락 정보 조회 준비
|
||||
par 회의 정보 조회
|
||||
Service -> Repo: getMeetingContext(meetingId)
|
||||
activate Repo
|
||||
Repo -> DB: SELECT meeting_info
|
||||
Repo -> DB: 회의 맥락 정보 조회
|
||||
activate DB
|
||||
DB --> Repo: 회의 정보 반환
|
||||
deactivate DB
|
||||
@ -39,7 +39,7 @@ par 회의 정보 조회
|
||||
else 이전 내용 조회
|
||||
Service -> Repo: getPreviousTranscripts(meetingId)
|
||||
activate Repo
|
||||
Repo -> DB: SELECT previous_content
|
||||
Repo -> DB: 이전 회의록 내용 조회
|
||||
activate DB
|
||||
DB --> Repo: 이전 회의록
|
||||
deactivate DB
|
||||
@ -82,7 +82,7 @@ Service -> Service: 회의록 데이터 구조화
|
||||
Service -> Repo: saveTranscriptDraft(meetingId, content)
|
||||
activate Repo
|
||||
|
||||
Repo -> DB: INSERT INTO ai_transcripts
|
||||
Repo -> DB: AI 회의록 초안 저장 (상태: DRAFT)
|
||||
activate DB
|
||||
note right
|
||||
저장 데이터:
|
||||
@ -222,6 +222,30 @@ note right
|
||||
* decisions: []
|
||||
end note
|
||||
|
||||
note over Controller, DB
|
||||
AI 제안 병합 프로세스:
|
||||
|
||||
1. 자동 병합 (Auto-merge):
|
||||
- confidence >= 0.8인 결정사항
|
||||
→ 회의록 "결정사항" 섹션에 자동 추가
|
||||
- priority: HIGH인 논의사항
|
||||
→ "추가 논의 필요" 섹션에 자동 반영
|
||||
|
||||
2. 사용자 검토 (User Review):
|
||||
- 0.5 <= confidence < 0.8
|
||||
→ 제안 목록에 표시, 사용자 승인 대기
|
||||
- priority: MEDIUM/LOW
|
||||
→ 선택적 반영 가능
|
||||
|
||||
3. 병합 시점:
|
||||
- 실시간: 회의 진행 중 자동 반영
|
||||
- 최종: 회의록 확정 전 사용자 최종 검토
|
||||
|
||||
4. 병합 이력:
|
||||
- 제안 ID, 병합 여부, 승인자, 시각 기록
|
||||
- 감사 추적(Audit Trail) 유지
|
||||
end note
|
||||
|
||||
note over Controller, DB
|
||||
처리 시간:
|
||||
- 맥락 조회: 100-200ms
|
||||
|
||||
@ -29,7 +29,7 @@ activate Service
|
||||
' Todo 정보 조회
|
||||
Service -> TodoRepo: findById(todoId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: SELECT * FROM todos WHERE id = ?
|
||||
TodoRepo -> DB: Todo 정보 조회
|
||||
activate DB
|
||||
DB --> TodoRepo: Todo 정보
|
||||
deactivate DB
|
||||
@ -49,10 +49,34 @@ Service -> Service: 완료 권한 검증\n(담당자만 가능)
|
||||
|
||||
alt 권한 없음
|
||||
Service --> Controller: 403 Forbidden\n담당자만 완료 가능
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "INSUFFICIENT_PERMISSION",
|
||||
"message": "Todo 완료 권한이 없습니다",
|
||||
"details": "담당자만 Todo를 완료할 수 있습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/todos/{todoId}/complete"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 403 Forbidden
|
||||
else 권한 있음
|
||||
alt Todo가 이미 완료됨
|
||||
Service --> Controller: 409 Conflict\n이미 완료된 Todo
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "TODO_ALREADY_COMPLETED",
|
||||
"message": "이미 완료된 Todo입니다",
|
||||
"details": "해당 Todo는 이미 완료 처리되었습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/todos/{todoId}/complete"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 409 Conflict
|
||||
else 완료 처리 가능
|
||||
' 완료 확인 다이얼로그 (프론트엔드에서 처리됨)
|
||||
@ -60,7 +84,7 @@ else 권한 있음
|
||||
' Todo 완료 처리
|
||||
Service -> TodoRepo: markAsCompleted(todoId, userId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: UPDATE todos\nSET status = 'COMPLETED',\n completedAt = NOW(),\n completedBy = ?\nWHERE id = ?
|
||||
TodoRepo -> DB: Todo 완료 상태 업데이트
|
||||
activate DB
|
||||
DB --> TodoRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -77,7 +101,7 @@ else 권한 있음
|
||||
' 회의록 섹션 업데이트
|
||||
Service -> MinutesRepo: updateTodoStatus(todoId, "COMPLETED")
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes_sections\nSET todoStatuses = JSON_SET(todoStatuses,\n CONCAT('$."', ?, '"'),\n JSON_OBJECT(\n 'status', 'COMPLETED',\n 'completedAt', NOW(),\n 'completedBy', ?\n )\n)\nWHERE JSON_CONTAINS(todoIds, CAST(? AS JSON))
|
||||
MinutesRepo -> DB: 회의록 섹션의 Todo 상태 업데이트
|
||||
activate DB
|
||||
DB --> MinutesRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -87,7 +111,7 @@ else 권한 있음
|
||||
' 회의록의 모든 Todo 완료 여부 확인
|
||||
Service -> TodoRepo: countPendingTodos(minutesId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: SELECT COUNT(*) FROM todos\nWHERE minutesId = ?\nAND status != 'COMPLETED'
|
||||
TodoRepo -> DB: 미완료 Todo 개수 조회
|
||||
activate DB
|
||||
DB --> TodoRepo: 미완료 Todo 개수
|
||||
deactivate DB
|
||||
|
||||
@ -44,7 +44,7 @@ end note
|
||||
' 회의록 존재 확인
|
||||
Service -> MinutesRepo: findById(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 정보 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 정보
|
||||
deactivate DB
|
||||
@ -58,7 +58,7 @@ Service -> Service: Todo 엔티티 생성\n- todoId (UUID)\n- 상태: IN_PROGRES
|
||||
|
||||
Service -> TodoRepo: save(todo)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: INSERT INTO todos\n(id, content, assignee, dueDate,\npriority, status, minutesId, sectionId,\ncreatedBy, createdAt)
|
||||
TodoRepo -> DB: Todo 정보 저장
|
||||
activate DB
|
||||
DB --> TodoRepo: Todo 저장 완료
|
||||
deactivate DB
|
||||
@ -74,7 +74,7 @@ end note
|
||||
' 회의록 섹션에 Todo 연결
|
||||
Service -> MinutesRepo: linkTodoToSection(sectionId, todoId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes_sections\nSET todoIds = JSON_ARRAY_APPEND(todoIds, '$', ?)\nWHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 섹션에 Todo 연결
|
||||
activate DB
|
||||
DB --> MinutesRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -100,7 +100,7 @@ alt 마감일 설정됨
|
||||
|
||||
Service -> TodoRepo: updateCalendarEventId(todoId, eventId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: UPDATE todos\nSET calendarEventId = ?\nWHERE id = ?
|
||||
TodoRepo -> DB: 캘린더 이벤트 ID 업데이트
|
||||
activate DB
|
||||
DB --> TodoRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -21,7 +21,7 @@ activate Service
|
||||
|
||||
Service -> Repository: findById(meetingId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM meetings WHERE id = ?
|
||||
Repository -> DB: 회의 정보 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
DB --> Repository: meeting_row
|
||||
deactivate DB
|
||||
@ -67,15 +67,20 @@ alt validation passed
|
||||
|
||||
Service -> Repository: save(meeting)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE meetings SET status = 'VALIDATED'
|
||||
Repository -> DB: 회의 상태 업데이트\n(상태='검증완료')
|
||||
activate DB
|
||||
DB --> Repository: affected_rows
|
||||
deactivate DB
|
||||
Repository --> Service: savedMeeting
|
||||
deactivate Repository
|
||||
|
||||
Service -> Cache: set(meeting:{id}, meetingData)
|
||||
Service -> Cache: SET meeting:{id}\n(TTL: 10분)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
회의 정보 캐싱:
|
||||
- TTL: 10분
|
||||
- 자동 만료
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
@ -89,7 +94,19 @@ alt validation passed
|
||||
|
||||
Service --> Controller: success response
|
||||
else validation failed
|
||||
Service --> Controller: error response with details
|
||||
Service --> Controller: 400 Bad Request
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_FAILED",
|
||||
"message": "회의록 검증에 실패했습니다",
|
||||
"details": "필수 항목 누락 또는 형식 오류",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/meetings/{id}/validate"
|
||||
}
|
||||
}
|
||||
end note
|
||||
end
|
||||
|
||||
deactivate Service
|
||||
|
||||
@ -34,7 +34,7 @@ else Cache Miss
|
||||
' 예정된 회의 조회
|
||||
Service -> MeetingRepo: findUpcomingMeetings(userId)
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: SELECT meetings\nWHERE userId = ? AND status = 'SCHEDULED'\nAND startTime > NOW()\nORDER BY startTime LIMIT 3
|
||||
MeetingRepo -> DB: 예정된 회의 조회
|
||||
activate DB
|
||||
DB --> MeetingRepo: 예정된 회의 목록
|
||||
deactivate DB
|
||||
@ -44,7 +44,7 @@ else Cache Miss
|
||||
' 진행 중 Todo 조회
|
||||
Service -> TodoRepo: findActiveTodos(userId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: SELECT todos\nWHERE assignee = ? AND status = 'IN_PROGRESS'\nORDER BY dueDate LIMIT 3
|
||||
TodoRepo -> DB: 진행 중 Todo 조회
|
||||
activate DB
|
||||
DB --> TodoRepo: 진행 중 Todo 목록
|
||||
deactivate DB
|
||||
@ -54,7 +54,7 @@ else Cache Miss
|
||||
' 최근 회의록 조회
|
||||
Service -> MinutesRepo: findRecentMinutes(userId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT minutes\nWHERE creatorId = ?\nORDER BY createdAt DESC LIMIT 3
|
||||
MinutesRepo -> DB: 최근 회의록 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 최근 회의록 목록
|
||||
deactivate DB
|
||||
@ -64,7 +64,7 @@ else Cache Miss
|
||||
' 공유받은 회의록 조회
|
||||
Service -> MinutesRepo: findSharedMinutes(userId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT minutes m JOIN shared_minutes sm\nON m.id = sm.minutesId\nWHERE sm.sharedWith = ?\nORDER BY sm.sharedAt DESC LIMIT 3
|
||||
MinutesRepo -> DB: 공유받은 회의록 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 공유받은 회의록 목록
|
||||
deactivate DB
|
||||
@ -74,7 +74,7 @@ else Cache Miss
|
||||
' 통계 정보 조회
|
||||
Service -> MeetingRepo: countUpcomingMeetings(userId)
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: SELECT COUNT(*)\nFROM meetings\nWHERE userId = ? AND status = 'SCHEDULED'
|
||||
MeetingRepo -> DB: 예정된 회의 개수 조회
|
||||
activate DB
|
||||
DB --> MeetingRepo: 예정된 회의 개수
|
||||
deactivate DB
|
||||
@ -83,7 +83,7 @@ else Cache Miss
|
||||
|
||||
Service -> TodoRepo: countActiveTodos(userId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: SELECT COUNT(*)\nFROM todos\nWHERE assignee = ? AND status = 'IN_PROGRESS'
|
||||
TodoRepo -> DB: 진행 중 Todo 개수 조회
|
||||
activate DB
|
||||
DB --> TodoRepo: 진행 중 Todo 개수
|
||||
deactivate DB
|
||||
@ -92,7 +92,7 @@ else Cache Miss
|
||||
|
||||
Service -> TodoRepo: calculateTodoCompletionRate(userId)
|
||||
activate TodoRepo
|
||||
TodoRepo -> DB: SELECT\n (COUNT(CASE WHEN status='COMPLETED' THEN 1 END) * 100.0 /\n COUNT(*)) as rate\nFROM todos WHERE assignee = ?
|
||||
TodoRepo -> DB: Todo 완료율 조회
|
||||
activate DB
|
||||
DB --> TodoRepo: Todo 완료율
|
||||
deactivate DB
|
||||
|
||||
@ -53,11 +53,13 @@ end note
|
||||
TranscriptService --> Service: updatedVersion
|
||||
deactivate TranscriptService
|
||||
|
||||
Service -> Cache: set(meeting:{id}:version, versionData)
|
||||
Service -> Cache: SET meeting:{id}:version\n(TTL: 1시간)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
버전 정보 업데이트
|
||||
최신 상태 유지
|
||||
세션 버전 정보 캐싱:
|
||||
- TTL: 1시간
|
||||
- 버전 정보 업데이트
|
||||
- 최신 상태 유지
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
title 최종 회의록 확정 내부 시퀀스
|
||||
|
||||
participant "API Gateway<<E>>" as Gateway
|
||||
participant "MeetingController" as Controller
|
||||
participant "MinutesController" as Controller
|
||||
participant "MeetingService" as Service
|
||||
participant "Meeting" as Domain
|
||||
participant "TranscriptService" as TranscriptService
|
||||
@ -13,7 +13,7 @@ database "PostgreSQL<<E>>" as DB
|
||||
database "Redis Cache<<E>>" as Cache
|
||||
queue "Event Hub<<E>>" as EventHub
|
||||
|
||||
Gateway -> Controller: POST /api/meetings/{id}/confirm
|
||||
Gateway -> Controller: POST /api/minutes/{minutesId}/finalize
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: confirmTranscript(meetingId)
|
||||
@ -21,7 +21,7 @@ activate Service
|
||||
|
||||
Service -> Repository: findById(meetingId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM meetings WHERE id = ?
|
||||
Repository -> DB: 회의 정보 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
DB --> Repository: meeting_row
|
||||
deactivate DB
|
||||
@ -33,10 +33,37 @@ activate Domain
|
||||
|
||||
Domain -> Domain: validateCanConfirm()
|
||||
note right of Domain
|
||||
도메인 규칙:
|
||||
- COMPLETED 상태 검증
|
||||
- 작성자 권한 검증
|
||||
- 회의록 존재 여부 확인
|
||||
회의록 확정 검증 규칙:
|
||||
|
||||
1. 상태 검증:
|
||||
- 회의 상태 = COMPLETED (종료됨)
|
||||
- 회의록 상태 = DRAFT (작성중)
|
||||
✗ IN_PROGRESS → 400 Bad Request
|
||||
|
||||
2. 권한 검증:
|
||||
- 확정 요청자 = 회의 생성자 OR
|
||||
- 확정 요청자 ∈ 참석자 목록
|
||||
✗ 권한 없음 → 403 Forbidden
|
||||
|
||||
3. 필수 항목 검증:
|
||||
- 회의록 제목: NOT NULL, 길이 >= 5
|
||||
- 참석자 목록: 최소 1명 이상
|
||||
- 주요 논의 내용: NOT NULL, 길이 >= 20
|
||||
- 결정 사항: 최소 1개 이상 OR
|
||||
"결정사항 없음" 명시적 표시
|
||||
✗ 누락 → 400 Bad Request
|
||||
(누락 항목 목록 반환)
|
||||
|
||||
4. 데이터 무결성:
|
||||
- 섹션별 내용 완결성 확인
|
||||
- 참조 링크 유효성 확인
|
||||
- 첨부 파일 접근 가능 여부
|
||||
✗ 무결성 위반 → 400 Bad Request
|
||||
|
||||
5. 이력 검증:
|
||||
- 마지막 수정 후 24시간 경과 경고
|
||||
- 미승인 AI 제안 존재 시 알림
|
||||
⚠️ 경고만 표시, 진행 가능
|
||||
end note
|
||||
|
||||
Domain -> Domain: changeStatus(CONFIRMED)
|
||||
@ -56,19 +83,24 @@ deactivate TranscriptService
|
||||
|
||||
Service -> Repository: save(meeting)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE meetings SET status = 'CONFIRMED', confirmed_at = ?
|
||||
Repository -> DB: 회의 상태 업데이트\n(상태='확정', 확정일시)
|
||||
activate DB
|
||||
DB --> Repository: affected_rows
|
||||
deactivate DB
|
||||
Repository --> Service: savedMeeting
|
||||
deactivate Repository
|
||||
|
||||
Service -> Cache: set(meeting:{id}, meetingData)
|
||||
Service -> Cache: SET meeting:{id}\n(TTL: 10분)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
회의 정보 캐싱:
|
||||
- TTL: 10분
|
||||
- 자동 만료
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
Service ->> EventHub: publish(TranscriptConfirmedEvent)
|
||||
Service ->> EventHub: publish(MinutesFinalized)
|
||||
activate EventHub
|
||||
note right of EventHub
|
||||
비동기 이벤트:
|
||||
|
||||
@ -62,11 +62,13 @@ alt auto-resolved
|
||||
deactivate Cache
|
||||
|
||||
else manual-required
|
||||
Service -> Cache: set(meeting:{id}:conflicts, conflictData)
|
||||
Service -> Cache: SET meeting:{id}:conflicts\n(TTL: 1시간)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
충돌 정보 저장
|
||||
수동 해결 대기
|
||||
충돌 정보 캐싱:
|
||||
- TTL: 1시간
|
||||
- 충돌 정보 저장
|
||||
- 수동 해결 대기
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
@ -25,7 +25,7 @@ deactivate Cache
|
||||
|
||||
Service -> Repository: findById(meetingId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM meetings WHERE id = ?
|
||||
Repository -> DB: 회의 정보 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
DB --> Repository: meeting_row
|
||||
deactivate DB
|
||||
@ -56,15 +56,20 @@ deactivate Domain
|
||||
|
||||
Service -> Repository: save(meeting)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE meetings SET template_id = ?
|
||||
Repository -> DB: 회의 템플릿 업데이트\n(템플릿ID)
|
||||
activate DB
|
||||
DB --> Repository: affected_rows
|
||||
deactivate DB
|
||||
Repository --> Service: savedMeeting
|
||||
deactivate Repository
|
||||
|
||||
Service -> Cache: set(meeting:{id}, meetingData)
|
||||
Service -> Cache: SET meeting:{id}\n(TTL: 10분)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
회의 정보 캐싱:
|
||||
- TTL: 10분
|
||||
- 자동 만료
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
|
||||
@ -39,7 +39,7 @@ activate Service
|
||||
' 회의록 정보 조회
|
||||
Service -> MinutesRepo: findById(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 정보 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 정보
|
||||
deactivate DB
|
||||
@ -61,6 +61,18 @@ Service -> Service: 회의록 상태 확인
|
||||
|
||||
alt 회의록이 확정되지 않음
|
||||
Service --> Controller: 400 Bad Request\n확정된 회의록만 공유 가능
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MINUTES_NOT_FINALIZED",
|
||||
"message": "확정된 회의록만 공유 가능합니다",
|
||||
"details": "회의록을 먼저 확정해 주세요",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}/share"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 400 Bad Request
|
||||
else 공유 가능
|
||||
' 공유 링크 생성
|
||||
@ -68,7 +80,7 @@ else 공유 가능
|
||||
|
||||
Service -> ShareRepo: createShareLink(minutesId, token, options)
|
||||
activate ShareRepo
|
||||
ShareRepo -> DB: INSERT INTO share_links\n(minutesId, token, permission,\nexpiresAt, requirePassword, passwordHash,\ncreatedBy, createdAt)
|
||||
ShareRepo -> DB: 공유 링크 정보 저장
|
||||
activate DB
|
||||
DB --> ShareRepo: 링크 생성 완료
|
||||
deactivate DB
|
||||
@ -79,7 +91,7 @@ else 공유 가능
|
||||
loop 각 공유 대상마다
|
||||
Service -> ShareRepo: addSharedUser(minutesId, userEmail, permission)
|
||||
activate ShareRepo
|
||||
ShareRepo -> DB: INSERT INTO shared_minutes\n(minutesId, sharedWith, permission,\nsharedBy, sharedAt)
|
||||
ShareRepo -> DB: 공유 대상 정보 저장
|
||||
activate DB
|
||||
DB --> ShareRepo: 공유 정보 저장 완료
|
||||
deactivate DB
|
||||
|
||||
@ -36,7 +36,7 @@ deactivate Cache
|
||||
Service -> Repository: findAll(specification, pageable)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: SELECT * FROM meetings WHERE ... ORDER BY ... LIMIT ? OFFSET ?
|
||||
Repository -> DB: 회의록 목록 조회\n(필터 조건, 정렬, 페이징)
|
||||
activate DB
|
||||
DB --> Repository: meeting_rows
|
||||
deactivate DB
|
||||
@ -51,8 +51,13 @@ note right of Service
|
||||
- 민감 정보 필터링
|
||||
end note
|
||||
|
||||
Service -> Cache: set(meetings:list:{hash}, responseData)
|
||||
Service -> Cache: SET meetings:list:{hash}\n(TTL: 5분)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
목록 데이터 캐싱:
|
||||
- TTL: 5분
|
||||
- 자동 만료
|
||||
end note
|
||||
Cache --> Service: OK
|
||||
deactivate Cache
|
||||
|
||||
|
||||
@ -35,6 +35,18 @@ alt Cache Hit
|
||||
|
||||
alt 권한 없음
|
||||
Service --> Controller: 403 Forbidden\n조회 권한 없음
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "ACCESS_DENIED",
|
||||
"message": "조회 권한이 없습니다",
|
||||
"details": "회의록 조회 권한이 없는 사용자입니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 403 Forbidden
|
||||
else 권한 있음
|
||||
Service --> Controller: MinutesDetailResponse\n(캐시 데이터)
|
||||
@ -44,7 +56,7 @@ else Cache Miss
|
||||
' 회의록 기본 정보 조회
|
||||
Service -> MinutesRepo: findByIdWithMeeting(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT m.*, mt.*\nFROM minutes m\nJOIN meetings mt ON m.meetingId = mt.id\nWHERE m.id = ?
|
||||
MinutesRepo -> DB: 회의록 및 회의 정보 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 및 회의 정보
|
||||
deactivate DB
|
||||
@ -65,7 +77,7 @@ else Cache Miss
|
||||
' 권한 확인
|
||||
Service -> MinutesRepo: hasAccessPermission(minutesId, userId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT COUNT(*) FROM (\n SELECT 1 FROM minutes WHERE id = ? AND creatorId = ?\n UNION\n SELECT 1 FROM participants p JOIN meetings m\n ON p.meetingId = m.id JOIN minutes mi\n ON mi.meetingId = m.id\n WHERE mi.id = ? AND p.userId = ?\n UNION\n SELECT 1 FROM shared_minutes\n WHERE minutesId = ? AND sharedWith = ?\n) AS access
|
||||
MinutesRepo -> DB: 회의록 조회 권한 확인
|
||||
activate DB
|
||||
DB --> MinutesRepo: 권한 확인 결과
|
||||
deactivate DB
|
||||
@ -74,12 +86,24 @@ else Cache Miss
|
||||
|
||||
alt 권한 없음
|
||||
Service --> Controller: 403 Forbidden\n조회 권한 없음
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "ACCESS_DENIED",
|
||||
"message": "조회 권한이 없습니다",
|
||||
"details": "회의록 조회 권한이 없는 사용자입니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 403 Forbidden
|
||||
else 권한 있음
|
||||
' 참석자 목록 조회
|
||||
Service -> MinutesRepo: findParticipants(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT p.*, u.name, u.email\nFROM participants p\nJOIN meetings m ON p.meetingId = m.id\nJOIN minutes mi ON mi.meetingId = m.id\nJOIN users u ON p.userId = u.id\nWHERE mi.id = ?
|
||||
MinutesRepo -> DB: 참석자 목록 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 참석자 목록
|
||||
deactivate DB
|
||||
@ -89,7 +113,7 @@ else Cache Miss
|
||||
' 섹션별 상세 내용 조회
|
||||
Service -> SectionRepo: findSectionsByMinutesId(minutesId)
|
||||
activate SectionRepo
|
||||
SectionRepo -> DB: SELECT * FROM minutes_sections\nWHERE minutesId = ?\nORDER BY sectionOrder
|
||||
SectionRepo -> DB: 섹션 목록 조회
|
||||
activate DB
|
||||
DB --> SectionRepo: 섹션 목록
|
||||
deactivate DB
|
||||
@ -99,7 +123,7 @@ else Cache Miss
|
||||
' AI 요약 정보 조회
|
||||
Service -> SectionRepo: findAISummaries(minutesId)
|
||||
activate SectionRepo
|
||||
SectionRepo -> DB: SELECT * FROM ai_summaries\nWHERE minutesId = ?\nORDER BY sectionId
|
||||
SectionRepo -> DB: AI 요약 정보 조회
|
||||
activate DB
|
||||
DB --> SectionRepo: AI 요약 목록
|
||||
deactivate DB
|
||||
@ -117,7 +141,7 @@ else Cache Miss
|
||||
- 최대 3개
|
||||
end note
|
||||
|
||||
RelatedService -> DB: SELECT * FROM related_minutes\nWHERE minutesId = ?\nAND similarityScore >= 0.7\nORDER BY similarityScore DESC\nLIMIT 3
|
||||
RelatedService -> DB: 관련 회의록 조회
|
||||
activate DB
|
||||
DB --> RelatedService: 관련 회의록 목록
|
||||
deactivate DB
|
||||
@ -127,7 +151,7 @@ else Cache Miss
|
||||
' 조회 이력 기록
|
||||
Service -> MinutesRepo: recordViewHistory(minutesId, userId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: INSERT INTO view_history\n(minutesId, userId, viewedAt)\nVALUES (?, ?, NOW())
|
||||
MinutesRepo -> DB: 조회 이력 기록
|
||||
activate DB
|
||||
DB --> MinutesRepo: 기록 완료
|
||||
deactivate DB
|
||||
|
||||
@ -40,7 +40,7 @@ activate Service
|
||||
' 회의록 정보 조회
|
||||
Service -> MinutesRepo: findById(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 정보 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 정보
|
||||
deactivate DB
|
||||
@ -61,6 +61,18 @@ Service -> Service: 수정 권한 검증\n(생성자만 가능)
|
||||
|
||||
alt 권한 없음
|
||||
Service --> Controller: 403 Forbidden\n수정 권한 없음
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "ACCESS_DENIED",
|
||||
"message": "회의록 수정 권한이 없습니다",
|
||||
"details": "생성자만 회의록을 수정할 수 있습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 403 Forbidden
|
||||
else 권한 있음
|
||||
' 잠긴 섹션 확인
|
||||
@ -77,12 +89,24 @@ else 권한 있음
|
||||
|
||||
alt 잠긴 섹션 수정 시도
|
||||
Service --> Controller: 400 Bad Request\n잠긴 섹션은 수정 불가
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "SECTION_LOCKED",
|
||||
"message": "잠긴 섹션은 수정할 수 없습니다",
|
||||
"details": "해당 섹션은 잠금 상태로 수정이 제한됩니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 400 Bad Request
|
||||
else 수정 가능
|
||||
' 수정 이력 저장 (버전 관리)
|
||||
Service -> VersionService: createModificationHistory(minutesId, userId, changes)
|
||||
activate VersionService
|
||||
VersionService -> DB: INSERT INTO modification_history\n(minutesId, modifiedBy, modifiedAt,\nchangeType, oldValue, newValue)
|
||||
VersionService -> DB: 수정 이력 저장
|
||||
activate DB
|
||||
DB --> VersionService: 이력 저장 완료
|
||||
deactivate DB
|
||||
@ -93,7 +117,7 @@ else 권한 있음
|
||||
alt 제목 변경됨
|
||||
Service -> MinutesRepo: updateTitle(minutesId, newTitle)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes\nSET title = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 제목 수정
|
||||
activate DB
|
||||
DB --> MinutesRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -105,7 +129,7 @@ else 권한 있음
|
||||
loop 각 섹션마다
|
||||
Service -> SectionRepo: updateSection(sectionId, content, aiSummary)
|
||||
activate SectionRepo
|
||||
SectionRepo -> DB: UPDATE minutes_sections\nSET content = ?,\n aiSummary = ?,\n updatedAt = NOW(),\n updatedBy = ?\nWHERE id = ?
|
||||
SectionRepo -> DB: 섹션 내용 및 AI 요약 수정
|
||||
activate DB
|
||||
DB --> SectionRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -117,7 +141,7 @@ else 권한 있음
|
||||
alt 확정된 회의록 수정
|
||||
Service -> MinutesRepo: updateStatus(minutesId, "DRAFT")
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes\nSET status = 'DRAFT'\nWHERE id = ?\nAND status = 'FINALIZED'
|
||||
MinutesRepo -> DB: 확정된 회의록을 다시 작성 중(DRAFT)으로 변경
|
||||
activate DB
|
||||
DB --> MinutesRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -28,7 +28,7 @@ activate Service
|
||||
' 회의록 정보 조회
|
||||
Service -> MinutesRepo: findById(minutesId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT * FROM minutes WHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 정보 조회
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 정보
|
||||
deactivate DB
|
||||
@ -49,6 +49,18 @@ Service -> Service: 회의록 상태 확인
|
||||
|
||||
alt 회의록이 이미 확정됨
|
||||
Service --> Controller: 409 Conflict\n이미 확정된 회의록
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MINUTES_ALREADY_FINALIZED",
|
||||
"message": "이미 확정된 회의록입니다",
|
||||
"details": "확정된 회의록은 다시 확정할 수 없습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}/finalize"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 409 Conflict
|
||||
else 확정 가능
|
||||
note over Service
|
||||
@ -71,6 +83,18 @@ else 확정 가능
|
||||
alt 필수 항목 누락
|
||||
ValidationService --> Service: ValidationException\n누락된 항목 목록
|
||||
Service --> Controller: 400 Bad Request\n필수 항목 누락
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MISSING_REQUIRED_FIELDS",
|
||||
"message": "필수 항목이 누락되었습니다",
|
||||
"details": "회의 제목, 참석자 목록, 논의 내용 중 일부가 누락되었습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/minutes/{minutesId}/finalize"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 400 Bad Request
|
||||
else 검증 통과
|
||||
ValidationService --> Service: 검증 성공
|
||||
@ -82,7 +106,7 @@ else 확정 가능
|
||||
' 회의록 상태 업데이트
|
||||
Service -> MinutesRepo: finalize(minutesId, version)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes\nSET status = 'FINALIZED',\n version = ?,\n finalizedAt = NOW(),\n finalizedBy = ?\nWHERE id = ?
|
||||
MinutesRepo -> DB: 회의록 확정 상태 업데이트
|
||||
activate DB
|
||||
DB --> MinutesRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -92,7 +116,7 @@ else 확정 가능
|
||||
' 회의록 스냅샷 저장 (버전 관리)
|
||||
Service -> MinutesRepo: saveSnapshot(minutesId, version, content)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: INSERT INTO minutes_snapshots\n(minutesId, version, content, createdAt)
|
||||
MinutesRepo -> DB: 회의록 스냅샷 저장
|
||||
activate DB
|
||||
DB --> MinutesRepo: 스냅샷 저장 완료
|
||||
deactivate DB
|
||||
|
||||
@ -34,15 +34,20 @@ deactivate Cache
|
||||
alt Cache Miss
|
||||
Service -> MeetingRepo: findById(meetingId)
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: SELECT * FROM meetings\nWHERE id = ?
|
||||
MeetingRepo -> DB: 회의 정보 조회
|
||||
activate DB
|
||||
DB --> MeetingRepo: 회의 정보
|
||||
deactivate DB
|
||||
MeetingRepo --> Service: Meeting
|
||||
deactivate MeetingRepo
|
||||
|
||||
Service -> Cache: SET meeting:info:{meetingId}
|
||||
Service -> Cache: SET meeting:info:{meetingId}\n(TTL: 10분)
|
||||
activate Cache
|
||||
note right of Cache
|
||||
회의 정보 캐싱:
|
||||
- TTL: 10분
|
||||
- 자동 만료
|
||||
end note
|
||||
Cache --> Service: 캐싱 완료
|
||||
deactivate Cache
|
||||
end
|
||||
@ -61,6 +66,18 @@ Service -> Service: 회의 상태 확인
|
||||
|
||||
alt 회의가 이미 진행 중
|
||||
Service --> Controller: 409 Conflict\n이미 진행 중인 회의
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MEETING_ALREADY_IN_PROGRESS",
|
||||
"message": "이미 진행 중인 회의입니다",
|
||||
"details": "해당 회의는 현재 진행 중 상태입니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/meetings/{meetingId}/start"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 409 Conflict
|
||||
else 시작 가능
|
||||
Service -> Service: 회의 세션 생성
|
||||
@ -78,7 +95,7 @@ else 시작 가능
|
||||
- status: ACTIVE
|
||||
end note
|
||||
|
||||
SessionRepo -> DB: INSERT INTO meeting_sessions\n(id, meetingId, startedBy, startedAt, status)
|
||||
SessionRepo -> DB: 회의 세션 생성
|
||||
activate DB
|
||||
DB --> SessionRepo: 세션 생성 완료
|
||||
deactivate DB
|
||||
@ -88,7 +105,7 @@ else 시작 가능
|
||||
' 회의 상태 업데이트
|
||||
Service -> MeetingRepo: updateStatus(meetingId, "IN_PROGRESS")
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: UPDATE meetings\nSET status = 'IN_PROGRESS',\n actualStartTime = NOW()\nWHERE id = ?
|
||||
MeetingRepo -> DB: 회의 상태를 'IN_PROGRESS'로 변경하고 실제 시작 시간 기록
|
||||
activate DB
|
||||
DB --> MeetingRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -106,7 +123,7 @@ else 시작 가능
|
||||
|
||||
Service -> MeetingRepo: createMinutesDraft(meetingId, sessionId)
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: INSERT INTO minutes\n(id, meetingId, sessionId, status, createdAt)\nVALUES (?, ?, ?, 'DRAFT', NOW())
|
||||
MeetingRepo -> DB: 회의록 초안 생성 (상태: DRAFT)
|
||||
activate DB
|
||||
DB --> MeetingRepo: 회의록 생성 완료
|
||||
deactivate DB
|
||||
|
||||
@ -42,7 +42,7 @@ Service -> Service: 회의 시간 유효성 검사
|
||||
|
||||
Service -> Repo: checkConflictingMeetings(userId, startTime, endTime)
|
||||
activate Repo
|
||||
Repo -> DB: SELECT COUNT(*) FROM meetings\nWHERE creatorId = ?\nAND status = 'SCHEDULED'\nAND (startTime <= ? AND endTime >= ?)
|
||||
Repo -> DB: 중복 회의 확인
|
||||
activate DB
|
||||
DB --> Repo: 중복 회의 개수
|
||||
deactivate DB
|
||||
@ -51,6 +51,18 @@ deactivate Repo
|
||||
|
||||
alt 중복 회의 존재
|
||||
Service --> Controller: 409 Conflict\n중복된 회의 시간
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MEETING_TIME_CONFLICT",
|
||||
"message": "중복된 회의 시간이 존재합니다",
|
||||
"details": "해당 시간대에 이미 예약된 회의가 있습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/meetings"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 409 Conflict
|
||||
else 중복 없음
|
||||
Service -> Service: Meeting 엔티티 생성\n- 회의 ID 생성\n- 상태: SCHEDULED\n- 생성자 정보 설정
|
||||
@ -58,7 +70,7 @@ else 중복 없음
|
||||
' 회의 정보 저장
|
||||
Service -> Repo: save(meeting)
|
||||
activate Repo
|
||||
Repo -> DB: INSERT INTO meetings\n(id, title, startTime, endTime,\nlocation, status, creatorId, createdAt)
|
||||
Repo -> DB: 회의 정보 저장
|
||||
activate DB
|
||||
DB --> Repo: 회의 저장 완료
|
||||
deactivate DB
|
||||
@ -68,7 +80,7 @@ else 중복 없음
|
||||
' 참석자 저장
|
||||
Service -> ParticipantRepo: saveAll(participants)
|
||||
activate ParticipantRepo
|
||||
ParticipantRepo -> DB: INSERT INTO participants\n(meetingId, email, role, status)
|
||||
ParticipantRepo -> DB: 참석자 정보 저장
|
||||
activate DB
|
||||
DB --> ParticipantRepo: 참석자 저장 완료
|
||||
deactivate DB
|
||||
|
||||
@ -33,7 +33,7 @@ activate Service
|
||||
' 회의 정보 조회
|
||||
Service -> MeetingRepo: findById(meetingId)
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: SELECT * FROM meetings WHERE id = ?
|
||||
MeetingRepo -> DB: 회의 정보 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
DB --> MeetingRepo: 회의 정보
|
||||
deactivate DB
|
||||
@ -53,12 +53,24 @@ Service -> Service: 회의 상태 확인
|
||||
|
||||
alt 회의가 진행 중이 아님
|
||||
Service --> Controller: 409 Conflict\n진행 중인 회의가 아님
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "MEETING_NOT_IN_PROGRESS",
|
||||
"message": "진행 중인 회의가 아닙니다",
|
||||
"details": "회의 종료는 진행 중 상태에서만 가능합니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/meetings/{meetingId}/end"
|
||||
}
|
||||
}
|
||||
end note
|
||||
return 409 Conflict
|
||||
else 종료 가능
|
||||
' 세션 정보 조회
|
||||
Service -> SessionRepo: findActiveSession(meetingId)
|
||||
activate SessionRepo
|
||||
SessionRepo -> DB: SELECT * FROM meeting_sessions\nWHERE meetingId = ?\nAND status = 'ACTIVE'
|
||||
SessionRepo -> DB: 활성 세션 조회\n(회의ID, 상태='진행중')
|
||||
activate DB
|
||||
DB --> SessionRepo: 세션 정보
|
||||
deactivate DB
|
||||
@ -68,7 +80,7 @@ else 종료 가능
|
||||
' 세션 종료
|
||||
Service -> SessionRepo: endSession(sessionId)
|
||||
activate SessionRepo
|
||||
SessionRepo -> DB: UPDATE meeting_sessions\nSET status = 'ENDED',\n endedAt = NOW()\nWHERE id = ?
|
||||
SessionRepo -> DB: 세션 종료 처리\n(상태='종료', 종료일시)
|
||||
activate DB
|
||||
DB --> SessionRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -78,7 +90,7 @@ else 종료 가능
|
||||
' 회의 상태 업데이트
|
||||
Service -> MeetingRepo: updateStatus(meetingId, "ENDED")
|
||||
activate MeetingRepo
|
||||
MeetingRepo -> DB: UPDATE meetings\nSET status = 'ENDED',\n actualEndTime = NOW()\nWHERE id = ?
|
||||
MeetingRepo -> DB: 회의 상태 업데이트\n(상태='종료', 실제종료시각)
|
||||
activate DB
|
||||
DB --> MeetingRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -97,12 +109,12 @@ else 종료 가능
|
||||
Service -> StatService: generateMeetingStatistics(sessionId)
|
||||
activate StatService
|
||||
|
||||
StatService -> DB: SELECT\n COUNT(DISTINCT speakerId) as speakerCount,\n COUNT(*) as utteranceCount,\n TIMESTAMPDIFF(MINUTE, startedAt, endedAt) as duration\nFROM transcripts WHERE sessionId = ?
|
||||
StatService -> DB: 회의 통계 데이터 조회\n(화자수, 발언횟수, 진행시간)
|
||||
activate DB
|
||||
DB --> StatService: 통계 데이터
|
||||
deactivate DB
|
||||
|
||||
StatService -> DB: INSERT INTO meeting_statistics\n(meetingId, sessionId, duration,\nparticipantCount, utteranceCount, createdAt)
|
||||
StatService -> DB: 회의 통계 저장\n(회의ID, 세션ID, 진행시간, 참석자수, 발언횟수)
|
||||
activate DB
|
||||
DB --> StatService: 통계 저장 완료
|
||||
deactivate DB
|
||||
@ -124,7 +136,7 @@ else 종료 가능
|
||||
|
||||
AISuggestion -> SuggestionRepo: findAppliedSuggestions(meetingId)
|
||||
activate SuggestionRepo
|
||||
SuggestionRepo -> DB: SELECT *\nFROM ai_suggestions\nWHERE meeting_id = ?\nAND status = 'APPLIED'\nORDER BY suggestion_type, applied_at
|
||||
SuggestionRepo -> DB: 적용된 AI 제안 조회\n(회의ID, 상태='적용됨', 유형별 정렬)
|
||||
activate DB
|
||||
note right
|
||||
조회 대상:
|
||||
@ -144,7 +156,7 @@ else 종료 가능
|
||||
' 2. 회의록 조회
|
||||
Service -> MinutesRepo: findByMeetingId(meetingId)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT * FROM minutes\nWHERE meeting_id = ?
|
||||
MinutesRepo -> DB: 회의록 정보 조회\n(회의ID 기준)
|
||||
activate DB
|
||||
DB --> MinutesRepo: 회의록 정보
|
||||
deactivate DB
|
||||
@ -172,7 +184,7 @@ else 종료 가능
|
||||
|
||||
MinutesService -> MinutesRepo: getSectionContent(minutesId, sectionType)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: SELECT section_content\nFROM minutes_sections\nWHERE minutes_id = ?\nAND section_type = ?
|
||||
MinutesRepo -> DB: 회의록 섹션 내용 조회\n(회의록ID, 섹션유형)
|
||||
activate DB
|
||||
DB --> MinutesRepo: 기존 섹션 내용
|
||||
deactivate DB
|
||||
@ -191,7 +203,7 @@ else 종료 가능
|
||||
|
||||
MinutesService -> MinutesRepo: updateSectionContent(minutesId, sectionType, mergedContent)
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes_sections\nSET section_content = ?,\n updated_at = NOW(),\n ai_enhanced = true\nWHERE minutes_id = ?\nAND section_type = ?
|
||||
MinutesRepo -> DB: 회의록 섹션 내용 업데이트\n(병합된 내용, AI보강 플래그, 갱신일시)
|
||||
activate DB
|
||||
note right
|
||||
저장 내용:
|
||||
@ -212,7 +224,7 @@ else 종료 가능
|
||||
' 4. AI 제안 상태 업데이트 (APPLIED → MERGED)
|
||||
Service -> SuggestionRepo: updateSuggestionsStatus(appliedSuggestions, "MERGED")
|
||||
activate SuggestionRepo
|
||||
SuggestionRepo -> DB: UPDATE ai_suggestions\nSET status = 'MERGED',\n merged_at = NOW()\nWHERE id IN (...)
|
||||
SuggestionRepo -> DB: AI 제안 상태 업데이트\n(상태='병합완료', 병합일시)
|
||||
activate DB
|
||||
note right
|
||||
상태 변경:
|
||||
@ -228,7 +240,7 @@ else 종료 가능
|
||||
' 회의록 상태 업데이트
|
||||
Service -> MinutesRepo: updateMinutesStatus(meetingId, "DRAFT")
|
||||
activate MinutesRepo
|
||||
MinutesRepo -> DB: UPDATE minutes\nSET status = 'DRAFT',\n endedAt = NOW(),\n ai_suggestions_merged = true,\n merged_count = ?\nWHERE meetingId = ?\nAND status = 'IN_PROGRESS'
|
||||
MinutesRepo -> DB: 회의록 상태 업데이트\n(상태='초안', 종료일시, AI제안병합 플래그, 병합건수)
|
||||
activate DB
|
||||
note right
|
||||
회의록 완성도 표시:
|
||||
|
||||
@ -35,7 +35,7 @@ activate Service
|
||||
Service -> Repository: createNotification(todoId, "TODO_ASSIGNED", assignee)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: INSERT INTO notifications\n(notification_id, todo_id,\ntype='TODO_ASSIGNED',\nstatus='PENDING',\nrecipients,\ncreated_at)
|
||||
Repository -> DB: 알림 정보 생성\n(알림ID, TodoID, 유형, 상태, 수신자, 생성일시)
|
||||
activate DB
|
||||
DB --> Repository: notificationId 반환
|
||||
deactivate DB
|
||||
@ -86,7 +86,7 @@ alt 발송 성공
|
||||
|
||||
Service -> Repository: updateNotificationStatus(notificationId, "SENT")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notifications\nSET status='SENT',\nsent_at=NOW()\nWHERE notification_id='{notificationId}'
|
||||
Repository -> DB: 알림 상태 업데이트\n(상태=발송완료, 발송일시=현재시각)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -98,7 +98,7 @@ else 발송 실패
|
||||
|
||||
Service -> Repository: updateNotificationStatus(notificationId, "FAILED")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notifications\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}'
|
||||
Repository -> DB: 알림 상태 업데이트\n(상태=발송실패, 오류메시지=에러내용)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -29,7 +29,7 @@ loop 각 회의별
|
||||
Service -> Repository: checkReminderSent(meetingId)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: SELECT * FROM notifications\nWHERE meeting_id='{meetingId}'\nAND type='REMINDER'
|
||||
Repository -> DB: 리마인더 알림 조회\n(회의ID, 유형='REMINDER')
|
||||
activate DB
|
||||
DB --> Repository: 조회 결과
|
||||
deactivate DB
|
||||
@ -46,7 +46,7 @@ loop 각 회의별
|
||||
Service -> Repository: createNotification(meetingId, "REMINDER", participants)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='REMINDER',\nstatus='PENDING',\nrecipients,\ncreated_at)
|
||||
Repository -> DB: 리마인더 알림 생성\n(알림ID, 회의ID, 유형, 상태, 수신자, 생성일시)
|
||||
activate DB
|
||||
DB --> Repository: notificationId 반환
|
||||
deactivate DB
|
||||
@ -97,7 +97,7 @@ loop 각 회의별
|
||||
|
||||
Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW()
|
||||
Repository -> DB: 수신자별 알림 상태 업데이트\n(상태='발송완료', 발송일시=현재시각)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -109,7 +109,7 @@ loop 각 회의별
|
||||
|
||||
Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notification_recipients\nSET status='FAILED'
|
||||
Repository -> DB: 수신자별 알림 상태 업데이트\n(상태='발송실패')
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -127,7 +127,7 @@ loop 각 회의별
|
||||
Service -> Repository: updateNotificationStatus(notificationId, finalStatus)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount}
|
||||
Repository -> DB: 알림 최종 상태 업데이트\n(상태, 완료일시, 발송건수, 실패건수)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -27,7 +27,7 @@ activate Service
|
||||
|
||||
Service -> Repository: checkDuplicateNotification(eventId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM notifications\nWHERE event_id = ?
|
||||
Repository -> DB: 알림 중복 확인 조회\n(이벤트ID 기준)
|
||||
note right
|
||||
중복 발송 방지:
|
||||
- Event ID 기반
|
||||
@ -58,7 +58,7 @@ else 신규 이벤트
|
||||
|
||||
Service -> Repository: getUserPreferences(userId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM user_notification_prefs\nWHERE user_id = ?
|
||||
Repository -> DB: 사용자 알림 설정 조회\n(사용자ID 기준)
|
||||
note right
|
||||
사용자 설정 확인:
|
||||
- 알림 채널 (email/sms)
|
||||
@ -139,7 +139,7 @@ else 신규 이벤트
|
||||
|
||||
Service -> Repository: saveNotificationLog(notification)
|
||||
activate Repository
|
||||
Repository -> DB: INSERT INTO notifications\n(event_id, user_id, type, channel, status, sent_at)
|
||||
Repository -> DB: 알림 이력 저장\n(이벤트ID, 사용자ID, 유형, 채널, 상태, 발송일시)
|
||||
note right
|
||||
알림 로그 저장:
|
||||
- 발송 이력
|
||||
@ -152,7 +152,7 @@ else 신규 이벤트
|
||||
|
||||
Service -> Repository: updateUserActivity(userId, "NOTIFICATION_SENT")
|
||||
activate Repository
|
||||
Repository -> DB: INSERT INTO user_activities\n(user_id, activity_type, details)
|
||||
Repository -> DB: 사용자 활동 이력 저장\n(사용자ID, 활동유형, 상세내용)
|
||||
DB --> Repository: saved
|
||||
Repository --> Service: updated
|
||||
deactivate Repository
|
||||
|
||||
@ -34,7 +34,7 @@ activate Service
|
||||
Service -> Repository: createNotification(meetingId, "INVITATION", participants)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: INSERT INTO notifications\n(notification_id, meeting_id,\ntype='INVITATION',\nstatus='PENDING',\nrecipients,\ncreated_at)
|
||||
Repository -> DB: 초대 알림 생성\n(알림ID, 회의ID, 유형, 상태, 수신자, 생성일시)
|
||||
activate DB
|
||||
DB --> Repository: notificationId 반환
|
||||
deactivate DB
|
||||
@ -86,7 +86,7 @@ loop 각 참석자별
|
||||
|
||||
Service -> Repository: updateRecipientStatus(notificationId, recipient, "SENT")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notification_recipients\nSET status='SENT', sent_at=NOW()\nWHERE notification_id='{notificationId}'\nAND recipient='{email}'
|
||||
Repository -> DB: 수신자별 알림 상태 업데이트\n(상태='발송완료', 발송일시=현재시각)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -98,7 +98,7 @@ loop 각 참석자별
|
||||
|
||||
Service -> Repository: updateRecipientStatus(notificationId, recipient, "FAILED")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE notification_recipients\nSET status='FAILED',\nerror_message='{errorMessage}'\nWHERE notification_id='{notificationId}'\nAND recipient='{email}'
|
||||
Repository -> DB: 수신자별 알림 상태 업데이트\n(상태='발송실패', 오류메시지=에러내용)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
@ -116,7 +116,7 @@ end
|
||||
Service -> Repository: updateNotificationStatus(notificationId, finalStatus)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: UPDATE notifications\nSET status='{finalStatus}',\ncompleted_at=NOW(),\nsent_count={sentCount},\nfailed_count={failedCount}\nWHERE notification_id='{notificationId}'
|
||||
Repository -> DB: 알림 최종 상태 업데이트\n(상태, 완료일시, 발송건수, 실패건수)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -28,7 +28,7 @@ end note
|
||||
Service -> Repository: createRecording(meetingId, sessionId)
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: INSERT INTO recordings\n(recording_id, meeting_id, session_id,\nstatus='READY', created_at)
|
||||
Repository -> DB: 녹음 세션 생성\n(녹음ID, 회의ID, 세션ID, 상태, 생성일시)
|
||||
activate DB
|
||||
DB --> Repository: recordingId 반환
|
||||
deactivate DB
|
||||
@ -62,7 +62,7 @@ deactivate AzureClient
|
||||
Service -> Repository: updateRecordingStatus(recordingId, "RECORDING")
|
||||
activate Repository
|
||||
|
||||
Repository -> DB: UPDATE recordings\nSET status='RECORDING',\nstarted_at=NOW(),\nstorage_path='{blobUrl}'\nWHERE recording_id='{recordingId}'
|
||||
Repository -> DB: 녹음 상태 업데이트\n(상태='녹음중', 시작일시, 저장경로)
|
||||
activate DB
|
||||
DB --> Repository: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -21,7 +21,7 @@ activate Service
|
||||
|
||||
Service -> Repository: findMeetingById(meetingId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM meetings\nWHERE meeting_id = ?
|
||||
Repository -> DB: 회의 정보 조회\n(회의ID 기준)
|
||||
DB --> Repository: meeting data
|
||||
Repository --> Service: Meeting entity
|
||||
deactivate Repository
|
||||
@ -54,7 +54,7 @@ deactivate Speaker
|
||||
|
||||
Service -> Repository: saveSttSession(session)
|
||||
activate Repository
|
||||
Repository -> DB: INSERT INTO stt_sessions\n(meeting_id, status, started_at)
|
||||
Repository -> DB: STT 세션 저장\n(회의ID, 상태, 시작일시)
|
||||
DB --> Repository: session saved
|
||||
Repository --> Service: SttSession entity
|
||||
deactivate Repository
|
||||
@ -103,7 +103,7 @@ deactivate Speaker
|
||||
|
||||
Service -> Repository: saveSttSegment(segment)
|
||||
activate Repository
|
||||
Repository -> DB: INSERT INTO stt_segments\n(session_id, text, speaker_id, timestamp)
|
||||
Repository -> DB: STT 세그먼트 저장\n(세션ID, 텍스트, 화자ID, 타임스탬프)
|
||||
DB --> Repository: segment saved
|
||||
Repository --> Service: saved
|
||||
deactivate Repository
|
||||
|
||||
@ -55,7 +55,7 @@ end note
|
||||
Service -> TranscriptRepo: createTranscript(recordingId, text, metadata)
|
||||
activate TranscriptRepo
|
||||
|
||||
TranscriptRepo -> DB: INSERT INTO transcripts\n(transcript_id, recording_id, speaker_id,\ntext, confidence, timestamp, warning_flag,\ncreated_at)
|
||||
TranscriptRepo -> DB: 변환 결과 저장\n(텍스트ID, 녹음ID, 화자ID, 텍스트, 신뢰도, 타임스탬프, 경고플래그)
|
||||
activate DB
|
||||
DB --> TranscriptRepo: transcriptId 반환
|
||||
deactivate DB
|
||||
@ -68,7 +68,7 @@ deactivate TranscriptRepo
|
||||
Service -> RecordingRepo: updateSpeakerInfo(recordingId, speakerId)
|
||||
activate RecordingRepo
|
||||
|
||||
RecordingRepo -> DB: INSERT INTO speakers\n(recording_id, speaker_id, segment_count)\nON CONFLICT UPDATE segment_count
|
||||
RecordingRepo -> DB: 화자 정보 저장/업데이트\n(녹음ID, 화자ID, 세그먼트수)
|
||||
activate DB
|
||||
DB --> RecordingRepo: 업데이트 완료
|
||||
deactivate DB
|
||||
|
||||
@ -20,7 +20,7 @@ activate Service
|
||||
|
||||
Service -> Repository: findSessionById(sessionId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM stt_sessions\nWHERE session_id = ?
|
||||
Repository -> DB: STT 세션 조회\n(세션ID 기준)
|
||||
DB --> Repository: session data
|
||||
Repository --> Service: SttSession entity
|
||||
deactivate Repository
|
||||
@ -62,7 +62,7 @@ alt 실시간 변환 모드
|
||||
|
||||
Service -> Repository: saveSttSegment(segment)
|
||||
activate Repository
|
||||
Repository -> DB: INSERT INTO stt_segments\n(session_id, text, confidence, timestamp)
|
||||
Repository -> DB: STT 세그먼트 저장\n(세션ID, 텍스트, 신뢰도, 타임스탬프)
|
||||
DB --> Repository: saved
|
||||
Repository --> Service: segment saved
|
||||
deactivate Repository
|
||||
@ -99,7 +99,7 @@ else 배치 변환 모드
|
||||
|
||||
Service -> Repository: updateSessionStatus(sessionId, "PROCESSING")
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE stt_sessions\nSET status = 'PROCESSING'
|
||||
Repository -> DB: 세션 상태 업데이트\n(상태='처리중')
|
||||
DB --> Repository: updated
|
||||
Repository --> Service: updated
|
||||
deactivate Repository
|
||||
@ -107,7 +107,7 @@ end
|
||||
|
||||
Service -> Repository: aggregateTranscription(sessionId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT text, timestamp\nFROM stt_segments\nWHERE session_id = ?\nORDER BY timestamp
|
||||
Repository -> DB: 세그먼트 목록 조회\n(세션ID 기준, 타임스탬프 순 정렬)
|
||||
DB --> Repository: segments
|
||||
Repository --> Service: ordered segments
|
||||
deactivate Repository
|
||||
@ -122,7 +122,7 @@ end note
|
||||
|
||||
Service -> Repository: saveTranscription(fullText)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE stt_sessions\nSET full_text = ?,\nstatus = 'COMPLETED'
|
||||
Repository -> DB: 전체 텍스트 저장 및 상태 업데이트\n(전체텍스트, 상태='완료')
|
||||
DB --> Repository: saved
|
||||
Repository --> Service: updated session
|
||||
deactivate Repository
|
||||
|
||||
@ -12,10 +12,16 @@ participant "UserRepository" as Repository
|
||||
database "PostgreSQL<<E>>" as DB
|
||||
database "Redis Cache<<E>>" as Cache
|
||||
|
||||
Gateway -> Controller: GET /api/v1/dashboard\nAuthorization: Bearer {token}
|
||||
Gateway -> Controller: GET /api/dashboard?\npage=1&size=10&sort=createdAt,desc\nAuthorization: Bearer {token}
|
||||
note right
|
||||
페이지네이션 파라미터:
|
||||
- page: 페이지 번호 (기본값: 1)
|
||||
- size: 페이지 크기 (기본값: 10)
|
||||
- sort: 정렬 기준 (기본값: createdAt,desc)
|
||||
end note
|
||||
activate Controller
|
||||
|
||||
Controller -> Service: getDashboard(userId)
|
||||
Controller -> Service: getDashboard(userId, page, size, sort)
|
||||
activate Service
|
||||
|
||||
Service -> Cache: get("dashboard:" + userId)
|
||||
@ -28,10 +34,10 @@ end note
|
||||
alt 캐시 존재
|
||||
Cache --> Service: cached dashboard data
|
||||
|
||||
Service --> Controller: DashboardResponse
|
||||
Service --> Controller: DashboardResponse\n{meetings, todos, activities, stats, pagination}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{dashboard data}
|
||||
Controller --> Gateway: 200 OK\n{dashboard data + pagination}
|
||||
deactivate Controller
|
||||
|
||||
else 캐시 미존재
|
||||
@ -62,14 +68,14 @@ else 캐시 미존재
|
||||
else
|
||||
Service -> Repository: getRecentActivities(userId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM user_activities\nWHERE user_id = ?\nORDER BY created_at DESC\nLIMIT 10
|
||||
Repository -> DB: 최근 활동 내역 조회\n(사용자ID, 최신순 정렬, 10건)
|
||||
DB --> Repository: activities
|
||||
Repository --> Service: recent activities
|
||||
deactivate Repository
|
||||
else
|
||||
Service -> Repository: getUserStatistics(userId)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT\n COUNT(DISTINCT meeting_id) as total_meetings,\n COUNT(DISTINCT todo_id) as total_todos,\n AVG(meeting_duration) as avg_duration\nFROM user_statistics\nWHERE user_id = ?
|
||||
Repository -> DB: 사용자 통계 조회\n(총 회의수, 총 할일수, 평균 회의시간)
|
||||
DB --> Repository: statistics
|
||||
Repository --> Service: user statistics
|
||||
deactivate Repository
|
||||
@ -100,10 +106,26 @@ else 캐시 미존재
|
||||
end note
|
||||
Cache --> Service: cached
|
||||
|
||||
Service --> Controller: DashboardResponse\n{meetings, todos, activities, stats}
|
||||
Service --> Controller: DashboardResponse\n{meetings, todos, activities, stats, pagination}
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 200 OK\n{dashboard data}
|
||||
Controller --> Gateway: 200 OK\n{dashboard data + pagination}
|
||||
note right
|
||||
응답 형식:
|
||||
{
|
||||
"upcomingMeetings": [...],
|
||||
"activeTodos": [...],
|
||||
"recentActivities": [...],
|
||||
"statistics": {...},
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"size": 10,
|
||||
"totalElements": 45,
|
||||
"totalPages": 5,
|
||||
"hasNext": true
|
||||
}
|
||||
}
|
||||
end note
|
||||
deactivate Controller
|
||||
end
|
||||
|
||||
|
||||
@ -49,12 +49,12 @@ alt 인증 성공
|
||||
|
||||
Service -> Repository: findByUsername(username)
|
||||
activate Repository
|
||||
Repository -> DB: SELECT * FROM users\nWHERE username = ?
|
||||
Repository -> DB: 사용자 정보 조회\n(사용자명 기준)
|
||||
|
||||
alt 사용자 존재
|
||||
DB --> Repository: user data
|
||||
else 신규 사용자
|
||||
Repository -> DB: INSERT INTO users\n(username, email, department)
|
||||
Repository -> DB: 신규 사용자 등록\n(사용자명, 이메일, 부서)
|
||||
DB --> Repository: user created
|
||||
note right
|
||||
LDAP 정보 동기화:
|
||||
@ -107,7 +107,7 @@ alt 인증 성공
|
||||
|
||||
Service -> Repository: updateLastLogin(userId)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE users\nSET last_login_at = NOW()
|
||||
Repository -> DB: 최종 로그인 일시 업데이트\n(현재시각)
|
||||
DB --> Repository: updated
|
||||
Repository --> Service: updated
|
||||
deactivate Repository
|
||||
@ -125,10 +125,10 @@ else 인증 실패
|
||||
|
||||
Service -> Repository: incrementFailedAttempts(username)
|
||||
activate Repository
|
||||
Repository -> DB: UPDATE users\nSET failed_attempts = failed_attempts + 1
|
||||
Repository -> DB: 실패 횟수 증가\n(failed_attempts + 1)
|
||||
|
||||
alt 실패 횟수 초과 (5회)
|
||||
Repository -> DB: UPDATE users\nSET locked_until = NOW() + INTERVAL '30 minutes'
|
||||
Repository -> DB: 계정 잠금 설정\n(잠금시간=현재+30분)
|
||||
note right
|
||||
계정 잠금:
|
||||
- 5회 실패 시
|
||||
@ -143,7 +143,19 @@ else 인증 실패
|
||||
Service --> Controller: AuthenticationException
|
||||
deactivate Service
|
||||
|
||||
Controller --> Gateway: 401 Unauthorized\n{error: "Invalid credentials"}
|
||||
Controller --> Gateway: 401 Unauthorized
|
||||
note right
|
||||
에러 응답 형식:
|
||||
{
|
||||
"error": {
|
||||
"code": "AUTHENTICATION_FAILED",
|
||||
"message": "인증에 실패했습니다",
|
||||
"details": "사용자명 또는 비밀번호가 올바르지 않습니다",
|
||||
"timestamp": "2025-10-23T12:00:00Z",
|
||||
"path": "/api/v1/auth/login"
|
||||
}
|
||||
}
|
||||
end note
|
||||
deactivate Controller
|
||||
end
|
||||
|
||||
|
||||
@ -10,6 +10,16 @@ participant "Meeting Service" as Meeting
|
||||
participant "Redis Cache" as Redis
|
||||
participant "Notification Service" as Notification
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
autonumber
|
||||
|
||||
== Todo 완료 처리 ==
|
||||
|
||||
@ -6,43 +6,59 @@ title 대시보드조회 외부 시퀀스
|
||||
actor "사용자" as User
|
||||
participant "Web App" as Frontend
|
||||
participant "API Gateway" as Gateway
|
||||
participant "Meeting Service" as Meeting
|
||||
participant "User Service" as UserService
|
||||
database "Redis Cache" as Cache
|
||||
database "Meeting DB" as MeetingDB
|
||||
database "User DB" as UserDB
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
User -> Frontend: 대시보드 접근
|
||||
activate Frontend
|
||||
|
||||
Frontend -> Gateway: GET /api/meetings/dashboard
|
||||
Frontend -> Gateway: GET /api/dashboard?\npage=1&size=10&sort=createdAt,desc
|
||||
note right
|
||||
페이지네이션 파라미터:
|
||||
- page: 페이지 번호 (기본값: 1)
|
||||
- size: 페이지 크기 (기본값: 10)
|
||||
- sort: 정렬 기준 (기본값: createdAt,desc)
|
||||
end note
|
||||
activate Gateway
|
||||
|
||||
Gateway -> Meeting: GET /dashboard
|
||||
activate Meeting
|
||||
Gateway -> UserService: GET /dashboard?\npage=1&size=10&sort=createdAt,desc
|
||||
activate UserService
|
||||
|
||||
' 캐시 조회
|
||||
Meeting -> Cache: GET dashboard:{userId}
|
||||
UserService -> Cache: GET dashboard:{userId}
|
||||
activate Cache
|
||||
Cache --> Meeting: 캐시 조회 결과
|
||||
Cache --> UserService: 캐시 조회 결과
|
||||
deactivate Cache
|
||||
|
||||
alt Cache Hit
|
||||
Meeting -> Meeting: 캐시 데이터 반환
|
||||
UserService -> UserService: 캐시 데이터 반환
|
||||
else Cache Miss
|
||||
Meeting -> MeetingDB: 대시보드 데이터 조회\n- 예정된 회의 목록\n- 진행 중 Todo 목록\n- 최근 회의록 목록\n- 공유받은 회의록 목록\n- 통계 정보
|
||||
activate MeetingDB
|
||||
MeetingDB --> Meeting: 조회 결과
|
||||
deactivate MeetingDB
|
||||
UserService -> UserDB: 대시보드 데이터 조회\n- 예정된 회의 목록\n- 진행 중 Todo 목록\n- 최근 회의록 목록\n- 공유받은 회의록 목록\n- 통계 정보
|
||||
activate UserDB
|
||||
UserDB --> UserService: 조회 결과
|
||||
deactivate UserDB
|
||||
|
||||
Meeting -> Cache: SET dashboard:{userId}\n(TTL: 5분)
|
||||
UserService -> Cache: SET dashboard:{userId}\n(TTL: 5분)
|
||||
activate Cache
|
||||
Cache --> Meeting: 캐시 저장 완료
|
||||
Cache --> UserService: 캐시 저장 완료
|
||||
deactivate Cache
|
||||
end
|
||||
|
||||
Meeting --> Gateway: 대시보드 데이터 응답\n{\n "upcomingMeetings": [...],\n "activeTodos": [...],\n "recentMinutes": [...],\n "sharedMinutes": [...],\n "statistics": {...}\n}
|
||||
deactivate Meeting
|
||||
UserService --> Gateway: 대시보드 데이터 응답\n{\n "upcomingMeetings": [...],\n "activeTodos": [...],\n "recentMinutes": [...],\n "sharedMinutes": [...],\n "statistics": {...},\n "pagination": {\n "page": 1,\n "size": 10,\n "totalElements": 45,\n "totalPages": 5,\n "hasNext": true\n }\n}
|
||||
deactivate UserService
|
||||
|
||||
Gateway --> Frontend: 200 OK\n대시보드 데이터
|
||||
Gateway --> Frontend: 200 OK\n대시보드 데이터 + 페이지네이션 정보
|
||||
deactivate Gateway
|
||||
|
||||
Frontend -> Frontend: 대시보드 화면 렌더링\n- 예정된 회의 표시\n- Todo 목록 표시\n- 최근/공유 회의록 표시\n- 통계 차트 표시
|
||||
|
||||
@ -11,6 +11,16 @@ participant "Redis Cache" as Redis
|
||||
participant "Azure Event Hubs" as EventHub
|
||||
participant "Notification Service" as Notification
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
== 회의록 공유 설정 ==
|
||||
User -> Web: 공유 버튼 클릭
|
||||
activate Web
|
||||
|
||||
@ -12,6 +12,16 @@ database "Redis Cache" as Cache
|
||||
database "Meeting DB" as MeetingDB
|
||||
participant "WebSocket" as WS
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
== 회의록 상세 조회 ==
|
||||
|
||||
User -> Frontend: 회의록 클릭
|
||||
|
||||
@ -12,6 +12,16 @@ participant "AI Service" as AI
|
||||
database "Redis Cache" as Cache
|
||||
queue "Azure Event Hubs" as EventHub
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
== 회의 시작 ==
|
||||
|
||||
User -> Frontend: 회의 시작 버튼 클릭
|
||||
|
||||
@ -13,6 +13,16 @@ participant "Azure Event Hubs" as EventHub
|
||||
participant "Notification Service" as Notification
|
||||
participant "Email Service" as Email
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
== 회의 예약 ==
|
||||
User -> WebApp: 회의 정보 입력\n(제목, 날짜/시간, 장소, 참석자)
|
||||
activate WebApp
|
||||
|
||||
@ -12,6 +12,16 @@ participant "AI Service" as AI
|
||||
participant "Notification Service" as Notification
|
||||
participant "Azure Event Hubs" as EventHub
|
||||
|
||||
note over Gateway
|
||||
라우팅 규칙:
|
||||
/api/meetings/** → Meeting Service
|
||||
/api/minutes/** → Meeting Service
|
||||
/api/dashboard → User Service
|
||||
/api/notifications/** → Notification Service
|
||||
/api/auth/** → User Service
|
||||
/api/todos/** → Meeting Service
|
||||
end note
|
||||
|
||||
== 회의 종료 ==
|
||||
User -> WebApp: 회의 종료 버튼 클릭
|
||||
WebApp -> Gateway: POST /meetings/{meetingId}/end
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user