mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 05:36:23 +00:00
add new meeting
This commit is contained in:
parent
b591cca33a
commit
7e06bb412f
175
claude/make-run-profile.md
Normal file
175
claude/make-run-profile.md
Normal file
@ -0,0 +1,175 @@
|
||||
# 서비스실행파일작성가이드
|
||||
|
||||
[요청사항]
|
||||
- <수행원칙>을 준용하여 수행
|
||||
- <수행순서>에 따라 수행
|
||||
- [결과파일] 안내에 따라 파일 작성
|
||||
|
||||
[가이드]
|
||||
<수행원칙>
|
||||
- 설정 Manifest(src/main/resources/application*.yml)의 각 항목의 값은 하드코딩하지 않고 환경변수 처리
|
||||
- Kubernetes에 배포된 데이터베이스는 LoadBalacer유형의 Service를 만들어 연결
|
||||
- MQ 이용 시 'MQ설치결과서'의 연결 정보를 실행 프로파일의 환경변수로 등록
|
||||
<수행순서>
|
||||
- 준비:
|
||||
- 데이터베이스설치결과서(develop/database/exec/db-exec-dev.md) 분석
|
||||
- 캐시설치결과서(develop/database/exec/cache-exec-dev.md) 분석
|
||||
- MQ설치결과서(develop/mq/mq-exec-dev.md) 분석 - 연결 정보 확인
|
||||
- kubectl get svc -n tripgen-dev | grep LoadBalancer 실행하여 External IP 목록 확인
|
||||
- 실행:
|
||||
- 각 서비스별를 서브에이젼트로 병렬 수행
|
||||
- 설정 Manifest 수정
|
||||
- 하드코딩 되어 있는 값이 있으면 환경변수로 변환
|
||||
- 특히, 데이터베이스, MQ 등의 연결 정보는 반드시 환경변수로 변환해야 함
|
||||
- 민감한 정보의 디퐅트값은 생략하거나 간략한 값으로 지정
|
||||
- '<로그설정>'을 참조하여 Log 파일 설정
|
||||
- '<실행프로파일 작성 가이드>'에 따라 서비스 실행프로파일 작성
|
||||
- LoadBalancer External IP를 DB_HOST, REDIS_HOST로 설정
|
||||
- MQ 연결 정보를 application.yml의 환경변수명에 맞춰 설정
|
||||
- 서비스 실행 및 오류 수정
|
||||
- 'IntelliJ서비스실행기'를 'tools' 디렉토리에 다운로드
|
||||
- python 또는 python3 명령으로 백그라우드로 실행하고 결과 로그를 분석
|
||||
nohup python3 tools/run-intellij-service-profile.py {service-name} > logs/{service-name}.log 2>&1 & echo "Started {service-name} with PID: $!"
|
||||
- 서비스 실행은 다른 방법 사용하지 말고 **반드시 python 프로그램 이용**
|
||||
- 오류 수정 후 필요 시 실행파일의 환경변수를 올바르게 변경
|
||||
- 서비스 정상 시작 확인 후 서비스 중지
|
||||
- 결과: {service-name}/.run
|
||||
<서비스 중지 방법>
|
||||
- Window
|
||||
- netstat -ano | findstr :{PORT}
|
||||
- powershell "Stop-Process -Id {Process number} -Force"
|
||||
- Linux/Mac
|
||||
- netstat -ano | grep {PORT}
|
||||
- kill -9 {Process number}
|
||||
<로그설정>
|
||||
- **application.yml 로그 파일 설정**:
|
||||
```yaml
|
||||
logging:
|
||||
file:
|
||||
name: ${LOG_FILE:logs/trip-service.log}
|
||||
logback:
|
||||
rollingpolicy:
|
||||
max-file-size: 10MB
|
||||
max-history: 7
|
||||
total-size-cap: 100MB
|
||||
```
|
||||
|
||||
<실행프로파일 작성 가이드>
|
||||
- {service-name}/.run/{service-name}.run.xml 파일로 작성
|
||||
- Spring Boot가 아니고 **Gradle 실행 프로파일**이어야 함: '[실행프로파일 예시]' 참조
|
||||
- Kubernetes에 배포된 데이터베이스의 LoadBalancer Service 확인:
|
||||
- kubectl get svc -n {namespace} | grep LoadBalancer 명령으로 LoadBalancer IP 확인
|
||||
- 각 서비스별 데이터베이스의 LoadBalancer External IP를 DB_HOST로 사용
|
||||
- 캐시(Redis)의 LoadBalancer External IP를 REDIS_HOST로 사용
|
||||
- MQ 연결 설정:
|
||||
- MQ설치결과서(develop/mq/mq-exec-dev.md)에서 연결 정보 확인
|
||||
- MQ 유형에 따른 연결 정보 설정 예시:
|
||||
- RabbitMQ: RABBITMQ_HOST, RABBITMQ_PORT, RABBITMQ_USERNAME, RABBITMQ_PASSWORD
|
||||
- Kafka: KAFKA_BOOTSTRAP_SERVERS, KAFKA_SECURITY_PROTOCOL
|
||||
- Azure Service Bus: SERVICE_BUS_CONNECTION_STRING
|
||||
- AWS SQS: AWS_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
|
||||
- Redis (Pub/Sub): REDIS_HOST, REDIS_PORT, REDIS_PASSWORD
|
||||
- ActiveMQ: ACTIVEMQ_BROKER_URL, ACTIVEMQ_USER, ACTIVEMQ_PASSWORD
|
||||
- 기타 MQ: 해당 MQ의 연결에 필요한 호스트, 포트, 인증정보, 연결문자열 등을 환경변수로 설정
|
||||
- application.yml에 정의된 환경변수명 확인 후 매핑
|
||||
- 백킹서비스 연결 정보 매핑:
|
||||
- 데이터베이스설치결과서에서 각 서비스별 DB 인증 정보 확인
|
||||
- 캐시설치결과서에서 각 서비스별 Redis 인증 정보 확인
|
||||
- LoadBalancer의 External IP를 호스트로 사용 (내부 DNS 아님)
|
||||
- 개발모드의 DDL_AUTO값은 update로 함
|
||||
- JWT Secret Key는 모든 서비스가 동일해야 함
|
||||
- application.yaml의 환경변수와 일치하도록 환경변수 설정
|
||||
- application.yaml의 민감 정보는 기본값으로 지정하지 않고 실제 백킹서비스 정보로 지정
|
||||
- 백킹서비스 연결 확인 결과를 바탕으로 정확한 값을 지정
|
||||
- 기존에 파일이 있으면 내용을 분석하여 항목 추가/수정/삭제
|
||||
|
||||
[실행프로파일 예시]
|
||||
```
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="user-service" type="GradleRunConfiguration" factoryName="Gradle">
|
||||
<ExternalSystemSettings>
|
||||
<option name="env">
|
||||
<map>
|
||||
<entry key="ACCOUNT_LOCK_DURATION_MINUTES" value="30" />
|
||||
<entry key="CACHE_TTL" value="1800" />
|
||||
<entry key="DB_HOST" value="20.249.197.193" /> <!-- LoadBalancer External IP 사용 -->
|
||||
<entry key="DB_NAME" value="tripgen_user_db" />
|
||||
<entry key="DB_PASSWORD" value="tripgen_user_123" />
|
||||
<entry key="DB_PORT" value="5432" />
|
||||
<entry key="DB_USERNAME" value="tripgen_user" />
|
||||
<entry key="FILE_BASE_URL" value="http://localhost:8081" />
|
||||
<entry key="FILE_MAX_SIZE" value="5242880" />
|
||||
<entry key="FILE_UPLOAD_PATH" value="/app/uploads" />
|
||||
<entry key="JPA_DDL_AUTO" value="update" />
|
||||
<entry key="JPA_SHOW_SQL" value="true" />
|
||||
<entry key="JWT_ACCESS_TOKEN_EXPIRATION" value="86400" />
|
||||
<entry key="JWT_REFRESH_TOKEN_EXPIRATION" value="604800" />
|
||||
<entry key="JWT_SECRET" value="dev-jwt-secret-key-for-development-only" />
|
||||
<entry key="LOG_LEVEL_APP" value="DEBUG" />
|
||||
<entry key="LOG_LEVEL_ROOT" value="INFO" />
|
||||
<entry key="LOG_LEVEL_SECURITY" value="DEBUG" />
|
||||
<entry key="MAX_LOGIN_ATTEMPTS" value="5" />
|
||||
<entry key="PASSWORD_MIN_LENGTH" value="8" />
|
||||
<entry key="REDIS_DATABASE" value="0" />
|
||||
<entry key="REDIS_HOST" value="20.214.121.28" /> <!-- Redis LoadBalancer External IP 사용 -->
|
||||
<entry key="REDIS_PASSWORD" value="" />
|
||||
<entry key="REDIS_PORT" value="6379" />
|
||||
<entry key="SERVER_PORT" value="8081" />
|
||||
<entry key="SPRING_PROFILES_ACTIVE" value="dev" />
|
||||
<!-- MQ 사용하는 서비스의 경우 MQ 유형에 맞게 추가 -->
|
||||
<!-- Azure Service Bus 예시 -->
|
||||
<entry key="SERVICE_BUS_CONNECTION_STRING" value="Endpoint=sb://...;SharedAccessKeyName=...;SharedAccessKey=..." />
|
||||
<!-- RabbitMQ 예시 -->
|
||||
<entry key="RABBITMQ_HOST" value="20.xxx.xxx.xxx" />
|
||||
<entry key="RABBITMQ_PORT" value="5672" />
|
||||
<!-- Kafka 예시 -->
|
||||
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.xxx.xxx.xxx:9092" />
|
||||
<!-- 기타 MQ의 경우 해당 MQ에 필요한 연결 정보를 환경변수로 추가 -->
|
||||
</map>
|
||||
</option>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value="user-service:bootRun" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<EXTENSION ID="com.intellij.execution.ExternalSystemRunConfigurationJavaExtension">
|
||||
<extension name="net.ashald.envfile">
|
||||
<option name="IS_ENABLED" value="false" />
|
||||
<option name="IS_SUBST" value="false" />
|
||||
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||
<ENTRIES>
|
||||
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||
</ENTRIES>
|
||||
</extension>
|
||||
</EXTENSION>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>false</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
```
|
||||
|
||||
[참고자료]
|
||||
- 데이터베이스설치결과서: develop/database/exec/db-exec-dev.md
|
||||
- 각 서비스별 DB 연결 정보 (사용자명, 비밀번호, DB명)
|
||||
- LoadBalancer Service External IP 목록
|
||||
- 캐시설치결과서: develop/database/exec/cache-exec-dev.md
|
||||
- 각 서비스별 Redis 연결 정보
|
||||
- LoadBalancer Service External IP 목록
|
||||
- MQ설치결과서: develop/mq/mq-exec-dev.md
|
||||
- MQ 유형 및 연결 정보
|
||||
- 연결에 필요한 호스트, 포트, 인증 정보
|
||||
- LoadBalancer Service External IP (해당하는 경우)
|
||||
418
develop/dev/dev-backend.md
Normal file
418
develop/dev/dev-backend.md
Normal file
@ -0,0 +1,418 @@
|
||||
# Meeting 서비스 백엔드 개발 결과
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 개발 범위
|
||||
- **서비스명**: Meeting Service
|
||||
- **포트**: 8081
|
||||
- **아키텍처**: Clean/Hexagonal Architecture
|
||||
- **프레임워크**: Spring Boot 3.3.0, Java 21
|
||||
- **데이터베이스**: PostgreSQL (meetingdb)
|
||||
- **캐시**: Redis (database: 1)
|
||||
- **메시징**: Azure Event Hubs
|
||||
|
||||
### 1.2 개발 방식
|
||||
3단계 점진적 개발:
|
||||
- **Stage 0 (준비)**: 프로젝트 구조 파악 및 메인 애플리케이션 생성
|
||||
- **Stage 1 (공통 모듈)**: common 모듈 검토
|
||||
- **Stage 2 (서비스 구현)**: Config, Domain, Service, Gateway, Controller 레이어 구현
|
||||
|
||||
---
|
||||
|
||||
## 2. Stage 0: 준비 단계
|
||||
|
||||
### 2.1 완료 항목
|
||||
✅ 기존 개발 결과 분석
|
||||
- 62개 Java 파일 확인 (Domain, Service, UseCase, Gateway, Entity, Repository)
|
||||
- Clean/Hexagonal 아키텍처 패턴 확인
|
||||
- 패키지 구조 문서 작성 (develop/dev/package-structure-meeting.md)
|
||||
|
||||
✅ MeetingApplication.java 생성
|
||||
```java
|
||||
위치: meeting/src/main/java/com/unicorn/hgzero/meeting/MeetingApplication.java
|
||||
패키지: com.unicorn.hgzero.meeting
|
||||
ComponentScan: {"com.unicorn.hgzero.meeting", "com.unicorn.hgzero.common"}
|
||||
```
|
||||
|
||||
✅ application.yml 확인
|
||||
```yaml
|
||||
서버 포트: 8081
|
||||
데이터베이스: PostgreSQL (meetingdb)
|
||||
Redis: database 1
|
||||
JWT 설정: access-token-validity 3600초
|
||||
CORS: http://localhost:*
|
||||
```
|
||||
|
||||
✅ 컴파일 에러 수정
|
||||
- TemplateEntity 패키지 경로 수정
|
||||
- Dashboard 도메인 클래스 확장:
|
||||
- userId, period 필드 추가
|
||||
- Statistics 클래스 필드 확장 (11개 필드)
|
||||
- 도메인 메서드 추가:
|
||||
- MinutesSection.update(String title, String content)
|
||||
- Todo.update(String title, String description, String assigneeId, LocalDate dueDate, String priority)
|
||||
- Minutes.incrementVersion()
|
||||
- Minutes.updateTitle(String title)
|
||||
|
||||
### 2.2 컴파일 결과
|
||||
```
|
||||
BUILD SUCCESSFUL
|
||||
경고: 1개 (MinutesEntity @Builder.Default)
|
||||
에러: 0개
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Stage 1: common 모듈
|
||||
|
||||
### 3.1 common 모듈 구성
|
||||
✅ 검토 완료
|
||||
|
||||
| 카테고리 | 클래스 | 설명 |
|
||||
|---------|--------|------|
|
||||
| AOP | LoggingAspect | 로깅 관점 |
|
||||
| Config | JpaConfig | JPA 설정 |
|
||||
| DTO | ApiResponse | API 응답 포맷 |
|
||||
| DTO | JwtTokenDTO, JwtTokenRefreshDTO, JwtTokenVerifyDTO | JWT 토큰 DTO |
|
||||
| Entity | BaseTimeEntity | 생성/수정 시간 베이스 엔티티 |
|
||||
| Exception | BusinessException | 비즈니스 예외 |
|
||||
| Exception | ErrorCode | 에러 코드 |
|
||||
| Exception | InfraException | 인프라 예외 |
|
||||
| Util | DateUtil | 날짜 유틸리티 |
|
||||
| Util | StringUtil | 문자열 유틸리티 |
|
||||
|
||||
### 3.2 컴파일 결과
|
||||
```
|
||||
BUILD SUCCESSFUL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Stage 2: meeting 서비스 구현
|
||||
|
||||
### 4.1 Config 레이어 (완료)
|
||||
|
||||
#### 4.1.1 SecurityConfig
|
||||
✅ 구현 완료
|
||||
```
|
||||
위치: infra/config/SecurityConfig.java
|
||||
기능:
|
||||
- JWT 기반 인증
|
||||
- CORS 설정 (환경변수 기반)
|
||||
- Stateless 세션 관리
|
||||
- 공개 엔드포인트: /actuator/**, /swagger-ui/**, /health, /ws/**
|
||||
- WebSocket 엔드포인트 허용
|
||||
```
|
||||
|
||||
#### 4.1.2 JWT 인증 시스템
|
||||
✅ 구현 완료
|
||||
```
|
||||
위치: infra/config/jwt/
|
||||
|
||||
JwtTokenProvider:
|
||||
- JWT 토큰 검증 및 파싱
|
||||
- 사용자 정보 추출 (userId, username, authority)
|
||||
- 토큰 만료 확인
|
||||
|
||||
JwtAuthenticationFilter:
|
||||
- HTTP 요청에서 JWT 토큰 추출
|
||||
- Spring Security 인증 컨텍스트 설정
|
||||
- 공개 엔드포인트 필터 제외
|
||||
|
||||
UserPrincipal:
|
||||
- 인증된 사용자 정보 객체
|
||||
- userId, username, authority 필드
|
||||
- 권한 확인 메서드 (isAdmin, isUser)
|
||||
```
|
||||
|
||||
#### 4.1.3 SwaggerConfig
|
||||
✅ 구현 완료
|
||||
```
|
||||
위치: infra/config/SwaggerConfig.java
|
||||
기능:
|
||||
- OpenAPI 3.0 설정
|
||||
- Bearer JWT 인증 스킴
|
||||
- 서버 설정 (localhost:8081, 커스텀 서버)
|
||||
- API 정보 (제목, 설명, 버전, 연락처)
|
||||
```
|
||||
|
||||
### 4.2 컴파일 결과
|
||||
```
|
||||
BUILD SUCCESSFUL
|
||||
경고: 1개 (deprecated API 사용)
|
||||
에러: 0개
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 기존 구현 현황
|
||||
|
||||
### 5.1 Domain 레이어 (6개 클래스)
|
||||
✅ 기존 구현 확인
|
||||
- Meeting: 회의 도메인
|
||||
- Minutes: 회의록 도메인 (updateTitle, incrementVersion 메서드 추가)
|
||||
- MinutesSection: 회의록 섹션 도메인 (update 메서드 추가)
|
||||
- Todo: Todo 도메인 (update 메서드 추가)
|
||||
- Template: 템플릿 도메인
|
||||
- Dashboard: 대시보드 도메인 (userId, period 필드 추가, Statistics 확장)
|
||||
|
||||
### 5.2 Service 레이어 (6개 클래스)
|
||||
✅ 기존 구현 확인
|
||||
- MeetingService: 회의 비즈니스 로직
|
||||
- MinutesService: 회의록 비즈니스 로직
|
||||
- MinutesSectionService: 회의록 섹션 비즈니스 로직
|
||||
- TodoService: Todo 비즈니스 로직
|
||||
- TemplateService: 템플릿 비즈니스 로직
|
||||
- DashboardService: 대시보드 비즈니스 로직
|
||||
|
||||
### 5.3 UseCase 레이어 (28개 인터페이스)
|
||||
✅ 기존 구현 확인
|
||||
- UseCase In (16개): Service 입력 포트
|
||||
- UseCase Out (12개): Gateway 출력 포트
|
||||
|
||||
### 5.4 Gateway 레이어 (6개 클래스)
|
||||
✅ 기존 구현 확인
|
||||
- MeetingGateway: 회의 게이트웨이
|
||||
- MinutesGateway: 회의록 게이트웨이
|
||||
- TodoGateway: Todo 게이트웨이
|
||||
- TemplateGateway: 템플릿 게이트웨이
|
||||
- DashboardGateway: 대시보드 게이트웨이
|
||||
- CacheGateway: 캐시 게이트웨이
|
||||
|
||||
### 5.5 Entity 레이어 (5개 클래스)
|
||||
✅ 기존 구현 확인
|
||||
- MeetingEntity: 회의 엔티티
|
||||
- MinutesEntity: 회의록 엔티티
|
||||
- MinutesSectionEntity: 회의록 섹션 엔티티 (package 수정)
|
||||
- TodoEntity: Todo 엔티티
|
||||
- TemplateEntity: 템플릿 엔티티 (package 수정, import 추가)
|
||||
|
||||
### 5.6 Repository 레이어 (5개 인터페이스)
|
||||
✅ 기존 구현 확인
|
||||
- MeetingJpaRepository
|
||||
- MinutesJpaRepository
|
||||
- TodoJpaRepository
|
||||
- TemplateJpaRepository
|
||||
- MinutesSectionJpaRepository
|
||||
|
||||
---
|
||||
|
||||
## 6. 추가 구현 필요 항목
|
||||
|
||||
### 6.1 Controller 레이어 (5개 클래스)
|
||||
⏳ 구현 필요
|
||||
- DashboardController (GET /dashboard)
|
||||
- MeetingController (POST, PUT, POST /meetings 관련 4개 API)
|
||||
- MinutesController (GET, PATCH, POST, DELETE /minutes 관련 7개 API)
|
||||
- TodoController (POST, PATCH /todos 관련 2개 API)
|
||||
- TemplateController (GET /templates 관련 2개 API)
|
||||
|
||||
### 6.2 DTO 레이어 (~20개 클래스)
|
||||
⏳ 구현 필요
|
||||
- Request DTOs (~10개): 각 API의 요청 DTO
|
||||
- Response DTOs (~10개): 각 API의 응답 DTO
|
||||
|
||||
### 6.3 WebSocket 레이어 (3개 클래스)
|
||||
⏳ 구현 필요
|
||||
- WebSocketConfig: WebSocket 설정
|
||||
- WebSocketHandler: WebSocket 메시지 핸들러
|
||||
- CollaborationMessage: 실시간 협업 메시지
|
||||
|
||||
### 6.4 Event 레이어 (6개 클래스)
|
||||
⏳ 구현 필요
|
||||
- Event Publishers (3개):
|
||||
- MeetingEventPublisher
|
||||
- MinutesEventPublisher
|
||||
- TodoEventPublisher
|
||||
- Event Messages (3개):
|
||||
- MeetingStartedEvent
|
||||
- MeetingEndedEvent
|
||||
- NotificationRequestEvent
|
||||
|
||||
### 6.5 Cache 레이어 (2개 클래스)
|
||||
⏳ 구현 필요
|
||||
- CacheService: 캐시 서비스 구현체
|
||||
- CacheKeyGenerator: 캐시 키 생성기
|
||||
|
||||
### 6.6 추가 Config (2개 클래스)
|
||||
⏳ 구현 필요
|
||||
- RedisConfig: Redis 설정
|
||||
- WebSocketConfig: WebSocket 설정
|
||||
|
||||
---
|
||||
|
||||
## 7. 다음 단계 계획
|
||||
|
||||
### 7.1 Controller 및 DTO 구현
|
||||
우선순위: 높음
|
||||
|
||||
1. **DashboardController + DTO**
|
||||
- GET /dashboard
|
||||
- DashboardResponse
|
||||
|
||||
2. **MeetingController + DTOs**
|
||||
- POST /meetings (CreateMeetingRequest/Response)
|
||||
- PUT /meetings/{id}/template (SelectTemplateRequest/Response)
|
||||
- POST /meetings/{id}/start (StartMeetingRequest/Response)
|
||||
- POST /meetings/{id}/end (EndMeetingRequest/Response)
|
||||
|
||||
3. **MinutesController + DTOs**
|
||||
- 7개 API + Request/Response DTOs
|
||||
|
||||
4. **TodoController + DTOs**
|
||||
- 2개 API + Request/Response DTOs
|
||||
|
||||
5. **TemplateController + DTOs**
|
||||
- 2개 API + Response DTOs
|
||||
|
||||
### 7.2 WebSocket 구현
|
||||
우선순위: 중간
|
||||
|
||||
- WebSocketConfig
|
||||
- WebSocketHandler
|
||||
- CollaborationMessage
|
||||
|
||||
### 7.3 Event 및 Cache 구현
|
||||
우선순위: 중간
|
||||
|
||||
- Event Publishers
|
||||
- Event Messages
|
||||
- Cache Service
|
||||
- Redis Config
|
||||
|
||||
### 7.4 통합 테스트
|
||||
우선순위: 높음
|
||||
|
||||
- 전체 빌드 (./gradlew meeting:build)
|
||||
- API 통합 테스트
|
||||
- WebSocket 연결 테스트
|
||||
|
||||
---
|
||||
|
||||
## 8. 개발 환경
|
||||
|
||||
### 8.1 기술 스택
|
||||
- **언어**: Java 21
|
||||
- **프레임워크**: Spring Boot 3.3.0
|
||||
- **빌드 도구**: Gradle 8.14
|
||||
- **데이터베이스**: PostgreSQL 14
|
||||
- **캐시**: Redis 7
|
||||
- **메시징**: Azure Event Hubs
|
||||
- **API 문서**: OpenAPI 3.0 (Swagger)
|
||||
|
||||
### 8.2 의존성
|
||||
```gradle
|
||||
Spring Boot Starter Web
|
||||
Spring Boot Starter Data JPA
|
||||
Spring Boot Starter Security
|
||||
Spring Boot Starter WebSocket
|
||||
Spring Boot Starter Data Redis
|
||||
Spring Boot Starter Actuator
|
||||
SpringDoc OpenAPI (2.5.0)
|
||||
JJWT (0.12.5)
|
||||
Lombok
|
||||
PostgreSQL Driver
|
||||
```
|
||||
|
||||
### 8.3 데이터베이스 연결 정보
|
||||
```yaml
|
||||
호스트: 4.230.48.72
|
||||
포트: 5432
|
||||
데이터베이스: meetingdb
|
||||
사용자: hgzerouser
|
||||
```
|
||||
|
||||
### 8.4 Redis 연결 정보
|
||||
```yaml
|
||||
호스트: 20.249.177.114
|
||||
포트: 6379
|
||||
데이터베이스: 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 컴파일 및 빌드
|
||||
|
||||
### 9.1 컴파일 명령
|
||||
```bash
|
||||
# Meeting 서비스 컴파일
|
||||
./gradlew meeting:compileJava
|
||||
|
||||
# Common 모듈 컴파일
|
||||
./gradlew common:compileJava
|
||||
|
||||
# 전체 프로젝트 컴파일
|
||||
./gradlew compileJava
|
||||
```
|
||||
|
||||
### 9.2 빌드 명령
|
||||
```bash
|
||||
# Meeting 서비스 빌드
|
||||
./gradlew meeting:build
|
||||
|
||||
# 전체 프로젝트 빌드
|
||||
./gradlew build
|
||||
```
|
||||
|
||||
### 9.3 실행 명령
|
||||
```bash
|
||||
# Meeting 서비스 실행
|
||||
./gradlew meeting:bootRun
|
||||
|
||||
# 또는 jar 실행
|
||||
java -jar meeting/build/libs/meeting.jar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. API 엔드포인트
|
||||
|
||||
### 10.1 Dashboard APIs (1개)
|
||||
| Method | Endpoint | 설명 | 상태 |
|
||||
|--------|----------|------|-----|
|
||||
| GET | /dashboard | 대시보드 데이터 조회 | ⏳ 미구현 |
|
||||
|
||||
### 10.2 Meeting APIs (4개)
|
||||
| Method | Endpoint | 설명 | 상태 |
|
||||
|--------|----------|------|-----|
|
||||
| POST | /meetings | 회의 예약 | ⏳ 미구현 |
|
||||
| PUT | /meetings/{meetingId}/template | 템플릿 선택 | ⏳ 미구현 |
|
||||
| POST | /meetings/{meetingId}/start | 회의 시작 | ⏳ 미구현 |
|
||||
| POST | /meetings/{meetingId}/end | 회의 종료 | ⏳ 미구현 |
|
||||
|
||||
### 10.3 Minutes APIs (7개)
|
||||
| Method | Endpoint | 설명 | 상태 |
|
||||
|--------|----------|------|-----|
|
||||
| GET | /minutes | 회의록 목록 조회 | ⏳ 미구현 |
|
||||
| GET | /minutes/{minutesId} | 회의록 상세 조회 | ⏳ 미구현 |
|
||||
| PATCH | /minutes/{minutesId} | 회의록 수정 | ⏳ 미구현 |
|
||||
| POST | /minutes/{minutesId}/finalize | 회의록 확정 | ⏳ 미구현 |
|
||||
| POST | /minutes/{minutesId}/sections/{sectionId}/verify | 섹션 검증 완료 | ⏳ 미구현 |
|
||||
| POST | /minutes/{minutesId}/sections/{sectionId}/lock | 섹션 잠금 | ⏳ 미구현 |
|
||||
| DELETE | /minutes/{minutesId}/sections/{sectionId}/lock | 섹션 잠금 해제 | ⏳ 미구현 |
|
||||
|
||||
### 10.4 Todo APIs (2개)
|
||||
| Method | Endpoint | 설명 | 상태 |
|
||||
|--------|----------|------|-----|
|
||||
| POST | /todos | Todo 할당 | ⏳ 미구현 |
|
||||
| PATCH | /todos/{todoId}/complete | Todo 완료 | ⏳ 미구현 |
|
||||
|
||||
### 10.5 Template APIs (2개)
|
||||
| Method | Endpoint | 설명 | 상태 |
|
||||
|--------|----------|------|-----|
|
||||
| GET | /templates | 템플릿 목록 조회 | ⏳ 미구현 |
|
||||
| GET | /templates/{templateId} | 템플릿 상세 조회 | ⏳ 미구현 |
|
||||
|
||||
### 10.6 WebSocket
|
||||
| Endpoint | 설명 | 상태 |
|
||||
|----------|------|-----|
|
||||
| GET /ws/minutes/{minutesId} | 회의록 실시간 협업 | ⏳ 미구현 |
|
||||
|
||||
---
|
||||
|
||||
## 11. 참고 문서
|
||||
- 패키지 구조도: develop/dev/package-structure-meeting.md
|
||||
- API 설계서: design/backend/api/API설계서.md
|
||||
- 논리 아키텍처: design/backend/logical/logical-architecture.md
|
||||
- 내부 시퀀스: design/backend/sequence/inner/*.puml
|
||||
- 데이터베이스 설치 결과: develop/database/exec/db-exec-dev.md
|
||||
312
develop/dev/package-structure-meeting.md
Normal file
312
develop/dev/package-structure-meeting.md
Normal file
@ -0,0 +1,312 @@
|
||||
# Meeting Service 패키지 구조도
|
||||
|
||||
## 전체 구조
|
||||
|
||||
```
|
||||
meeting/
|
||||
├── src/
|
||||
│ ├── main/
|
||||
│ │ ├── java/
|
||||
│ │ │ └── com/unicorn/hgzero/meeting/
|
||||
│ │ │ ├── MeetingApplication.java
|
||||
│ │ │ ├── biz/
|
||||
│ │ │ │ ├── domain/
|
||||
│ │ │ │ │ ├── Dashboard.java (✅ 기존)
|
||||
│ │ │ │ │ ├── Meeting.java (✅ 기존)
|
||||
│ │ │ │ │ ├── Minutes.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesSection.java (✅ 기존)
|
||||
│ │ │ │ │ ├── Template.java (✅ 기존)
|
||||
│ │ │ │ │ ├── Todo.java (✅ 기존)
|
||||
│ │ │ │ │ ├── Session.java
|
||||
│ │ │ │ │ ├── Participant.java
|
||||
│ │ │ │ │ ├── CollaborationEvent.java
|
||||
│ │ │ │ │ └── Statistics.java
|
||||
│ │ │ │ ├── service/
|
||||
│ │ │ │ │ ├── DashboardService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MeetingService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesSectionService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TemplateService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TodoService.java (✅ 기존)
|
||||
│ │ │ │ │ ├── SessionService.java
|
||||
│ │ │ │ │ ├── CollaborationService.java
|
||||
│ │ │ │ │ └── StatisticsService.java
|
||||
│ │ │ │ ├── usecase/
|
||||
│ │ │ │ │ ├── in/
|
||||
│ │ │ │ │ │ ├── dashboard/
|
||||
│ │ │ │ │ │ │ └── GetDashboardUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ ├── meeting/
|
||||
│ │ │ │ │ │ │ ├── CreateMeetingUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── StartMeetingUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── EndMeetingUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── GetMeetingUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── CancelMeetingUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ └── SelectTemplateUseCase.java
|
||||
│ │ │ │ │ │ ├── minutes/
|
||||
│ │ │ │ │ │ │ ├── CreateMinutesUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── GetMinutesUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── UpdateMinutesUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── FinalizeMinutesUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ └── GetMinutesListUseCase.java
|
||||
│ │ │ │ │ │ ├── section/
|
||||
│ │ │ │ │ │ │ ├── CreateSectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── GetSectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── UpdateSectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── DeleteSectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── VerifySectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ └── LockSectionUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ ├── template/
|
||||
│ │ │ │ │ │ │ ├── CreateTemplateUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ ├── GetTemplateUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ │ └── GetTemplateListUseCase.java
|
||||
│ │ │ │ │ │ └── todo/
|
||||
│ │ │ │ │ │ ├── CreateTodoUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ ├── GetTodoUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ ├── UpdateTodoUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ ├── CompleteTodoUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ │ └── CancelTodoUseCase.java (✅ 기존)
|
||||
│ │ │ │ │ └── out/
|
||||
│ │ │ │ │ ├── DashboardReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MeetingReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MeetingWriter.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesWriter.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesSectionReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── MinutesSectionWriter.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TemplateReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TemplateWriter.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TodoReader.java (✅ 기존)
|
||||
│ │ │ │ │ ├── TodoWriter.java (✅ 기존)
|
||||
│ │ │ │ │ ├── SessionReader.java
|
||||
│ │ │ │ │ ├── SessionWriter.java
|
||||
│ │ │ │ │ ├── ParticipantReader.java
|
||||
│ │ │ │ │ ├── ParticipantWriter.java
|
||||
│ │ │ │ │ ├── CollaborationEventWriter.java
|
||||
│ │ │ │ │ └── StatisticsWriter.java
|
||||
│ │ │ │ └── dto/
|
||||
│ │ │ │ ├── DashboardDTO.java
|
||||
│ │ │ │ ├── MeetingDTO.java
|
||||
│ │ │ │ ├── MinutesDTO.java
|
||||
│ │ │ │ ├── SectionDTO.java
|
||||
│ │ │ │ ├── TemplateDTO.java
|
||||
│ │ │ │ └── TodoDTO.java
|
||||
│ │ │ └── infra/
|
||||
│ │ │ ├── controller/
|
||||
│ │ │ │ ├── DashboardController.java
|
||||
│ │ │ │ ├── MeetingController.java
|
||||
│ │ │ │ ├── MinutesController.java
|
||||
│ │ │ │ ├── TemplateController.java
|
||||
│ │ │ │ └── TodoController.java
|
||||
│ │ │ ├── dto/
|
||||
│ │ │ │ ├── request/
|
||||
│ │ │ │ │ ├── CreateMeetingRequest.java
|
||||
│ │ │ │ │ ├── UpdateMeetingRequest.java
|
||||
│ │ │ │ │ ├── CreateMinutesRequest.java
|
||||
│ │ │ │ │ ├── UpdateMinutesRequest.java
|
||||
│ │ │ │ │ ├── CreateSectionRequest.java
|
||||
│ │ │ │ │ ├── UpdateSectionRequest.java
|
||||
│ │ │ │ │ ├── CreateTodoRequest.java
|
||||
│ │ │ │ │ ├── UpdateTodoRequest.java
|
||||
│ │ │ │ │ ├── CreateTemplateRequest.java
|
||||
│ │ │ │ │ └── SelectTemplateRequest.java
|
||||
│ │ │ │ └── response/
|
||||
│ │ │ │ ├── DashboardResponse.java
|
||||
│ │ │ │ ├── MeetingResponse.java
|
||||
│ │ │ │ ├── SessionResponse.java
|
||||
│ │ │ │ ├── MeetingEndResponse.java
|
||||
│ │ │ │ ├── MinutesResponse.java
|
||||
│ │ │ │ ├── MinutesDetailResponse.java
|
||||
│ │ │ │ ├── MinutesListResponse.java
|
||||
│ │ │ │ ├── SectionResponse.java
|
||||
│ │ │ │ ├── TemplateResponse.java
|
||||
│ │ │ │ ├── TemplateListResponse.java
|
||||
│ │ │ │ └── TodoResponse.java
|
||||
│ │ │ ├── gateway/
|
||||
│ │ │ │ ├── DashboardGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── MeetingGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesSectionGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── TemplateGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── TodoGateway.java (✅ 기존)
|
||||
│ │ │ │ ├── SessionGateway.java
|
||||
│ │ │ │ ├── ParticipantGateway.java
|
||||
│ │ │ │ ├── CollaborationEventGateway.java
|
||||
│ │ │ │ ├── StatisticsGateway.java
|
||||
│ │ │ │ └── entity/
|
||||
│ │ │ │ ├── MeetingEntity.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesEntity.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesSectionEntity.java (✅ 기존)
|
||||
│ │ │ │ ├── TemplateEntity.java (✅ 기존)
|
||||
│ │ │ │ ├── TodoEntity.java (✅ 기존)
|
||||
│ │ │ │ ├── SessionEntity.java
|
||||
│ │ │ │ ├── ParticipantEntity.java
|
||||
│ │ │ │ ├── CollaborationEventEntity.java
|
||||
│ │ │ │ └── StatisticsEntity.java
|
||||
│ │ │ │ └── repository/
|
||||
│ │ │ │ ├── MeetingJpaRepository.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesJpaRepository.java (✅ 기존)
|
||||
│ │ │ │ ├── MinutesSectionJpaRepository.java (✅ 기존)
|
||||
│ │ │ │ ├── TemplateJpaRepository.java (✅ 기존)
|
||||
│ │ │ │ ├── TodoJpaRepository.java (✅ 기존)
|
||||
│ │ │ │ ├── SessionJpaRepository.java
|
||||
│ │ │ │ ├── ParticipantJpaRepository.java
|
||||
│ │ │ │ ├── CollaborationEventJpaRepository.java
|
||||
│ │ │ │ └── StatisticsJpaRepository.java
|
||||
│ │ │ ├── websocket/
|
||||
│ │ │ │ ├── WebSocketConfig.java
|
||||
│ │ │ │ ├── WebSocketHandler.java
|
||||
│ │ │ │ └── CollaborationMessageHandler.java
|
||||
│ │ │ ├── event/
|
||||
│ │ │ │ ├── publisher/
|
||||
│ │ │ │ │ ├── EventPublisher.java
|
||||
│ │ │ │ │ └── EventHubPublisher.java
|
||||
│ │ │ │ └── dto/
|
||||
│ │ │ │ ├── MeetingStartedEvent.java
|
||||
│ │ │ │ ├── MeetingEndedEvent.java
|
||||
│ │ │ │ ├── TodoAssignedEvent.java
|
||||
│ │ │ │ └── NotificationRequestEvent.java
|
||||
│ │ │ ├── cache/
|
||||
│ │ │ │ ├── CacheConfig.java
|
||||
│ │ │ │ └── CacheService.java
|
||||
│ │ │ └── config/
|
||||
│ │ │ ├── SecurityConfig.java
|
||||
│ │ │ ├── SwaggerConfig.java
|
||||
│ │ │ ├── JpaConfig.java
|
||||
│ │ │ └── jwt/
|
||||
│ │ │ ├── JwtAuthenticationFilter.java
|
||||
│ │ │ ├── JwtTokenProvider.java
|
||||
│ │ │ └── UserPrincipal.java
|
||||
│ │ └── resources/
|
||||
│ │ ├── application.yml (✅ 기존)
|
||||
│ │ └── application-dev.yml
|
||||
│ └── test/
|
||||
│ └── java/
|
||||
│ └── com/unicorn/hgzero/meeting/
|
||||
│ └── (테스트 코드는 제외)
|
||||
└── build.gradle (✅ 기존)
|
||||
```
|
||||
|
||||
## 패키지별 역할
|
||||
|
||||
### 1. biz (비즈니스 로직 레이어)
|
||||
|
||||
#### 1.1 domain
|
||||
- **역할**: 비즈니스 도메인 모델
|
||||
- **파일**:
|
||||
- Dashboard, Meeting, Minutes, MinutesSection, Template, Todo (✅ 기존)
|
||||
- Session, Participant, CollaborationEvent, Statistics (신규 필요)
|
||||
|
||||
#### 1.2 service
|
||||
- **역할**: 비즈니스 로직 구현 (UseCase 구현체)
|
||||
- **파일**:
|
||||
- DashboardService, MeetingService, MinutesService, MinutesSectionService, TemplateService, TodoService (✅ 기존)
|
||||
- SessionService, CollaborationService, StatisticsService (신규 필요)
|
||||
|
||||
#### 1.3 usecase/in
|
||||
- **역할**: Input Port (애플리케이션 서비스 인터페이스)
|
||||
- **파일**: 16개 기존, 3개 신규 필요
|
||||
- SelectTemplateUseCase
|
||||
- GetMinutesListUseCase
|
||||
- GetTemplateListUseCase
|
||||
|
||||
#### 1.4 usecase/out
|
||||
- **역할**: Output Port (Repository 인터페이스)
|
||||
- **파일**: 12개 기존, 7개 신규 필요
|
||||
- SessionReader/Writer
|
||||
- ParticipantReader/Writer
|
||||
- CollaborationEventWriter
|
||||
- StatisticsWriter
|
||||
|
||||
#### 1.5 dto
|
||||
- **역할**: 비즈니스 레이어 DTO
|
||||
- **파일**: 전부 신규 필요 (6개)
|
||||
|
||||
### 2. infra (인프라 레이어)
|
||||
|
||||
#### 2.1 controller
|
||||
- **역할**: REST API 엔드포인트
|
||||
- **파일**: 전부 신규 필요 (5개)
|
||||
|
||||
#### 2.2 dto/request & dto/response
|
||||
- **역할**: API 요청/응답 DTO
|
||||
- **파일**: 전부 신규 필요 (약 20개)
|
||||
|
||||
#### 2.3 gateway
|
||||
- **역할**: Repository 구현체 (Adapter)
|
||||
- **파일**:
|
||||
- 6개 Gateway 기존, 4개 신규 필요
|
||||
- 5개 Entity 기존, 4개 신규 필요
|
||||
- 5개 Repository 기존, 4개 신규 필요
|
||||
|
||||
#### 2.4 websocket
|
||||
- **역할**: 실시간 협업 WebSocket 처리
|
||||
- **파일**: 전부 신규 필요 (3개)
|
||||
|
||||
#### 2.5 event
|
||||
- **역할**: 이벤트 발행
|
||||
- **파일**: 전부 신규 필요 (6개)
|
||||
|
||||
#### 2.6 cache
|
||||
- **역할**: Redis 캐시 처리
|
||||
- **파일**: 전부 신규 필요 (2개)
|
||||
|
||||
#### 2.7 config
|
||||
- **역할**: Spring 설정
|
||||
- **파일**: 전부 신규 필요 (7개)
|
||||
|
||||
## 구현 상태 요약
|
||||
|
||||
### ✅ 구현 완료 (62개 파일)
|
||||
- Domain: 6개
|
||||
- Service: 6개
|
||||
- UseCase In: 16개
|
||||
- UseCase Out: 12개
|
||||
- Gateway: 6개
|
||||
- Entity: 5개
|
||||
- Repository: 5개
|
||||
- Application Config: 1개 (application.yml)
|
||||
- Build Config: 1개 (build.gradle)
|
||||
- Main Class: 0개
|
||||
|
||||
### ❌ 구현 필요 (약 80개 파일)
|
||||
- Main Application: 1개
|
||||
- Domain: 4개
|
||||
- Service: 3개
|
||||
- UseCase In: 3개
|
||||
- UseCase Out: 7개
|
||||
- Business DTO: 6개
|
||||
- Controller: 5개
|
||||
- API DTO: ~20개
|
||||
- Gateway: 4개
|
||||
- Entity: 4개
|
||||
- Repository: 4개
|
||||
- WebSocket: 3개
|
||||
- Event: 6개
|
||||
- Cache: 2개
|
||||
- Config: 7개
|
||||
- Application Config: 1개 (application-dev.yml)
|
||||
|
||||
## 다음 단계
|
||||
|
||||
1. **0단계: 준비**
|
||||
- ✅ 패키지 구조도 작성 완료
|
||||
- Application 메인 클래스 작성
|
||||
- application-dev.yml 작성
|
||||
|
||||
2. **1단계: common 모듈 확인 및 보완**
|
||||
- common 모듈 검토
|
||||
- 필요한 공통 클래스 추가
|
||||
|
||||
3. **2단계: meeting 서비스 구현**
|
||||
- Config 레이어 (SecurityConfig, SwaggerConfig, JWT 등)
|
||||
- Event 발행 인프라
|
||||
- Cache 서비스
|
||||
- Controller 레이어
|
||||
- DTO 레이어
|
||||
- 누락된 Domain/Service/Gateway
|
||||
- WebSocket 실시간 협업
|
||||
|
||||
4. **3단계: 컴파일 및 검증**
|
||||
- 각 단계별 컴파일
|
||||
- 에러 수정
|
||||
- 최종 빌드
|
||||
@ -0,0 +1,18 @@
|
||||
package com.unicorn.hgzero.meeting;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* Meeting Service Application
|
||||
* 회의, 회의록, Todo, 실시간 협업 관리 서비스 메인 클래스
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@ComponentScan(basePackages = {"com.unicorn.hgzero.meeting", "com.unicorn.hgzero.common"})
|
||||
public class MeetingApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MeetingApplication.class, args);
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,16 @@ import java.util.List;
|
||||
@AllArgsConstructor
|
||||
public class Dashboard {
|
||||
|
||||
/**
|
||||
* 사용자 ID
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 조회 기간
|
||||
*/
|
||||
private String period;
|
||||
|
||||
/**
|
||||
* 다가오는 회의 목록
|
||||
*/
|
||||
@ -49,6 +59,11 @@ public class Dashboard {
|
||||
*/
|
||||
private Integer totalMeetings;
|
||||
|
||||
/**
|
||||
* 예정된 회의 수
|
||||
*/
|
||||
private Integer scheduledMeetings;
|
||||
|
||||
/**
|
||||
* 진행 중인 회의 수
|
||||
*/
|
||||
@ -59,11 +74,31 @@ public class Dashboard {
|
||||
*/
|
||||
private Integer completedMeetings;
|
||||
|
||||
/**
|
||||
* 전체 회의록 수
|
||||
*/
|
||||
private Integer totalMinutes;
|
||||
|
||||
/**
|
||||
* 초안 상태 회의록 수
|
||||
*/
|
||||
private Integer draftMinutes;
|
||||
|
||||
/**
|
||||
* 확정된 회의록 수
|
||||
*/
|
||||
private Integer finalizedMinutes;
|
||||
|
||||
/**
|
||||
* 전체 Todo 수
|
||||
*/
|
||||
private Integer totalTodos;
|
||||
|
||||
/**
|
||||
* 대기 중인 Todo 수
|
||||
*/
|
||||
private Integer pendingTodos;
|
||||
|
||||
/**
|
||||
* 완료된 Todo 수
|
||||
*/
|
||||
|
||||
@ -84,4 +84,19 @@ public class Minutes {
|
||||
public boolean isFinalized() {
|
||||
return "FINALIZED".equals(this.status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 버전 증가
|
||||
*/
|
||||
public void incrementVersion() {
|
||||
this.version++;
|
||||
}
|
||||
|
||||
/**
|
||||
* 회의록 제목 업데이트
|
||||
*/
|
||||
public void updateTitle(String title) {
|
||||
this.title = title;
|
||||
this.version++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,4 +95,12 @@ public class MinutesSection {
|
||||
public boolean isVerified() {
|
||||
return Boolean.TRUE.equals(this.verified);
|
||||
}
|
||||
|
||||
/**
|
||||
* 섹션 정보 업데이트
|
||||
*/
|
||||
public void update(String title, String content) {
|
||||
this.title = title;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,4 +97,15 @@ public class Todo {
|
||||
LocalDate.now().isAfter(this.dueDate) &&
|
||||
!isCompleted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Todo 정보 업데이트
|
||||
*/
|
||||
public void update(String title, String description, String assigneeId, LocalDate dueDate, String priority) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
this.assigneeId = assigneeId;
|
||||
this.dueDate = dueDate;
|
||||
this.priority = priority;
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,7 +76,7 @@ public class MinutesService implements
|
||||
}
|
||||
|
||||
// 제목 수정
|
||||
minutes.update(title, minutes.getSections());
|
||||
minutes.updateTitle(title);
|
||||
|
||||
// 저장
|
||||
Minutes updatedMinutes = minutesWriter.save(minutes);
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
package com.unicorn.hgzero.meeting.infra.config;
|
||||
|
||||
import com.unicorn.hgzero.meeting.infra.config.jwt.JwtAuthenticationFilter;
|
||||
import com.unicorn.hgzero.meeting.infra.config.jwt.JwtTokenProvider;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Spring Security 설정
|
||||
* JWT 기반 인증 및 API 보안 설정
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Value("${cors.allowed-origins:http://localhost:3000,http://localhost:8080,http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084}")
|
||||
private String allowedOrigins;
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
return http
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
// Actuator endpoints
|
||||
.requestMatchers("/actuator/**").permitAll()
|
||||
// Swagger UI endpoints
|
||||
.requestMatchers("/swagger-ui/**", "/swagger-ui.html", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").permitAll()
|
||||
// Health check
|
||||
.requestMatchers("/health").permitAll()
|
||||
// WebSocket endpoints
|
||||
.requestMatchers("/ws/**").permitAll()
|
||||
// All other requests require authentication
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider),
|
||||
UsernamePasswordAuthenticationFilter.class)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
|
||||
// 환경변수에서 허용할 Origin 패턴 설정
|
||||
String[] origins = allowedOrigins.split(",");
|
||||
configuration.setAllowedOriginPatterns(Arrays.asList(origins));
|
||||
|
||||
// 허용할 HTTP 메소드
|
||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
|
||||
|
||||
// 허용할 헤더
|
||||
configuration.setAllowedHeaders(Arrays.asList(
|
||||
"Authorization", "Content-Type", "X-Requested-With", "Accept",
|
||||
"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"
|
||||
));
|
||||
|
||||
// 자격 증명 허용
|
||||
configuration.setAllowCredentials(true);
|
||||
|
||||
// Pre-flight 요청 캐시 시간
|
||||
configuration.setMaxAge(3600L);
|
||||
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.unicorn.hgzero.meeting.infra.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import io.swagger.v3.oas.models.servers.Server;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger/OpenAPI 설정
|
||||
* Meeting Service API 문서화를 위한 설정
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.info(apiInfo())
|
||||
.addServersItem(new Server()
|
||||
.url("http://localhost:8081")
|
||||
.description("Local Development"))
|
||||
.addServersItem(new Server()
|
||||
.url("{protocol}://{host}:{port}")
|
||||
.description("Custom Server")
|
||||
.variables(new io.swagger.v3.oas.models.servers.ServerVariables()
|
||||
.addServerVariable("protocol", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||
._default("http")
|
||||
.description("Protocol (http or https)")
|
||||
.addEnumItem("http")
|
||||
.addEnumItem("https"))
|
||||
.addServerVariable("host", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||
._default("localhost")
|
||||
.description("Server host"))
|
||||
.addServerVariable("port", new io.swagger.v3.oas.models.servers.ServerVariable()
|
||||
._default("8081")
|
||||
.description("Server port"))))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("Bearer Authentication", createAPIKeyScheme()));
|
||||
}
|
||||
|
||||
private Info apiInfo() {
|
||||
return new Info()
|
||||
.title("Meeting Service API")
|
||||
.description("회의, 회의록, Todo, 실시간 협업 관리 API")
|
||||
.version("1.0.0")
|
||||
.contact(new Contact()
|
||||
.name("HGZero Development Team")
|
||||
.email("dev@hgzero.com"));
|
||||
}
|
||||
|
||||
private SecurityScheme createAPIKeyScheme() {
|
||||
return new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.bearerFormat("JWT")
|
||||
.scheme("bearer");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
package com.unicorn.hgzero.meeting.infra.config.jwt;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* JWT 인증 필터
|
||||
* HTTP 요청에서 JWT 토큰을 추출하여 인증을 수행
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
|
||||
String token = jwtTokenProvider.resolveToken(request);
|
||||
|
||||
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
|
||||
String userId = jwtTokenProvider.getUserId(token);
|
||||
String username = null;
|
||||
String authority = null;
|
||||
|
||||
try {
|
||||
username = jwtTokenProvider.getUsername(token);
|
||||
} catch (Exception e) {
|
||||
log.debug("JWT에 username 클레임이 없음: {}", e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
authority = jwtTokenProvider.getAuthority(token);
|
||||
} catch (Exception e) {
|
||||
log.debug("JWT에 authority 클레임이 없음: {}", e.getMessage());
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(userId)) {
|
||||
// UserPrincipal 객체 생성 (username과 authority가 없어도 동작)
|
||||
UserPrincipal userPrincipal = UserPrincipal.builder()
|
||||
.userId(userId)
|
||||
.username(username != null ? username : "unknown")
|
||||
.authority(authority != null ? authority : "USER")
|
||||
.build();
|
||||
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(
|
||||
userPrincipal,
|
||||
null,
|
||||
Collections.singletonList(new SimpleGrantedAuthority(authority != null ? authority : "USER"))
|
||||
);
|
||||
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
log.debug("인증된 사용자: {} ({})", userPrincipal.getUsername(), userId);
|
||||
}
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String path = request.getRequestURI();
|
||||
return path.startsWith("/actuator") ||
|
||||
path.startsWith("/swagger-ui") ||
|
||||
path.startsWith("/v3/api-docs") ||
|
||||
path.equals("/health") ||
|
||||
path.startsWith("/ws");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
package com.unicorn.hgzero.meeting.infra.config.jwt;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.UnsupportedJwtException;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import io.jsonwebtoken.security.SecurityException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT 토큰 제공자
|
||||
* JWT 토큰의 생성, 검증, 파싱을 담당
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
private final long tokenValidityInMilliseconds;
|
||||
|
||||
public JwtTokenProvider(@Value("${jwt.secret}") String secret,
|
||||
@Value("${jwt.access-token-validity:3600}") long tokenValidityInSeconds) {
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
this.tokenValidityInMilliseconds = tokenValidityInSeconds * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP 요청에서 JWT 토큰 추출
|
||||
*/
|
||||
public String resolveToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰 유효성 검증
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token);
|
||||
return true;
|
||||
} catch (SecurityException | MalformedJwtException e) {
|
||||
log.debug("Invalid JWT signature: {}", e.getMessage());
|
||||
} catch (ExpiredJwtException e) {
|
||||
log.debug("Expired JWT token: {}", e.getMessage());
|
||||
} catch (UnsupportedJwtException e) {
|
||||
log.debug("Unsupported JWT token: {}", e.getMessage());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.debug("JWT token compact of handler are invalid: {}", e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 사용자 ID 추출
|
||||
*/
|
||||
public String getUserId(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
return claims.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 사용자명 추출
|
||||
*/
|
||||
public String getUsername(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
return claims.get("username", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* JWT 토큰에서 권한 정보 추출
|
||||
*/
|
||||
public String getAuthority(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
return claims.get("authority", String.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰 만료 시간 확인
|
||||
*/
|
||||
public boolean isTokenExpired(String token) {
|
||||
try {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
return claims.getExpiration().before(new Date());
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 만료 시간 추출
|
||||
*/
|
||||
public Date getExpirationDate(String token) {
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
|
||||
return claims.getExpiration();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,51 @@
|
||||
package com.unicorn.hgzero.meeting.infra.config.jwt;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* 인증된 사용자 정보
|
||||
* JWT 토큰에서 추출된 사용자 정보를 담는 Principal 객체
|
||||
*/
|
||||
@Getter
|
||||
@Builder
|
||||
@RequiredArgsConstructor
|
||||
public class UserPrincipal {
|
||||
|
||||
/**
|
||||
* 사용자 고유 ID
|
||||
*/
|
||||
private final String userId;
|
||||
|
||||
/**
|
||||
* 사용자명
|
||||
*/
|
||||
private final String username;
|
||||
|
||||
/**
|
||||
* 사용자 권한
|
||||
*/
|
||||
private final String authority;
|
||||
|
||||
/**
|
||||
* 사용자 ID 반환 (별칭)
|
||||
*/
|
||||
public String getName() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 관리자 권한 여부 확인
|
||||
*/
|
||||
public boolean isAdmin() {
|
||||
return "ADMIN".equals(authority);
|
||||
}
|
||||
|
||||
/**
|
||||
* 일반 사용자 권한 여부 확인
|
||||
*/
|
||||
public boolean isUser() {
|
||||
return "USER".equals(authority) || authority == null;
|
||||
}
|
||||
}
|
||||
@ -55,19 +55,19 @@ public class DashboardGateway implements DashboardReader {
|
||||
.count();
|
||||
|
||||
// 통계 객체 생성
|
||||
Dashboard.Statistics statistics = new Dashboard.Statistics(
|
||||
totalMeetings,
|
||||
scheduledMeetings,
|
||||
inProgressMeetings,
|
||||
completedMeetings,
|
||||
totalMinutes,
|
||||
draftMinutes,
|
||||
finalizedMinutes,
|
||||
totalTodos,
|
||||
pendingTodos,
|
||||
completedTodos,
|
||||
overdueTodos
|
||||
);
|
||||
Dashboard.Statistics statistics = Dashboard.Statistics.builder()
|
||||
.totalMeetings((int) totalMeetings)
|
||||
.scheduledMeetings((int) scheduledMeetings)
|
||||
.inProgressMeetings((int) inProgressMeetings)
|
||||
.completedMeetings((int) completedMeetings)
|
||||
.totalMinutes((int) totalMinutes)
|
||||
.draftMinutes((int) draftMinutes)
|
||||
.finalizedMinutes((int) finalizedMinutes)
|
||||
.totalTodos((int) totalTodos)
|
||||
.pendingTodos((int) pendingTodos)
|
||||
.completedTodos((int) completedTodos)
|
||||
.overdueTodos((int) overdueTodos)
|
||||
.build();
|
||||
|
||||
// 대시보드 생성
|
||||
return Dashboard.builder()
|
||||
@ -121,19 +121,19 @@ public class DashboardGateway implements DashboardReader {
|
||||
.count();
|
||||
|
||||
// 통계 객체 생성
|
||||
Dashboard.Statistics statistics = new Dashboard.Statistics(
|
||||
totalMeetings,
|
||||
scheduledMeetings,
|
||||
inProgressMeetings,
|
||||
completedMeetings,
|
||||
totalMinutes,
|
||||
draftMinutes,
|
||||
finalizedMinutes,
|
||||
totalTodos,
|
||||
pendingTodos,
|
||||
completedTodos,
|
||||
overdueTodos
|
||||
);
|
||||
Dashboard.Statistics statistics = Dashboard.Statistics.builder()
|
||||
.totalMeetings((int) totalMeetings)
|
||||
.scheduledMeetings((int) scheduledMeetings)
|
||||
.inProgressMeetings((int) inProgressMeetings)
|
||||
.completedMeetings((int) completedMeetings)
|
||||
.totalMinutes((int) totalMinutes)
|
||||
.draftMinutes((int) draftMinutes)
|
||||
.finalizedMinutes((int) finalizedMinutes)
|
||||
.totalTodos((int) totalTodos)
|
||||
.pendingTodos((int) pendingTodos)
|
||||
.completedTodos((int) completedTodos)
|
||||
.overdueTodos((int) overdueTodos)
|
||||
.build();
|
||||
|
||||
// 대시보드 생성
|
||||
return Dashboard.builder()
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.unicorn.hgzero.meeting.biz.domain;
|
||||
package com.unicorn.hgzero.meeting.infra.gateway.entity;
|
||||
|
||||
import com.unicorn.hgzero.common.entity.BaseTimeEntity;
|
||||
import com.unicorn.hgzero.meeting.biz.domain.Template;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user