Compare commits

...

58 Commits

Author SHA1 Message Date
merrycoral
7dc039361f Event-AI Kafka 통신 개선 및 타입 헤더 불일치 문제 해결
주요 변경사항:
- event-service KafkaConfig: JsonSerializer로 변경, 타입 헤더 비활성화
- ai-service application.yml: 타입 헤더 사용 안 함, 기본 타입 지정
- AIEventGenerationJobMessage: region, targetAudience, budget 필드 추가
- AiRecommendationRequest: region, targetAudience, budget 필드 추가
- AIJobKafkaProducer: 객체 직접 전송으로 변경 (이중 직렬화 문제 해결)
- AIJobKafkaConsumer: 양방향 통신 이슈로 비활성화 (.bak)
- EventService: Kafka producer 호출 시 새 필드 전달

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 15:58:23 +09:00
merrycoral
8ff79ca1ab 테스트 결과 파일들을 test/ 폴더로 이동
- API-TEST-RESULT.md → test/
- content-service-integration-analysis.md → test/
- content-service-integration-test-results.md → test/
- test-kafka-integration-results.md → test/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 01:40:21 +09:00
merrycoral
336d811f55 content-service 통합 테스트 완료 및 보고서 작성
- content-service HTTP 통신 테스트 완료 (9개 시나리오 성공)
- Job 관리 메커니즘 검증 (Redis 기반)
- EventId 기반 콘텐츠 조회 및 필터링 테스트
- 이미지 재생성 기능 검증
- Kafka 연동 현황 분석 (Consumer 미구현 확인)
- 통합 테스트 결과 보고서 작성
- 테스트 자동화 스크립트 추가

테스트 성공률: 100% (9/9)
응답 성능: < 150ms

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 01:24:29 +09:00
merrycoral
ee941e4910 Event-AI Kafka 연동 개선 및 메시지 필드명 camelCase 변경
주요 변경사항:
- AI Service Kafka 브로커 설정 수정 (4.230.50.63:9092 → 20.249.182.13:9095,4.217.131.59:9095)
- IntelliJ 실행 프로파일 Kafka 환경 변수 수정 (3개 파일)
- Kafka 메시지 DTO 필드명 snake_case → camelCase 변경
- @JsonProperty 어노테이션 제거로 코드 간결성 향상 (18줄 감소)

개선 효과:
- Event-AI Kafka 연동 정상 작동 확인
- 메시지 필드 매핑 성공률 0% → 100%
- jobId, eventId, storeName 등 모든 필드 정상 매핑
- AI 추천 생성 로직 정상 실행

테스트 결과:
- Kafka 메시지 발행/수신: Offset 34로 정상 동작 확인
- AI Service에서 메시지 처리 완료 (COMPLETED)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 22:55:20 +09:00
merrycoral
b71d27aa8b 비즈니스 친화적 eventId 및 jobId 생성 로직 구현
- EventIdGenerator 추가: EVT-{storeId}-{yyyyMMddHHmmss}-{random8} 형식
- JobIdGenerator 추가: JOB-{type}-{timestamp}-{random8} 형식
- EventService, JobService에 Generator 주입 및 사용
- AIJobKafkaProducer에 eventId 및 메시지 필드 추가
- AIEventGenerationJobMessage DTO 필드 확장
- Javadoc에서 UUID 표현 제거 및 실제 형식 명시
- Event.java의 UUID 백업 생성 로직 제거

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 20:54:10 +09:00
merrycoral
34291e1613 백엔드 서비스 구조 개선 및 데이터베이스 스키마 추가 2025-10-29 17:51:48 +09:00
hyeda2020
a41e431daf
Disable test execution in CI workflow
Comment out the test execution step in the CI workflow.
2025-10-29 16:11:28 +09:00
wonho
3da9303091 백엔드 서비스 설정 및 배포 구성 개선
- CORS 설정 업데이트 (모든 서비스)
- Swagger UI 경로 및 설정 수정
- Kubernetes 배포 설정 개선 (Ingress, Deployment)
- distribution-service SecurityConfig 및 Controller 개선
- IntelliJ 실행 프로파일 업데이트
- 컨테이너 이미지 빌드 문서화 (deployment/container/build-image.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 15:55:30 +09:00
jhbkjh
3075a5d49f 물리아키텍처 설계 완료
 주요 기능
- Azure 기반 물리아키텍처 설계 (개발환경/운영환경)
- 7개 마이크로서비스 물리 구조 설계
- 네트워크 아키텍처 다이어그램 작성 (Mermaid)
- 환경별 비교 분석 및 마스터 인덱스 문서

📁 생성 파일
- design/backend/physical/physical-architecture.md (마스터)
- design/backend/physical/physical-architecture-dev.md (개발환경)
- design/backend/physical/physical-architecture-prod.md (운영환경)
- design/backend/physical/*.mmd (4개 Mermaid 다이어그램)

🎯 핵심 성과
- 비용 최적화: 개발환경 월 $143, 운영환경 월 $2,860
- 확장성: 개발환경 100명 → 운영환경 10,000명 (100배)
- 가용성: 개발환경 95% → 운영환경 99.9%
- 보안: 다층 보안 아키텍처 (L1~L4)

🛠️ 기술 스택
- Azure Kubernetes Service (AKS)
- Azure Database for PostgreSQL Flexible
- Azure Cache for Redis Premium
- Azure Service Bus Premium
- Application Gateway + WAF

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 15:13:01 +09:00
merrycoral
2bce7cfb24 Merge branch 'feature/event' into develop 2025-10-29 15:01:57 +09:00
merrycoral
bcfbb6c7f9 Kafka 메시지 구조 개선 및 알림 서비스 추가 2025-10-29 15:00:20 +09:00
merrycoral
da173d79e9 EventService에 Kafka Producer 연동 추가 및 이벤트 배포 시 메시지 발행 구현
- EventService에 EventKafkaProducer 의존성 주입
- publishEvent 메서드에서 event-created 토픽으로 메시지 발행
- Event 엔티티의 selectedImageId 검증 임시 비활성화
- Kafka 메시지 발행 테스트 결과 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 14:11:07 +09:00
이선민
7711f2d527
Merge pull request #24 from ktds-dg0501/feature/distribution
api path 수정
2025-10-29 13:38:30 +09:00
sunmingLee
edcb519800 api path 수정 2025-10-29 13:37:33 +09:00
wonho
e7ffdcfe44 GitHub Actions CI/CD 파이프라인 및 Kustomize 다중 환경 배포 설정
- GitHub Actions workflow로 백엔드 서비스 자동 빌드/배포 구성
- Kustomize를 통한 dev/staging/prod 환경별 설정 관리
- 각 마이크로서비스별 Dockerfile 추가
- 배포 자동화 스크립트 및 환경 변수 설정
- CI/CD 가이드 문서 작성
2025-10-29 13:24:04 +09:00
merrycoral
95a419f104 Event 엔티티에 참여자 및 ROI 필드 추가 및 Frontend-Backend 통합
🔧 Backend 변경사항:
- Event 엔티티에 participants, targetParticipants, roi 필드 추가
- EventDetailResponse DTO 및 EventService 매퍼 업데이트
- ROI 자동 계산 비즈니스 로직 구현
- SecurityConfig CORS 설정 추가 (localhost:3000 허용)

🎨 Frontend 변경사항:
- TypeScript EventDetail 타입 정의 업데이트
- Events 페이지 실제 API 데이터 연동 (Mock 데이터 제거)
- 참여자 수 및 ROI 기반 통계 계산 로직 개선

📝 문서:
- Event 필드 추가 및 API 통합 테스트 결과서 작성

 테스트 완료:
- Backend API 응답 검증
- CORS 설정 검증
- Frontend-Backend 통합 테스트 성공

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 13:23:09 +09:00
cherry2250
1b73d2880b Redis 마스터 노드로 연결 설정 변경
- REDIS_HOST를 'redis'에서 'redis-node-0.redis-headless'로 변경
- Redis read-only replica 오류 해결
- Content Service 이미지 생성 정상 작동 확인
2025-10-29 11:20:27 +09:00
Cherry Kim
5a93205f30
Update secret-content-service.yaml 2025-10-29 11:07:01 +09:00
wonho
df04f85346 백엔드 서비스 AKS 배포 및 설정 완료
- Kubernetes 매니페스트 파일 생성 (7개 서비스)
  * user-service, event-service, ai-service, content-service
  * participation-service, analytics-service, distribution-service
  * 공통 리소스: Ingress, ConfigMap, Secret, ImagePullSecret

- analytics-service 배포 문제 해결
  * Hibernate PostgreSQL dialect 추가
  * DB 자격증명 수정 (eventuser/Hi5Jessica!)
  * analytics_db 데이터베이스 생성

- content-service Probe 경로 수정
  * Context path 포함 (/api/v1/content/actuator/health)

- distribution-service 신규 배포
  * Docker 이미지 빌드 및 ACR 푸시
  * K8s 매니페스트 생성 및 배포
  * Ingress 경로 추가 (/distribution)

- Gradle bootJar 설정 추가
  * 5개 서비스에 archiveFileName 설정

- 배포 가이드 문서 추가
  * deployment/k8s/deploy-k8s-guide.md
  * claude/deploy-k8s-back.md
  * deployment/container/build-image.md 업데이트

배포 완료: 모든 백엔드 서비스(7개) 정상 실행 중

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 10:59:09 +09:00
이선민
23265b5849
Merge pull request #23 from ktds-dg0501/feature/distribution
merge feature/distribution into develop branch
2025-10-29 10:14:56 +09:00
sunmingLee
8fef09df02 필요없는 폴더 삭제 2025-10-29 10:12:42 +09:00
sunmingLee
63ba449f93 Merge origin/develop into feature/distribution
- develop 브랜치의 최신 변경사항 병합
- .gradle 캐시 파일 충돌 해결 (삭제)
- ParticipationServiceApplication.run.xml 충돌 해결 (develop 버전 선택)
- make-run-profile.md 충돌 해결 (develop 버전 선택)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 10:09:47 +09:00
sunmingLee
64aec0fda5 distribution-completed Kafka 이벤트 형식 변경
- DistributedChannelInfo DTO 추가 (channel, channelType, status, expectedViews)
- ChannelType enum에 category 필드 추가 (TV, CALL, SNS 구분)
- DistributionCompletedEvent 구조 변경 (distributedChannels를 상세 정보 리스트로 변경)
- completedAt 필드에 @JsonFormat 추가하여 ISO 8601 문자열 형식으로 직렬화
- publishDistributionCompletedEvent 메서드 수정하여 새로운 형식으로 이벤트 발행

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 09:56:32 +09:00
Cherry Kim
436c0bf2b8
Merge pull request #22 from ktds-dg0501/feature/content
Feature/content
2025-10-29 09:43:17 +09:00
Cherry Kim
ea026d7fa3
Merge branch 'develop' into feature/content 2025-10-29 09:42:16 +09:00
cherry2250
019ac96daa HuggingFace 제거 및 Replicate API 통합 완료
주요 변경사항:
- HuggingFace 관련 코드 및 의존성 완전 제거
  - HuggingFaceImageGenerator.java 삭제
  - HuggingFaceApiClient.java 삭제
  - HuggingFaceRequest.java 삭제
  - Resilience4j의 HuggingFace CircuitBreaker 제거

- Kubernetes 배포 설정
  - Deployment: content-service-deployment.yaml 업데이트
  - Service: content-service-service.yaml 추가
  - Health check 경로 수정 (/api/v1/content/actuator/health)
  - Dockerfile 추가 (멀티스테이지 빌드)

- Spring Boot 설정 최적화
  - application.yml: context-path 설정 (/api/v1/content)
  - HuggingFace 설정 제거, Replicate API 설정 유지
  - CORS 설정: kt-event-marketing* 도메인 허용

- Controller 경로 수정
  - ContentController: @RequestMapping 중복 제거
  - context-path와의 충돌 해결

- Security 설정
  - Chrome DevTools 경로 예외 처리 추가 (/.well-known/**)
  - CORS 설정 강화

- Swagger/OpenAPI 설정
  - VM Development Server URL 추가
  - 서버 URL 우선순위 조정

- 환경 변수 통일
  - REPLICATE_API_KEY → REPLICATE_API_TOKEN으로 변경

테스트 결과:
 Replicate API 정상 작동 (이미지 생성 성공)
 Azure Blob Storage 업로드 성공
 Redis 연결 정상 (마스터 노드 연결)
 Swagger UI 정상 작동
 모든 API 엔드포인트 정상 응답

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 23:08:54 +09:00
cherry2250
bc57b27852 라우팅 충돌 해결: imageId 경로 변수에 숫자 정규식 추가
- /images/{imageId}를 /images/{imageId:[0-9]+}로 변경
- /images/generate와의 라우팅 충돈 해결
- NumberFormatException 오류 수정
- content-service Kubernetes Deployment 파일 추가
2025-10-28 20:15:35 +09:00
cherry2250
b9514257b0 HuggingFaceImageGenerator를 프로파일 기반으로 변경하여 빈 충돌 해결
- @Profile("huggingface") 추가로 기본 프로파일에서는 비활성화
- StableDiffusionImageGenerator를 기본 구현체로 사용
- content-service 배포 오류 해결
2025-10-28 19:47:39 +09:00
jhbkjh
977a287a91 participation-service: CORS 설정 추가
- ParticipationController, DebugController, WinnerController에 @CrossOrigin 애노테이션 추가
- http://localhost:3000에서의 크로스 오리진 요청 허용
- 프론트엔드 개발 환경과의 연동을 위한 CORS 해결

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 17:47:25 +09:00
Hyowon Yang
3f0eccb69a
Merge pull request #21 from ktds-dg0501/feature/analytics
Analytics Service 실행 프로파일 추가
2025-10-28 16:44:32 +09:00
Hyowon Yang
f30213d1a2 Analytics Service 실행 프로파일 추가 2025-10-28 16:42:05 +09:00
merrycoral
284278180c Merge branch 'feature/event' into develop 2025-10-28 16:40:57 +09:00
SWPARK
9438e0d285
Merge pull request #20 from ktds-dg0501/feature/ai
ai-service application.yml 환경 변수를 static 값으로 변경
2025-10-28 16:37:16 +09:00
박세원
02a4e966e8 ai-service application.yml 환경 변수를 static 값으로 변경
- Redis, Kafka, Server, JWT, CORS 설정을 static 값으로 변경
- AI API Configuration을 실제 API 키와 함께 static하게 설정
- 모든 환경 변수 플레이스홀더를 제거하고 직접 값 지정

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 16:33:59 +09:00
Cherry Kim
d36dc5be27
Merge pull request #19 from ktds-dg0501/feature/content
Feature/content
2025-10-28 16:22:49 +09:00
cherry2250
9305dfdb7f application.yml 통합 및 Azure Blob Storage 설정 추가
- application-dev.yml, application-local.yml 삭제
- 단일 application.yml로 통합 (user-service 형식 참고)
- Azure Blob Storage connection string 기본값 추가
- Redis, Actuator, Logging 상세 설정 추가
- OpenAPI/Swagger 설정 추가
- CORS 설정 추가
- 모든 설정을 환경 변수로 관리 가능하도록 구성

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 16:19:51 +09:00
Hyowon Yang
d511140ecb
Merge pull request #18 from ktds-dg0501/feature/analytics
Feature/analytics
2025-10-28 16:03:44 +09:00
Hyowon Yang
4421f4447f Analytics Service 프론트엔드 연동을 위한 DTO 필드명 수정 및 증감 데이터 추가
- DTO 필드명 통일 (프론트엔드 호환)
  - totalParticipants → participants
  - channelName → channel
  - totalInvestment → totalCost

- 증감 데이터 필드 추가
  - participantsDelta: 참여자 증감 (현재 0, TODO)
  - targetRoi: 목표 ROI (EventStats에서 가져옴)

- EventStats 엔티티 스키마 변경
  - targetRoi 컬럼 추가 (BigDecimal, default: 0)

- Service 로직 수정
  - AnalyticsService: 필드명 변경 및 증감 데이터 적용
  - ROICalculator: totalCost 필드명 변경
  - UserAnalyticsService: 필드명 변경 및 증감 데이터 적용

- 검증 문서 추가
  - frontend-backend-validation.md: 수정 내용 및 다음 단계 정리

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 15:59:49 +09:00
cherry2250
5a82fe3610 Mock 구현 제거 및 원격 서비스 연결 설정
- Mock 디렉토리 완전 제거 (biz/service/mock, infra/gateway/mock)
- @Profile 조건부 어노테이션 모두 제거
- Redis 원격 서버 연결 (20.214.210.71:6379)
- RegenerateImageService 실제 구현 추가
- ContentWriter.getImageById() 메서드 추가
- JWT Secret 보안 강화 (32자 이상)
- API 토큰 기본값 설정 추가
- AKS 배포 준비 완료

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 15:52:57 +09:00
Hyowon Yang
02fd82e0af Analytics Service DDL_AUTO를 create로 변경하여 스키마 재생성
문제 해결:
- storeId → userId 필드명 변경으로 인한 스키마 불일치
- PostgreSQL ERROR: column "user_id" of relation "event_stats" contains null values
- update 모드는 컬럼명 변경(rename)을 자동 처리하지 못함

변경사항:
- DDL_AUTO: update → create
- 서비스 시작 시 테이블을 DROP 후 재생성
- MVP 환경: SampleDataLoader가 샘플 데이터 자동 생성

주의사항:
- create 모드는 매번 테이블을 재생성함 (데이터 손실)
- MVP 환경에서만 사용, 실제 운영 시 update/validate로 변경 필요

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 15:31:51 +09:00
merrycoral
0c718c67f6 백엔드 컨테이너 실행 가이드 작성
- deployment/container/run-container-guide.md 생성
- 4개 서비스(user, event, analytics, participation) 컨테이너 실행 방법 안내
- VM 접속, ACR 설정, 이미지 푸시, 컨테이너 실행, 재배포 절차 포함
- CORS 설정에 프론트엔드 주소(http://20.196.65.160:3000) 추가
- 실행정보: ACR(acrdigitalgarage01), VM(20.196.65.160)
2025-10-28 15:26:35 +09:00
Hyowon Yang
ea4aa5d072 Analytics Service storeId → userId 변환 및 User 통합 분석 API 개발 완료
주요 변경사항:
- EventStats 엔티티 storeId → userId 필드 변경
- EventStatsRepository 메소드명 변경 (findAllByStoreId → findAllByUserId)
- MVP 환경 1:1 관계 적용 (1 user = 1 store)
- EventCreatedConsumer에서 storeId → userId 매핑 처리

User 통합 분석 API 4개 신규 개발:
1. GET /api/v1/users/{userId}/analytics - 사용자 전체 성과 대시보드
2. GET /api/v1/users/{userId}/analytics/channels - 채널별 성과 분석
3. GET /api/v1/users/{userId}/analytics/roi - ROI 상세 분석
4. GET /api/v1/users/{userId}/analytics/timeline - 시간대별 참여 추이

기술 스택:
- Spring Boot 3.3.0, Java 21
- JPA/Hibernate, Redis 캐싱 (TTL 30분)
- Kafka Event-Driven 아키텍처

문서:
- test-backend.md: 백엔드 테스트 결과서 작성 완료

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 15:19:43 +09:00
kkkd-max
e807bdbd59
Merge pull request #17 from ktds-dg0501/feature/participation-service
participant_id 중복 생성 문제 수정
2025-10-28 15:18:01 +09:00
merrycoral
cf2689390d Kafka 메시지 타입 불일치 수정 (Long → UUID)
변경 내역:
- EventCreatedMessage: eventId, userId 타입을 Long에서 UUID로 변경
- EventKafkaProducer: publishEventCreated 메소드 파라미터 타입을 UUID로 변경

변경 이유:
- Event Entity는 UUID 타입을 사용하지만 Kafka 메시지는 Long을 사용하여 타입 불일치 발생
- Entity와 Kafka 메시지 간 타입 일관성 확보
- 런타임 타입 변환 오류 방지

영향:
- Event Service 내부 일관성 확보
- 향후 타 서비스와의 통합 시 UUID 표준 준비

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 15:07:35 +09:00
merrycoral
89a86c1301 Event Service 컨테이너 이미지 빌드 및 타입 시스템 통일
- UserPrincipal userId/storeId 타입을 Long에서 UUID로 변경
- JwtTokenProvider UUID 파싱 로직 수정
- event-service build.gradle에 bootJar 설정 추가
- Docker 이미지 빌드 성공 (event-service:latest, 1.08GB)
- 컨테이너 이미지 빌드 가이드 문서 작성

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 14:41:48 +09:00
doyeon
c768fff11e participant_id 중복 생성 문제 수정
- ParticipantRepository에 날짜별 최대 순번 조회 메서드 추가
- ParticipationService의 순번 생성 로직을 날짜 기반으로 수정
- 이벤트별 database ID 대신 날짜별 전체 최대 순번 사용
- participant_id unique 제약조건 위반으로 인한 PART_001 에러 해결
- 다른 이벤트 간 participant_id 충돌 방지

🎯 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 14:34:09 +09:00
merrycoral
f07002ac33 Merge branch 'feature/event' into develop
Event Service 전체 API 구현 완료

주요 변경 사항:
- 14개 API 전체 구현 완료 (100%)
- AI 추천 플로우 구현
- 이미지 생성/편집 API 구현
- 배포 채널 선택 API 구현
- 이벤트 수정 API 구현
- Redis 연동 구현
- Kafka Producer 구현
- Content Service 클라이언트 구현
- API 매핑 문서 현행화 (v2.0)
- Docker Compose 설정 추가
- 테스트 및 유틸리티 스크립트 추가

충돌 해결:
- .run/EventServiceApplication.run.xml 삭제 (새 위치로 이동)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:36:20 +09:00
merrycoral
2ca453f89e event 서비스 설정파일 충돌 수정 2025-10-28 13:33:00 +09:00
merrycoral
e2179daaf7 Event Service API 매핑 문서 현행화 (v2.0)
- 구현률 100% 달성: 14개 API 전체 구현 완료
- 신규 구현 API 문서화 (5개):
  * AI 추천 요청/선택 API
  * 이미지 편집 API
  * 배포 채널 선택 API
  * 이벤트 수정 API
- 문서 구조 개선:
  * 미구현 API 계획 섹션 제거
  * 서비스 간 연동 가이드 추가
  * 통합 테스트 시나리오 추가
- Controller 라인 번호 정확도 향상
- .gitignore에 heap dump 파일 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-28 13:22:22 +09:00
merrycoral
435ba1a86c Event Service 백엔드 테스트 완료
- 백엔드 API 테스트 완료 (8/8 성공)
- Redis, PostgreSQL, Kafka 연동 검증
- ErrorHandlingDeserializer를 통한 Kafka Consumer 안정화
- 테스트 결과 보고서 작성 (develop/dev/test-backend.md)
- 실행 프로파일 추가 (event-service/.run/)
- 설정 일치 검증 완료 (application.yml ↔ run.xml)
2025-10-28 11:45:09 +09:00
cherry2250
16a91c85bf gradlew 실행 권한 추가 2025-10-28 10:46:47 +09:00
merrycoral
d89ee4edf7 Event Service 백엔드 API 개발 및 테스트 완료
- Event Service API 엔드포인트 추가 (이벤트 생성, 조회, 수정, AI 추천, 배포)
- DTO 클래스 추가 (요청/응답 모델)
- Kafka Producer 구성 (AI 작업 비동기 처리)
- Content Service Feign 클라이언트 구성
- Redis 설정 추가 및 테스트 컨트롤러 작성
- Docker Compose 설정 (Redis, Kafka, Zookeeper)
- 백엔드 API 테스트 완료 및 결과 문서 작성
- JWT 테스트 토큰 생성 스크립트 추가
- Event Service 실행 스크립트 추가

테스트 결과: 6개 주요 API 모두 정상 작동 확인

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 17:24:09 +09:00
merrycoral
55e546e0b3 이벤트 API 매핑 문서 업데이트 (v1.1)
- 구현 현황: 7개 → 9개 API (64.3% 구현률)
- 신규 구현 API 추가:
  * POST /api/v1/events/{eventId}/images - 이미지 생성 요청
  * PUT /api/v1/events/{eventId}/images/{imageId}/select - 이미지 선택
- API 경로 버전 명시: /api/events → /api/v1/events
- Event Creation Flow 구현률: 12.5% → 37.5%
- 변경 이력 섹션 추가
2025-10-27 15:24:28 +09:00
sunmingLee
828a76b630 channel_status 테이블 시간 역전 현상 수정
- convertToChannelStatus 메서드에 completedAt 파라미터 추가
- completedAt이 항상 createdAt 이후 시간으로 설정되도록 수정
- LocalDateTime.now() 대신 메서드 파라미터로 전달된 completedAt 사용

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 16:27:03 +09:00
sunmingLee
074be336d6 distribution-service Swagger UI 설정 추가 및 API 문서화
- OpenApiConfig 추가: Swagger UI 기본 설정 및 API 정보 정의
- application.yml: Springdoc 설정 추가 및 웹 로깅 레벨 강화
- DistributionController: Swagger 어노테이션 추가 (@Tag, @Operation, @ApiResponses)
- API path 주석 수정: /api prefix 제거하여 실제 경로와 일치
2025-10-24 14:13:23 +09:00
sunmingLee
b0d8a6d10e distribution-service API 명세서를 실제 구현에 맞게 수정
- ChannelType 열거형 값 수정 (URIDONGNETV, RINGOBIZ, GINITV 등)
- DistributionRequest 스키마 변경 (title, description, imageUrl 추가)
- DistributionResponse 스키마 변경 (success, successCount, failureCount 등)
- ChannelDistributionResult 스키마 단순화
- 모든 예제 코드 실제 구현에 맞게 업데이트
- IntelliJ 서비스 실행 프로파일 추가
- Distribution 서비스 엔티티, 매퍼, 리포지토리 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 13:45:45 +09:00
sunmingLee
9f50c7feaa distribution-service에 PostgreSQL, Redis 연결 설정 추가
- application.yml에 DB 및 캐시 설정 추가
- IntelliJ 실행 프로파일에 환경 변수 설정
- Kafka 브로커 주소를 실제 서버로 변경
2025-10-24 13:08:42 +09:00
sunmingLee
28a7a91ca2 Swagger 관련 변경사항 롤백 및 정리 2025-10-24 11:04:59 +09:00
430 changed files with 39832 additions and 3316 deletions

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-actions-cicd-guide-back" command: "/deploy-actions-cicd-guide-back"
description: "백엔드 GitHub Actions CI/CD 파이프라인 가이드 작성"
--- ---
@cicd @cicd
'백엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요. '백엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-actions-cicd-guide-front" command: "/deploy-actions-cicd-guide-front"
description: "프론트엔드 GitHub Actions CI/CD 파이프라인 가이드 작성"
--- ---
@cicd @cicd
'프론트엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요. '프론트엔드GitHubActions파이프라인작성가이드'에 따라 GitHub Actions를 이용한 CI/CD 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,5 +1,6 @@
--- ---
command: "/deploy-build-image-back" command: "/deploy-build-image-back"
description: "백엔드 컨테이너 이미지 작성"
--- ---
@cicd @cicd

View File

@ -1,5 +1,6 @@
--- ---
command: "/deploy-build-image-front" command: "/deploy-build-image-front"
description: "프론트엔드 컨테이너 이미지 작성"
--- ---
@cicd @cicd

View File

@ -1,81 +1,64 @@
--- ---
command: "/deploy-help" command: "/deploy-help"
description: "배포 작업 순서 및 명령어 안내"
--- ---
# 배포 작업 순서 # 배포 작업 순서
## 1단계: 컨테이너 이미지 작성 ## 컨테이너 이미지 작성
### 백엔드 ### 백엔드
```
/deploy-build-image-back /deploy-build-image-back
``` - 백엔드 서비스들의 컨테이너 이미지를 작성합니다
- 백엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
### 프론트엔드 ### 프론트엔드
```
/deploy-build-image-front /deploy-build-image-front
``` - 프론트엔드 서비스의 컨테이너 이미지를 작성합니다
- 프론트엔드컨테이너이미지작성가이드를 참고하여 컨테이너 이미지를 빌드합니다
## 2단계: 컨테이너 실행 가이드 작성 ## 컨테이너 실행 가이드 작성
### 백엔드 ### 백엔드
```
/deploy-run-container-guide-back /deploy-run-container-guide-back
``` - 백엔드 컨테이너 실행 가이드를 작성합니다
- 백엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다 - [실행정보] 섹션에 ACR명, VM 접속 정보 제공 필요
- 실행정보(ACR명, VM정보)가 필요합니다
### 프론트엔드 ### 프론트엔드
```
/deploy-run-container-guide-front /deploy-run-container-guide-front
``` - 프론트엔드 컨테이너 실행 가이드를 작성합니다
- 프론트엔드컨테이너실행방법가이드를 참고하여 컨테이너 실행 방법을 작성합니다 - [실행정보] 섹션에 시스템명, ACR명, VM 접속 정보 제공 필요
- 실행정보(시스템명, ACR명, VM정보)가 필요합니다
## 3단계: Kubernetes 배포 가이드 작성 ## Kubernetes 배포 가이드 작성
### 백엔드 ### 백엔드
```
/deploy-k8s-guide-back /deploy-k8s-guide-back
``` - 백엔드 서비스 Kubernetes 배포 가이드를 작성합니다
- 백엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다 - [실행정보] 섹션에 ACR명, k8s명, 네임스페이스, 리소스 정보 제공 필요
- 실행정보(ACR명, k8s명, 네임스페이스, 리소스 설정)가 필요합니다
### 프론트엔드 ### 프론트엔드
```
/deploy-k8s-guide-front /deploy-k8s-guide-front
``` - 프론트엔드 서비스 Kubernetes 배포 가이드를 작성합니다
- 프론트엔드배포가이드를 참고하여 쿠버네티스 배포 방법을 작성합니다 - [실행정보] 섹션에 시스템명, ACR명, k8s명, 네임스페이스, Gateway Host 정보 제공 필요
- 실행정보(시스템명, ACR명, k8s명, 네임스페이스, Gateway Host, 리소스 설정)가 필요합니다
## 4단계: CI/CD 파이프라인 구성 ## CI/CD 파이프라인 작성
### Jenkins CI/CD
### Jenkins 사용 시
#### 백엔드 #### 백엔드
```
/deploy-jenkins-cicd-guide-back /deploy-jenkins-cicd-guide-back
``` - Jenkins를 이용한 백엔드 CI/CD 파이프라인 가이드를 작성합니다
- 백엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다 - [실행정보] 섹션에 ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
#### 프론트엔드 #### 프론트엔드
```
/deploy-jenkins-cicd-guide-front /deploy-jenkins-cicd-guide-front
``` - Jenkins를 이용한 프론트엔드 CI/CD 파이프라인 가이드를 작성합니다
- 프론트엔드Jenkins파이프라인작성가이드를 참고하여 Jenkins CI/CD 파이프라인을 구성합니다 - [실행정보] 섹션에 SYSTEM_NAME, ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
### GitHub Actions 사용 시 ### GitHub Actions CI/CD
#### 백엔드 #### 백엔드
```
/deploy-actions-cicd-guide-back /deploy-actions-cicd-guide-back
``` - GitHub Actions를 이용한 백엔드 CI/CD 파이프라인 가이드를 작성합니다
- 백엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다 - [실행정보] 섹션에 ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
#### 프론트엔드 #### 프론트엔드
```
/deploy-actions-cicd-guide-front /deploy-actions-cicd-guide-front
``` - GitHub Actions를 이용한 프론트엔드 CI/CD 파이프라인 가이드를 작성합니다
- 프론트엔드GitHubActions파이프라인작성가이드를 참고하여 GitHub Actions CI/CD 파이프라인을 구성합니다 - [실행정보] 섹션에 SYSTEM_NAME, ACR_NAME, RESOURCE_GROUP, AKS_CLUSTER, NAMESPACE 제공 필요
## 참고사항 ---
- 각 명령 실행 전 필요한 실행정보를 프롬프트에 포함해야 합니다
- 실행정보가 없으면 안내 메시지가 표시되며 작업이 중단됩니다 **참고**: 각 명령어 실행 시 [실행정보] 섹션에 필요한 정보를 함께 제공해야 합니다.
- CI/CD 도구는 Jenkins 또는 GitHub Actions 중 선택하여 사용합니다

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-jenkins-cicd-guide-back" command: "/deploy-jenkins-cicd-guide-back"
description: "백엔드 Jenkins CI/CD 파이프라인 가이드 작성"
--- ---
@cicd @cicd
'백엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요. '백엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-jenkins-cicd-guide-front" command: "/deploy-jenkins-cicd-guide-front"
description: "프론트엔드 Jenkins CI/CD 파이프라인 가이드 작성"
--- ---
@cicd @cicd
'프론트엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요. '프론트엔드Jenkins파이프라인작성가이드'에 따라 Jenkins를 이용한 CI/CD 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-k8s-guide-back" command: "/deploy-k8s-guide-back"
description: "백엔드 Kubernetes 배포 가이드 작성"
--- ---
@cicd @cicd
'백엔드배포가이드'에 따라 백엔드 서비스 배포 방법을 작성해 주세요. '백엔드배포가이드'에 따라 백엔드 서비스 배포 방법을 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-k8s-guide-front" command: "/deploy-k8s-guide-front"
description: "프론트엔드 Kubernetes 배포 가이드 작성"
--- ---
@cicd @cicd
'프론트엔드배포가이드'에 따라 프론트엔드 서비스 배포 방법을 작성해 주세요. '프론트엔드배포가이드'에 따라 프론트엔드 서비스 배포 방법을 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-run-container-guide-back" command: "/deploy-run-container-guide-back"
description: "백엔드 컨테이너 실행방법 가이드 작성"
--- ---
@cicd @cicd
'백엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요. '백엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

View File

@ -1,10 +1,13 @@
--- ---
command: "/deploy-run-container-guide-front" command: "/deploy-run-container-guide-front"
description: "프론트엔드 컨테이너 실행방법 가이드 작성"
--- ---
@cicd @cicd
'프론트엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요. '프론트엔드컨테이너실행방법가이드'에 따라 컨테이너 실행 가이드를 작성해 주세요.
프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요. 프롬프트에 '[실행정보]'항목이 없으면 수행을 중단하고 안내 메시지를 표시해 주세요.
{안내메시지} {안내메시지}
'[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요. '[실행정보]'섹션 하위에 아래 예와 같이 필요한 정보를 제시해 주세요.
[실행정보] [실행정보]

186
.github/README.md vendored Normal file
View File

@ -0,0 +1,186 @@
# KT Event Marketing - CI/CD Infrastructure
이 디렉토리는 KT Event Marketing 백엔드 서비스의 CI/CD 인프라를 포함합니다.
## 디렉토리 구조
```
.github/
├── README.md # 이 파일
├── workflows/
│ └── backend-cicd.yaml # GitHub Actions 워크플로우
├── kustomize/ # Kubernetes 매니페스트 관리
│ ├── base/ # 기본 리소스 정의
│ │ ├── kustomization.yaml
│ │ ├── cm-common.yaml
│ │ ├── secret-common.yaml
│ │ ├── secret-imagepull.yaml
│ │ ├── ingress.yaml
│ │ └── {service}-*.yaml # 각 서비스별 리소스
│ └── overlays/ # 환경별 설정
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── *-patch.yaml # 1 replica, 256Mi-1024Mi
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── *-patch.yaml # 2 replicas, 512Mi-2048Mi
│ └── prod/
│ ├── kustomization.yaml
│ └── *-patch.yaml # 3 replicas, 1024Mi-4096Mi
├── config/
│ ├── deploy_env_vars_dev # Dev 환경 변수
│ ├── deploy_env_vars_staging # Staging 환경 변수
│ └── deploy_env_vars_prod # Prod 환경 변수
└── scripts/
├── deploy.sh # 수동 배포 스크립트
├── generate-patches.sh # 패치 파일 생성 스크립트
└── copy-manifests-to-base.py # 매니페스트 복사 스크립트
```
## 주요 파일 설명
### workflows/backend-cicd.yaml
GitHub Actions 워크플로우 정의 파일입니다.
**트리거**:
- develop 브랜치 push → dev 환경 배포
- main 브랜치 push → prod 환경 배포
- Manual workflow dispatch → 원하는 환경과 서비스 선택
**Jobs**:
1. `detect-changes`: 변경된 서비스 감지
2. `build-and-push`: 서비스 빌드 및 ACR 푸시
3. `deploy`: AKS에 배포
4. `notify`: 배포 결과 알림
### kustomize/base/kustomization.yaml
모든 환경에서 공통으로 사용하는 기본 리소스를 정의합니다.
**포함 리소스**:
- Common ConfigMaps and Secrets
- Ingress
- 7개 서비스의 Deployment, Service, ConfigMap, Secret
### kustomize/overlays/{env}/kustomization.yaml
환경별 설정을 오버라이드합니다.
**주요 차이점**:
- 이미지 태그 (dev/staging/prod)
- Replica 수 (1/2/3)
- 리소스 할당량 (작음/중간/큼)
### scripts/deploy.sh
로컬에서 수동 배포를 위한 스크립트입니다.
**사용법**:
```bash
# 모든 서비스를 dev 환경에 배포
./scripts/deploy.sh dev
# 특정 서비스만 prod 환경에 배포
./scripts/deploy.sh prod user-service
```
## 배포 프로세스
### 자동 배포 (GitHub Actions)
1. **Dev 환경**:
```bash
git checkout develop
git push origin develop
```
2. **Prod 환경**:
```bash
git checkout main
git merge develop
git push origin main
```
3. **수동 배포**:
- GitHub Actions UI → Run workflow
- Environment 선택 (dev/staging/prod)
- Service 선택 (all 또는 특정 서비스)
### 수동 배포 (로컬)
```bash
# 사전 요구사항: Azure CLI, kubectl, kustomize 설치
# Azure 로그인 필요
# Dev 환경에 모든 서비스 배포
./.github/scripts/deploy.sh dev
# Prod 환경에 user-service만 배포
./.github/scripts/deploy.sh prod user-service
```
## 환경별 설정
| 환경 | 브랜치 | 이미지 태그 | Replicas | CPU Request | Memory Request |
|------|--------|-------------|----------|-------------|----------------|
| Dev | develop | dev | 1 | 256m | 256Mi |
| Staging | manual | staging | 2 | 512m | 512Mi |
| Prod | main | prod | 3 | 1024m | 1024Mi |
## 서비스 목록
1. **user-service** (8081) - 사용자 관리
2. **event-service** (8082) - 이벤트 관리
3. **ai-service** (8083) - AI 기반 콘텐츠 생성
4. **content-service** (8084) - 콘텐츠 관리
5. **distribution-service** (8085) - 경품 배포
6. **participation-service** (8086) - 이벤트 참여
7. **analytics-service** (8087) - 분석 및 통계
## 모니터링
### Pod 상태 확인
```bash
kubectl get pods -n kt-event-marketing
```
### 로그 확인
```bash
# 실시간 로그
kubectl logs -n kt-event-marketing -l app=user-service -f
# 이전 컨테이너 로그
kubectl logs -n kt-event-marketing <pod-name> --previous
```
### 리소스 사용량
```bash
# Pod 리소스
kubectl top pods -n kt-event-marketing
# Node 리소스
kubectl top nodes
```
## 트러블슈팅
상세한 트러블슈팅 가이드는 [deployment/cicd/CICD-GUIDE.md](../../deployment/cicd/CICD-GUIDE.md)를 참조하세요.
**주요 문제 해결**:
- ImagePullBackOff → ACR Secret 확인
- CrashLoopBackOff → 로그 확인 및 환경 변수 검증
- Readiness Probe Failed → Context Path 및 Actuator 경로 확인
## 롤백
```bash
# 이전 버전으로 롤백
kubectl rollout undo deployment/user-service -n kt-event-marketing
# 특정 리비전으로 롤백
kubectl rollout undo deployment/user-service --to-revision=2 -n kt-event-marketing
```
## 참고 자료
- [CI/CD 가이드 (한글)](../../deployment/cicd/CICD-GUIDE.md)
- [GitHub Actions 공식 문서](https://docs.github.com/en/actions)
- [Kustomize 공식 문서](https://kustomize.io/)
- [Azure AKS 공식 문서](https://docs.microsoft.com/en-us/azure/aks/)

11
.github/config/deploy_env_vars_dev vendored Normal file
View File

@ -0,0 +1,11 @@
# Development Environment Variables
ENVIRONMENT=dev
ACR_NAME=acrdigitalgarage01
RESOURCE_GROUP=rg-digitalgarage-01
AKS_CLUSTER=aks-digitalgarage-01
NAMESPACE=kt-event-marketing
REPLICAS=1
CPU_REQUEST=256m
MEMORY_REQUEST=256Mi
CPU_LIMIT=1024m
MEMORY_LIMIT=1024Mi

11
.github/config/deploy_env_vars_prod vendored Normal file
View File

@ -0,0 +1,11 @@
# Production Environment Variables
ENVIRONMENT=prod
ACR_NAME=acrdigitalgarage01
RESOURCE_GROUP=rg-digitalgarage-01
AKS_CLUSTER=aks-digitalgarage-01
NAMESPACE=kt-event-marketing
REPLICAS=3
CPU_REQUEST=1024m
MEMORY_REQUEST=1024Mi
CPU_LIMIT=4096m
MEMORY_LIMIT=4096Mi

11
.github/config/deploy_env_vars_staging vendored Normal file
View File

@ -0,0 +1,11 @@
# Staging Environment Variables
ENVIRONMENT=staging
ACR_NAME=acrdigitalgarage01
RESOURCE_GROUP=rg-digitalgarage-01
AKS_CLUSTER=aks-digitalgarage-01
NAMESPACE=kt-event-marketing
REPLICAS=2
CPU_REQUEST=512m
MEMORY_REQUEST=512Mi
CPU_LIMIT=2048m
MEMORY_LIMIT=2048Mi

View File

@ -0,0 +1,55 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-ai-service
data:
# Server Configuration
SERVER_PORT: "8083"
# Redis Configuration (service-specific)
REDIS_DATABASE: "3"
REDIS_TIMEOUT: "3000"
REDIS_POOL_MIN: "2"
# Kafka Configuration (service-specific)
KAFKA_CONSUMER_GROUP: "ai-service-consumers"
# Kafka Topics Configuration
KAFKA_TOPICS_AI_JOB: "ai-event-generation-job"
KAFKA_TOPICS_AI_JOB_DLQ: "ai-event-generation-job-dlq"
# AI Provider Configuration
AI_PROVIDER: "CLAUDE"
AI_CLAUDE_API_URL: "https://api.anthropic.com/v1/messages"
AI_CLAUDE_ANTHROPIC_VERSION: "2023-06-01"
AI_CLAUDE_MODEL: "claude-sonnet-4-5-20250929"
AI_CLAUDE_MAX_TOKENS: "4096"
AI_CLAUDE_TEMPERATURE: "0.7"
AI_CLAUDE_TIMEOUT: "300000"
# Circuit Breaker Configuration
RESILIENCE4J_CIRCUITBREAKER_FAILURE_RATE_THRESHOLD: "50"
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_RATE_THRESHOLD: "50"
RESILIENCE4J_CIRCUITBREAKER_SLOW_CALL_DURATION_THRESHOLD: "60s"
RESILIENCE4J_CIRCUITBREAKER_PERMITTED_CALLS_HALF_OPEN: "3"
RESILIENCE4J_CIRCUITBREAKER_SLIDING_WINDOW_SIZE: "10"
RESILIENCE4J_CIRCUITBREAKER_MINIMUM_CALLS: "5"
RESILIENCE4J_CIRCUITBREAKER_WAIT_DURATION_OPEN: "60s"
RESILIENCE4J_TIMELIMITER_TIMEOUT_DURATION: "300s"
# Redis Cache TTL Configuration (seconds)
CACHE_TTL_RECOMMENDATION: "86400"
CACHE_TTL_JOB_STATUS: "86400"
CACHE_TTL_TREND: "3600"
CACHE_TTL_FALLBACK: "604800"
# Logging Configuration
LOG_LEVEL_ROOT: "INFO"
LOG_LEVEL_AI: "DEBUG"
LOG_LEVEL_KAFKA: "INFO"
LOG_LEVEL_REDIS: "INFO"
LOG_LEVEL_RESILIENCE4J: "DEBUG"
LOG_FILE_NAME: "logs/ai-service.log"
LOG_FILE_MAX_SIZE: "10MB"
LOG_FILE_MAX_HISTORY: "7"
LOG_FILE_TOTAL_CAP: "100MB"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-service
labels:
app: ai-service
spec:
replicas: 1
selector:
matchLabels:
app: ai-service
template:
metadata:
labels:
app: ai-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: ai-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8083
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-ai-service
- secretRef:
name: secret-common
- secretRef:
name: secret-ai-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /actuator/health
port: 8083
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8083
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8083
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-ai-service
type: Opaque
stringData:
# Claude API Key
AI_CLAUDE_API_KEY: "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"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: ai-service
labels:
app: ai-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8083
protocol: TCP
name: http
selector:
app: ai-service

View File

@ -0,0 +1,37 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-analytics-service
data:
# Server Configuration
SERVER_PORT: "8086"
# Database Configuration
DB_HOST: "analytic-postgresql"
DB_PORT: "5432"
DB_NAME: "analytics_db"
DB_USERNAME: "eventuser"
# Redis Configuration (service-specific)
REDIS_DATABASE: "5"
# Kafka Configuration (service-specific)
KAFKA_ENABLED: "true"
KAFKA_CONSUMER_GROUP_ID: "analytics-service"
# Sample Data Configuration (MVP only)
SAMPLE_DATA_ENABLED: "true"
# Batch Scheduler Configuration
BATCH_REFRESH_INTERVAL: "300000" # 5분 (밀리초)
BATCH_INITIAL_DELAY: "30000" # 30초 (밀리초)
BATCH_ENABLED: "true"
# Logging Configuration
LOG_LEVEL_APP: "INFO"
LOG_LEVEL_WEB: "INFO"
LOG_LEVEL_SQL: "WARN"
LOG_LEVEL_SQL_TYPE: "WARN"
SHOW_SQL: "false"
DDL_AUTO: "update"
LOG_FILE: "logs/analytics-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: analytics-service
labels:
app: analytics-service
spec:
replicas: 1
selector:
matchLabels:
app: analytics-service
template:
metadata:
labels:
app: analytics-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: analytics-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8086
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-analytics-service
- secretRef:
name: secret-common
- secretRef:
name: secret-analytics-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8086
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8086
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8086
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-analytics-service
type: Opaque
stringData:
DB_PASSWORD: "Hi5Jessica!"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: analytics-service
labels:
app: analytics-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8086
protocol: TCP
name: http
selector:
app: analytics-service

46
.github/kustomize/base/cm-common.yaml vendored Normal file
View File

@ -0,0 +1,46 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-common
data:
# Redis Configuration
REDIS_ENABLED: "true"
REDIS_HOST: "redis"
REDIS_PORT: "6379"
REDIS_TIMEOUT: "2000ms"
REDIS_POOL_MAX: "8"
REDIS_POOL_IDLE: "8"
REDIS_POOL_MIN: "0"
REDIS_POOL_WAIT: "-1ms"
# Kafka Configuration
KAFKA_BOOTSTRAP_SERVERS: "20.249.182.13:9095,4.217.131.59:9095"
EXCLUDE_KAFKA: ""
EXCLUDE_REDIS: ""
# CORS Configuration
CORS_ALLOWED_ORIGINS: "http://localhost:8081,http://localhost:8082,http://localhost:8083,http://localhost:8084,http://kt-event-marketing.20.214.196.128.nip.io"
CORS_ALLOWED_METHODS: "GET,POST,PUT,DELETE,OPTIONS,PATCH"
CORS_ALLOWED_HEADERS: "*"
CORS_ALLOW_CREDENTIALS: "true"
CORS_MAX_AGE: "3600"
# JWT Configuration
JWT_ACCESS_TOKEN_VALIDITY: "604800000"
JWT_REFRESH_TOKEN_VALIDITY: "86400000"
# JPA Configuration
DDL_AUTO: "update"
SHOW_SQL: "false"
JPA_DIALECT: "org.hibernate.dialect.PostgreSQLDialect"
H2_CONSOLE_ENABLED: "false"
# Logging Configuration
LOG_LEVEL_APP: "INFO"
LOG_LEVEL_WEB: "INFO"
LOG_LEVEL_SQL: "WARN"
LOG_LEVEL_SQL_TYPE: "WARN"
LOG_LEVEL_ROOT: "INFO"
LOG_FILE_MAX_SIZE: "10MB"
LOG_FILE_MAX_HISTORY: "7"
LOG_FILE_TOTAL_CAP: "100MB"

View File

@ -0,0 +1,24 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-content-service
data:
# Server Configuration
SERVER_PORT: "8084"
# Redis Configuration (service-specific)
REDIS_DATABASE: "1"
# Replicate API Configuration (Stable Diffusion)
REPLICATE_API_URL: "https://api.replicate.com"
REPLICATE_MODEL_VERSION: "stability-ai/sdxl:39ed52f2a78e934b3ba6e2a89f5b1c712de7dfea535525255b1aa35c5565e08b"
# HuggingFace API Configuration
HUGGINGFACE_API_URL: "https://api-inference.huggingface.co"
HUGGINGFACE_MODEL: "runwayml/stable-diffusion-v1-5"
# Azure Blob Storage Configuration
AZURE_CONTAINER_NAME: "content-images"
# Logging Configuration
LOG_FILE_PATH: "logs/content-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: content-service
labels:
app: content-service
spec:
replicas: 1
selector:
matchLabels:
app: content-service
template:
metadata:
labels:
app: content-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: content-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8084
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-content-service
- secretRef:
name: secret-common
- secretRef:
name: secret-content-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /api/v1/content/actuator/health
port: 8084
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /api/v1/content/actuator/health/readiness
port: 8084
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/v1/content/actuator/health/liveness
port: 8084
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,14 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-content-service
type: Opaque
stringData:
# Azure Blob Storage Connection String
AZURE_STORAGE_CONNECTION_STRING: "DefaultEndpointsProtocol=https;AccountName=blobkteventstorage;AccountKey=tcBN7mAfojbl0uGsOpU7RNuKNhHnzmwDiWjN31liSMVSrWaEK+HHnYKZrjBXXAC6ZPsuxUDlsf8x+AStd++QYg==;EndpointSuffix=core.windows.net"
# Replicate API Token
REPLICATE_API_TOKEN: ""
# HuggingFace API Token
HUGGINGFACE_API_TOKEN: ""

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: content-service
labels:
app: content-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8084
protocol: TCP
name: http
selector:
app: content-service

View File

@ -0,0 +1,28 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-distribution-service
data:
# Server Configuration
SERVER_PORT: "8085"
# Database Configuration
DB_HOST: "distribution-postgresql"
DB_PORT: "5432"
DB_NAME: "distributiondb"
DB_USERNAME: "eventuser"
# Kafka Configuration
KAFKA_ENABLED: "true"
KAFKA_CONSUMER_GROUP: "distribution-service"
# External Channel APIs
URIDONGNETV_API_URL: "http://localhost:9001/api/uridongnetv"
RINGOBIZ_API_URL: "http://localhost:9002/api/ringobiz"
GINITV_API_URL: "http://localhost:9003/api/ginitv"
INSTAGRAM_API_URL: "http://localhost:9004/api/instagram"
NAVER_API_URL: "http://localhost:9005/api/naver"
KAKAO_API_URL: "http://localhost:9006/api/kakao"
# Logging Configuration
LOG_FILE: "logs/distribution-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: distribution-service
labels:
app: distribution-service
spec:
replicas: 1
selector:
matchLabels:
app: distribution-service
template:
metadata:
labels:
app: distribution-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: distribution-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8085
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-distribution-service
- secretRef:
name: secret-common
- secretRef:
name: secret-distribution-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /api/v1/distribution/actuator/health
port: 8085
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /api/v1/distribution/actuator/health/readiness
port: 8085
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /api/v1/distribution/actuator/health/liveness
port: 8085
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-distribution-service
type: Opaque
stringData:
DB_PASSWORD: "Hi5Jessica!"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: distribution-service
labels:
app: distribution-service
spec:
type: ClusterIP
selector:
app: distribution-service
ports:
- name: http
port: 80
targetPort: 8085
protocol: TCP

View File

@ -0,0 +1,28 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-event-service
data:
# Server Configuration
SERVER_PORT: "8080"
# Database Configuration
DB_HOST: "event-postgresql"
DB_PORT: "5432"
DB_NAME: "eventdb"
DB_USERNAME: "eventuser"
# Redis Configuration (service-specific)
REDIS_DATABASE: "2"
# Kafka Configuration (service-specific)
KAFKA_CONSUMER_GROUP: "event-service-consumers"
# Service URLs
CONTENT_SERVICE_URL: "http://content-service"
DISTRIBUTION_SERVICE_URL: "http://distribution-service"
# Logging Configuration
LOG_LEVEL: "INFO"
SQL_LOG_LEVEL: "WARN"
LOG_FILE: "logs/event-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-service
labels:
app: event-service
spec:
replicas: 1
selector:
matchLabels:
app: event-service
template:
metadata:
labels:
app: event-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: event-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-event-service
- secretRef:
name: secret-common
- secretRef:
name: secret-event-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-event-service
type: Opaque
stringData:
# Database Password
DB_PASSWORD: "Hi5Jessica!"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: event-service
labels:
app: event-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
selector:
app: event-service

116
.github/kustomize/base/ingress.yaml vendored Normal file
View File

@ -0,0 +1,116 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: kt-event-marketing
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/use-regex: "true"
spec:
ingressClassName: nginx
rules:
- host: kt-event-marketing-api.20.214.196.128.nip.io
http:
paths:
# User Service
- path: /api/v1/users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
# Content Service
- path: /api/v1/content
pathType: Prefix
backend:
service:
name: content-service
port:
number: 80
# Event Service
- path: /api/v1/events
pathType: Prefix
backend:
service:
name: event-service
port:
number: 80
- path: /api/v1/jobs
pathType: Prefix
backend:
service:
name: event-service
port:
number: 80
- path: /api/v1/redis-test
pathType: Prefix
backend:
service:
name: event-service
port:
number: 80
# AI Service
- path: /api/v1/ai-service
pathType: Prefix
backend:
service:
name: ai-service
port:
number: 80
# Participation Service
- path: /api/v1/participations
pathType: Prefix
backend:
service:
name: participation-service
port:
number: 80
- path: /api/v1/winners
pathType: Prefix
backend:
service:
name: participation-service
port:
number: 80
- path: /debug
pathType: Prefix
backend:
service:
name: participation-service
port:
number: 80
# Analytics Service - Event Analytics
- path: /api/v1/events/([0-9]+)/analytics
pathType: ImplementationSpecific
backend:
service:
name: analytics-service
port:
number: 80
# Analytics Service - User Analytics
- path: /api/v1/users/([0-9]+)/analytics
pathType: ImplementationSpecific
backend:
service:
name: analytics-service
port:
number: 80
# Distribution Service
- path: /distribution
pathType: Prefix
backend:
service:
name: distribution-service
port:
number: 80

View File

@ -0,0 +1,76 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Common resources
resources:
# Common ConfigMaps and Secrets
- cm-common.yaml
- secret-common.yaml
- secret-imagepull.yaml
# Ingress
- ingress.yaml
# user-service
- user-service-deployment.yaml
- user-service-service.yaml
- user-service-cm-user-service.yaml
- user-service-secret-user-service.yaml
# event-service
- event-service-deployment.yaml
- event-service-service.yaml
- event-service-cm-event-service.yaml
- event-service-secret-event-service.yaml
# ai-service
- ai-service-deployment.yaml
- ai-service-service.yaml
- ai-service-cm-ai-service.yaml
- ai-service-secret-ai-service.yaml
# content-service
- content-service-deployment.yaml
- content-service-service.yaml
- content-service-cm-content-service.yaml
- content-service-secret-content-service.yaml
# distribution-service
- distribution-service-deployment.yaml
- distribution-service-service.yaml
- distribution-service-cm-distribution-service.yaml
- distribution-service-secret-distribution-service.yaml
# participation-service
- participation-service-deployment.yaml
- participation-service-service.yaml
- participation-service-cm-participation-service.yaml
- participation-service-secret-participation-service.yaml
# analytics-service
- analytics-service-deployment.yaml
- analytics-service-service.yaml
- analytics-service-cm-analytics-service.yaml
- analytics-service-secret-analytics-service.yaml
# Common labels for all resources
commonLabels:
app.kubernetes.io/managed-by: kustomize
app.kubernetes.io/part-of: kt-event-marketing
# Image tag replacement (will be overridden by overlays)
images:
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
newTag: latest
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
newTag: latest

View File

@ -0,0 +1,24 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-participation-service
data:
# Server Configuration
SERVER_PORT: "8084"
# Database Configuration
DB_HOST: "participation-postgresql"
DB_PORT: "5432"
DB_NAME: "participationdb"
DB_USERNAME: "eventuser"
# Redis Configuration (service-specific)
REDIS_DATABASE: "4"
# Kafka Configuration (service-specific)
KAFKA_CONSUMER_GROUP: "participation-service-consumers"
# Logging Configuration
LOG_LEVEL: "INFO"
SHOW_SQL: "false"
LOG_FILE: "logs/participation-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: participation-service
labels:
app: participation-service
spec:
replicas: 1
selector:
matchLabels:
app: participation-service
template:
metadata:
labels:
app: participation-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: participation-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8084
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-participation-service
- secretRef:
name: secret-common
- secretRef:
name: secret-participation-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /actuator/health/liveness
port: 8084
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 30
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8084
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8084
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-participation-service
type: Opaque
stringData:
DB_PASSWORD: "Hi5Jessica!"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: participation-service
labels:
app: participation-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8084
protocol: TCP
name: http
selector:
app: participation-service

View File

@ -0,0 +1,11 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-common
type: Opaque
stringData:
# Redis Password
REDIS_PASSWORD: "Hi5Jessica!"
# JWT Secret
JWT_SECRET: "QL0czzXckz18kHnxpaTDoWFkq+3qKO7VQXeNvf2bOoU="

View File

@ -0,0 +1,16 @@
apiVersion: v1
kind: Secret
metadata:
name: kt-event-marketing
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"acrdigitalgarage01.azurecr.io": {
"username": "acrdigitalgarage01",
"password": "+OY+rmOagorjWvQe/tTk6oqvnZI8SmNbY/Y2o5EDcY+ACRDCDbYk",
"auth": "YWNyZGlnaXRhbGdhcmFnZTAxOitPWStybU9hZ29yald2UWUvdFRrNm9xdm5aSThTbU5iWS9ZMm81RURjWStBQ1JEQ0RiWWs="
}
}
}

View File

@ -0,0 +1,31 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: cm-user-service
data:
# Server Configuration
SERVER_PORT: "8081"
# Database Configuration
DB_URL: "jdbc:postgresql://user-postgresql:5432/userdb"
DB_HOST: "user-postgresql"
DB_PORT: "5432"
DB_NAME: "userdb"
DB_USERNAME: "eventuser"
DB_DRIVER: "org.postgresql.Driver"
DB_KIND: "postgresql"
DB_POOL_MAX: "20"
DB_POOL_MIN: "5"
DB_CONN_TIMEOUT: "30000"
DB_IDLE_TIMEOUT: "600000"
DB_MAX_LIFETIME: "1800000"
DB_LEAK_THRESHOLD: "60000"
# Redis Configuration (service-specific)
REDIS_DATABASE: "0"
# Kafka Configuration (service-specific)
KAFKA_CONSUMER_GROUP: "user-service-consumers"
# Logging Configuration
LOG_FILE_PATH: "logs/user-service.log"

View File

@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
labels:
app: user-service
spec:
replicas: 1
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
imagePullSecrets:
- name: kt-event-marketing
containers:
- name: user-service
image: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8081
name: http
envFrom:
- configMapRef:
name: cm-common
- configMapRef:
name: cm-user-service
- secretRef:
name: secret-common
- secretRef:
name: secret-user-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"
startupProbe:
httpGet:
path: /actuator/health
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 30
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8081
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8081
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: Secret
metadata:
name: secret-user-service
type: Opaque
stringData:
# Database Password
DB_PASSWORD: "Hi5Jessica!"

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: user-service
labels:
app: user-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8081
protocol: TCP
name: http
selector:
app: user-service

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-service
spec:
replicas: 1
template:
spec:
containers:
- name: ai-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: analytics-service
spec:
replicas: 1
template:
spec:
containers:
- name: analytics-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: content-service
spec:
replicas: 1
template:
spec:
containers:
- name: content-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: distribution-service
spec:
replicas: 1
template:
spec:
containers:
- name: distribution-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-service
spec:
replicas: 1
template:
spec:
containers:
- name: event-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,38 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kt-event-marketing
bases:
- ../../base
# Environment-specific labels
commonLabels:
environment: dev
# Environment-specific patches
patchesStrategicMerge:
- user-service-patch.yaml
- event-service-patch.yaml
- ai-service-patch.yaml
- content-service-patch.yaml
- distribution-service-patch.yaml
- participation-service-patch.yaml
- analytics-service-patch.yaml
# Override image tags for dev environment
images:
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
newTag: dev
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
newTag: dev

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: participation-service
spec:
replicas: 1
template:
spec:
containers:
- name: participation-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 1
template:
spec:
containers:
- name: user-service
resources:
requests:
cpu: "256m"
memory: "256Mi"
limits:
cpu: "1024m"
memory: "1024Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-service
spec:
replicas: 3
template:
spec:
containers:
- name: ai-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: analytics-service
spec:
replicas: 3
template:
spec:
containers:
- name: analytics-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: content-service
spec:
replicas: 3
template:
spec:
containers:
- name: content-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: distribution-service
spec:
replicas: 3
template:
spec:
containers:
- name: distribution-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-service
spec:
replicas: 3
template:
spec:
containers:
- name: event-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,38 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kt-event-marketing
bases:
- ../../base
# Environment-specific labels
commonLabels:
environment: prod
# Environment-specific patches
patchesStrategicMerge:
- user-service-patch.yaml
- event-service-patch.yaml
- ai-service-patch.yaml
- content-service-patch.yaml
- distribution-service-patch.yaml
- participation-service-patch.yaml
- analytics-service-patch.yaml
# Override image tags for prod environment
images:
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
newTag: prod
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
newTag: prod

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: participation-service
spec:
replicas: 3
template:
spec:
containers:
- name: participation-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
template:
spec:
containers:
- name: user-service
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ai-service
spec:
replicas: 2
template:
spec:
containers:
- name: ai-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: analytics-service
spec:
replicas: 2
template:
spec:
containers:
- name: analytics-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: content-service
spec:
replicas: 2
template:
spec:
containers:
- name: content-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: distribution-service
spec:
replicas: 2
template:
spec:
containers:
- name: distribution-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: event-service
spec:
replicas: 2
template:
spec:
containers:
- name: event-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,38 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: kt-event-marketing
bases:
- ../../base
# Environment-specific labels
commonLabels:
environment: staging
# Environment-specific patches
patchesStrategicMerge:
- user-service-patch.yaml
- event-service-patch.yaml
- ai-service-patch.yaml
- content-service-patch.yaml
- distribution-service-patch.yaml
- participation-service-patch.yaml
- analytics-service-patch.yaml
# Override image tags for staging environment
images:
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service
newTag: staging
- name: acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service
newTag: staging

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: participation-service
spec:
replicas: 2
template:
spec:
containers:
- name: participation-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,17 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 2
template:
spec:
containers:
- name: user-service
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"

View File

@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
Copy K8s manifests to Kustomize base directory and remove namespace declarations
"""
import os
import shutil
import yaml
from pathlib import Path
# Service names
SERVICES = [
'user-service',
'event-service',
'ai-service',
'content-service',
'distribution-service',
'participation-service',
'analytics-service'
]
# Base directories
SOURCE_DIR = Path('deployment/k8s')
BASE_DIR = Path('.github/kustomize/base')
def remove_namespace_from_yaml(content):
"""Remove namespace field from YAML content"""
docs = list(yaml.safe_load_all(content))
for doc in docs:
if doc and isinstance(doc, dict):
if 'metadata' in doc and 'namespace' in doc['metadata']:
del doc['metadata']['namespace']
return yaml.dump_all(docs, default_flow_style=False, sort_keys=False)
def copy_and_process_file(source_path, dest_path):
"""Copy file and remove namespace declaration"""
with open(source_path, 'r', encoding='utf-8') as f:
content = f.read()
# Remove namespace from YAML
processed_content = remove_namespace_from_yaml(content)
# Write to destination
dest_path.parent.mkdir(parents=True, exist_ok=True)
with open(dest_path, 'w', encoding='utf-8') as f:
f.write(processed_content)
print(f"✓ Copied and processed: {source_path} -> {dest_path}")
def main():
print("Starting manifest copy to Kustomize base...")
# Copy common resources
print("\n[Common Resources]")
common_dir = SOURCE_DIR / 'common'
for file in ['cm-common.yaml', 'secret-common.yaml', 'secret-imagepull.yaml', 'ingress.yaml']:
source = common_dir / file
if source.exists():
dest = BASE_DIR / file
copy_and_process_file(source, dest)
# Copy service-specific resources
print("\n[Service Resources]")
for service in SERVICES:
service_dir = SOURCE_DIR / service
if not service_dir.exists():
print(f"⚠ Service directory not found: {service_dir}")
continue
print(f"\nProcessing {service}...")
for file in service_dir.glob('*.yaml'):
dest = BASE_DIR / f"{service}-{file.name}"
copy_and_process_file(file, dest)
print("\n✅ All manifests copied to base directory!")
if __name__ == '__main__':
main()

181
.github/scripts/deploy.sh vendored Normal file
View File

@ -0,0 +1,181 @@
#!/bin/bash
set -e
###############################################################################
# Backend Services Deployment Script for AKS
#
# Usage:
# ./deploy.sh <environment> [service-name]
#
# Arguments:
# environment - Target environment (dev, staging, prod)
# service-name - Specific service to deploy (optional, deploys all if not specified)
#
# Examples:
# ./deploy.sh dev # Deploy all services to dev
# ./deploy.sh prod user-service # Deploy only user-service to prod
###############################################################################
# Color output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Functions
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Validate arguments
if [ $# -lt 1 ]; then
log_error "Usage: $0 <environment> [service-name]"
log_error "Environment must be one of: dev, staging, prod"
exit 1
fi
ENVIRONMENT=$1
SERVICE=${2:-all}
# Validate environment
if [[ ! "$ENVIRONMENT" =~ ^(dev|staging|prod)$ ]]; then
log_error "Invalid environment: $ENVIRONMENT"
log_error "Must be one of: dev, staging, prod"
exit 1
fi
# Load environment variables
ENV_FILE=".github/config/deploy_env_vars_${ENVIRONMENT}"
if [ ! -f "$ENV_FILE" ]; then
log_error "Environment file not found: $ENV_FILE"
exit 1
fi
source "$ENV_FILE"
log_info "Loaded environment configuration: $ENVIRONMENT"
# Service list
SERVICES=(
"user-service"
"event-service"
"ai-service"
"content-service"
"distribution-service"
"participation-service"
"analytics-service"
)
# Validate service if specified
if [ "$SERVICE" != "all" ]; then
if [[ ! " ${SERVICES[@]} " =~ " ${SERVICE} " ]]; then
log_error "Invalid service: $SERVICE"
log_error "Must be one of: ${SERVICES[*]}"
exit 1
fi
SERVICES=("$SERVICE")
fi
log_info "Services to deploy: ${SERVICES[*]}"
# Check prerequisites
log_info "Checking prerequisites..."
if ! command -v az &> /dev/null; then
log_error "Azure CLI not found. Please install Azure CLI."
exit 1
fi
if ! command -v kubectl &> /dev/null; then
log_error "kubectl not found. Please install kubectl."
exit 1
fi
if ! command -v kustomize &> /dev/null; then
log_error "kustomize not found. Please install kustomize."
exit 1
fi
# Azure login check
log_info "Checking Azure authentication..."
if ! az account show &> /dev/null; then
log_error "Not logged in to Azure. Please run 'az login'"
exit 1
fi
# Get AKS credentials
log_info "Getting AKS credentials..."
az aks get-credentials \
--resource-group "$RESOURCE_GROUP" \
--name "$AKS_CLUSTER" \
--overwrite-existing
# Check namespace
log_info "Checking namespace: $NAMESPACE"
if ! kubectl get namespace "$NAMESPACE" &> /dev/null; then
log_warn "Namespace $NAMESPACE does not exist. Creating..."
kubectl create namespace "$NAMESPACE"
fi
# Build and deploy with Kustomize
OVERLAY_DIR=".github/kustomize/overlays/${ENVIRONMENT}"
if [ ! -d "$OVERLAY_DIR" ]; then
log_error "Kustomize overlay directory not found: $OVERLAY_DIR"
exit 1
fi
log_info "Building Kustomize manifests for $ENVIRONMENT..."
cd "$OVERLAY_DIR"
# Update image tags
log_info "Updating image tags to: $ENVIRONMENT"
kustomize edit set image \
${ACR_NAME}.azurecr.io/kt-event-marketing/user-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/event-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/ai-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/content-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/distribution-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/participation-service:${ENVIRONMENT} \
${ACR_NAME}.azurecr.io/kt-event-marketing/analytics-service:${ENVIRONMENT}
# Apply manifests
log_info "Applying manifests to AKS..."
kustomize build . | kubectl apply -f -
cd - > /dev/null
# Wait for deployments
log_info "Waiting for deployments to be ready..."
for service in "${SERVICES[@]}"; do
log_info "Waiting for $service deployment..."
if ! kubectl rollout status deployment/"$service" -n "$NAMESPACE" --timeout=5m; then
log_error "Deployment of $service failed!"
exit 1
fi
log_info "$service is ready"
done
# Verify deployment
log_info "Verifying deployment..."
echo ""
echo "=== Pods Status ==="
kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/part-of=kt-event-marketing
echo ""
echo "=== Services ==="
kubectl get svc -n "$NAMESPACE"
echo ""
echo "=== Ingress ==="
kubectl get ingress -n "$NAMESPACE"
log_info "Deployment completed successfully!"
log_info "Environment: $ENVIRONMENT"
log_info "Services: ${SERVICES[*]}"

51
.github/scripts/generate-patches.sh vendored Normal file
View File

@ -0,0 +1,51 @@
#!/bin/bash
SERVICES=(user-service event-service ai-service content-service distribution-service participation-service analytics-service)
# Staging patches (2 replicas, increased resources)
for service in "${SERVICES[@]}"; do
cat > ".github/kustomize/overlays/staging/${service}-patch.yaml" << YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${service}
spec:
replicas: 2
template:
spec:
containers:
- name: ${service}
resources:
requests:
cpu: "512m"
memory: "512Mi"
limits:
cpu: "2048m"
memory: "2048Mi"
YAML
done
# Prod patches (3 replicas, maximum resources)
for service in "${SERVICES[@]}"; do
cat > ".github/kustomize/overlays/prod/${service}-patch.yaml" << YAML
apiVersion: apps/v1
kind: Deployment
metadata:
name: ${service}
spec:
replicas: 3
template:
spec:
containers:
- name: ${service}
resources:
requests:
cpu: "1024m"
memory: "1024Mi"
limits:
cpu: "4096m"
memory: "4096Mi"
YAML
done
echo "✅ Generated all patch files for staging and prod"

207
.github/workflows/backend-cicd.yaml vendored Normal file
View File

@ -0,0 +1,207 @@
name: Backend CI/CD Pipeline
on:
push:
branches:
- develop
- main
paths:
- '*-service/**'
- '.github/workflows/backend-cicd.yaml'
- '.github/kustomize/**'
pull_request:
branches:
- develop
- main
paths:
- '*-service/**'
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- dev
- staging
- prod
service:
description: 'Service to deploy (all for all services)'
required: true
default: 'all'
env:
ACR_NAME: acrdigitalgarage01
RESOURCE_GROUP: rg-digitalgarage-01
AKS_CLUSTER: aks-digitalgarage-01
NAMESPACE: kt-event-marketing
JDK_VERSION: '21'
jobs:
detect-changes:
name: Detect Changed Services
runs-on: ubuntu-latest
outputs:
services: ${{ steps.detect.outputs.services }}
environment: ${{ steps.env.outputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine environment
id: env
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "environment=prod" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" = "refs/heads/develop" ]; then
echo "environment=dev" >> $GITHUB_OUTPUT
else
echo "environment=dev" >> $GITHUB_OUTPUT
fi
- name: Detect changed services
id: detect
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.service }}" != "all" ]; then
echo "services=[\"${{ github.event.inputs.service }}\"]" >> $GITHUB_OUTPUT
elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ github.event.inputs.service }}" = "all" ]; then
echo "services=[\"user-service\",\"event-service\",\"ai-service\",\"content-service\",\"distribution-service\",\"participation-service\",\"analytics-service\"]" >> $GITHUB_OUTPUT
else
CHANGED_SERVICES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | \
grep -E '^(user|event|ai|content|distribution|participation|analytics)-service/' | \
cut -d'/' -f1 | sort -u | \
jq -R -s -c 'split("\n") | map(select(length > 0))')
if [ "$CHANGED_SERVICES" = "[]" ] || [ -z "$CHANGED_SERVICES" ]; then
echo "services=[\"user-service\",\"event-service\",\"ai-service\",\"content-service\",\"distribution-service\",\"participation-service\",\"analytics-service\"]" >> $GITHUB_OUTPUT
else
echo "services=$CHANGED_SERVICES" >> $GITHUB_OUTPUT
fi
fi
build-and-push:
name: Build and Push - ${{ matrix.service }}
needs: detect-changes
runs-on: ubuntu-latest
strategy:
matrix:
service: ${{ fromJson(needs.detect-changes.outputs.services) }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK ${{ env.JDK_VERSION }}
uses: actions/setup-java@v4
with:
java-version: ${{ env.JDK_VERSION }}
distribution: 'temurin'
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew ${{ matrix.service }}:build -x test
# - name: Run tests
# run: ./gradlew ${{ matrix.service }}:test
- name: Build JAR
run: ./gradlew ${{ matrix.service }}:bootJar
- name: Log in to Azure Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.ACR_NAME }}.azurecr.io
username: ${{ secrets.ACR_USERNAME }}
password: ${{ secrets.ACR_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./${{ matrix.service }}
file: ./${{ matrix.service }}/Dockerfile
push: true
tags: |
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:${{ needs.detect-changes.outputs.environment }}
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:${{ github.sha }}
${{ env.ACR_NAME }}.azurecr.io/kt-event-marketing/${{ matrix.service }}:latest
deploy:
name: Deploy to AKS - ${{ needs.detect-changes.outputs.environment }}
needs: [detect-changes, build-and-push]
runs-on: ubuntu-latest
environment: ${{ needs.detect-changes.outputs.environment }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get AKS credentials
run: |
az aks get-credentials \
--resource-group ${{ env.RESOURCE_GROUP }} \
--name ${{ env.AKS_CLUSTER }} \
--overwrite-existing
- name: Setup Kustomize
uses: imranismail/setup-kustomize@v2
- name: Deploy with Kustomize
run: |
cd .github/kustomize/overlays/${{ needs.detect-changes.outputs.environment }}
kustomize edit set image \
acrdigitalgarage01.azurecr.io/kt-event-marketing/user-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/event-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/ai-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/content-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/distribution-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/participation-service:${{ needs.detect-changes.outputs.environment }} \
acrdigitalgarage01.azurecr.io/kt-event-marketing/analytics-service:${{ needs.detect-changes.outputs.environment }}
kustomize build . | kubectl apply -f -
- name: Wait for deployment rollout
run: |
for service in $(echo '${{ needs.detect-changes.outputs.services }}' | jq -r '.[]'); do
echo "Waiting for ${service} deployment..."
kubectl rollout status deployment/${service} -n ${{ env.NAMESPACE }} --timeout=5m
done
- name: Verify deployment
run: |
echo "=== Pods Status ==="
kubectl get pods -n ${{ env.NAMESPACE }} -l app.kubernetes.io/part-of=kt-event-marketing
echo "=== Services ==="
kubectl get svc -n ${{ env.NAMESPACE }}
echo "=== Ingress ==="
kubectl get ingress -n ${{ env.NAMESPACE }}
notify:
name: Notify Deployment Result
needs: [detect-changes, deploy]
runs-on: ubuntu-latest
if: always()
steps:
- name: Deployment Success
if: needs.deploy.result == 'success'
run: |
echo "✅ Deployment to ${{ needs.detect-changes.outputs.environment }} succeeded!"
echo "Services: ${{ needs.detect-changes.outputs.services }}"
- name: Deployment Failure
if: needs.deploy.result == 'failure'
run: |
echo "❌ Deployment to ${{ needs.detect-changes.outputs.environment }} failed!"
echo "Services: ${{ needs.detect-changes.outputs.services }}"
exit 1

2
.gitignore vendored
View File

@ -61,3 +61,5 @@ k8s/**/*-local.yaml
# Gradle (로컬 환경 설정) # Gradle (로컬 환경 설정)
gradle.properties gradle.properties
*.hprof
test-data.json

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="AiServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.ai-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.ai.AiApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.ai.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8081" />
<env name="DB_HOST" value="4.230.112.141" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="aidb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
<env name="KAFKA_CONSUMER_GROUP" value="ai" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="AnalyticsServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.analytics-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.analytics.AnalyticsApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.analytics.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8087" />
<env name="DB_HOST" value="4.230.49.9" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="analyticdb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
<env name="KAFKA_CONSUMER_GROUP" value="analytic" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ContentServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.content-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.content.ContentApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.content.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8084" />
<env name="DB_HOST" value="4.217.131.139" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="contentdb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
<env name="REPLICATE_API_TOKEN" value="r8_cqE8IzQr9DZ8Dr72ozbomiXe6IFPL0005Vuq9" />
<env name="REPLICATE_MOCK_ENABLED" value="true" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="DistributionServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.distribution-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.distribution.DistributionApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.distribution.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8085" />
<env name="DB_HOST" value="4.217.133.59" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="distributiondb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
<env name="KAFKA_CONSUMER_GROUP" value="distribution-service" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,31 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="EventServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.event-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.event.EventApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.event.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8082" />
<env name="DB_HOST" value="20.249.177.232" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="eventdb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="KAFKA_BOOTSTRAP_SERVERS" value="4.230.50.63:9092" />
<env name="DISTRIBUTION_SERVICE_URL" value="http://localhost:8085" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,29 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="UserServiceApplication" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
<option name="ACTIVE_PROFILES" />
<module name="kt-event-marketing.user-service.main" />
<option name="SPRING_BOOT_MAIN_CLASS" value="com.kt.user.UserApplication" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="com.kt.user.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<envs>
<env name="SERVER_PORT" value="8083" />
<env name="DB_HOST" value="20.249.125.115" />
<env name="DB_PORT" value="5432" />
<env name="DB_NAME" value="userdb" />
<env name="DB_USERNAME" value="eventuser" />
<env name="DB_PASSWORD" value="Hi5Jessica!" />
<env name="REDIS_HOST" value="20.214.210.71" />
<env name="REDIS_PORT" value="6379" />
<env name="REDIS_PASSWORD" value="Hi5Jessica!" />
<env name="JPA_DDL_AUTO" value="update" />
<env name="JPA_SHOW_SQL" value="false" />
</envs>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@ -0,0 +1,84 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="analytics-service" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<!-- Database Configuration -->
<entry key="DB_KIND" value="postgresql" />
<entry key="DB_HOST" value="4.230.49.9" />
<entry key="DB_PORT" value="5432" />
<entry key="DB_NAME" value="analyticdb" />
<entry key="DB_USERNAME" value="eventuser" />
<entry key="DB_PASSWORD" value="Hi5Jessica!" />
<!-- JPA Configuration -->
<entry key="DDL_AUTO" value="create" />
<entry key="SHOW_SQL" value="true" />
<!-- Redis Configuration -->
<entry key="REDIS_HOST" value="20.214.210.71" />
<entry key="REDIS_PORT" value="6379" />
<entry key="REDIS_PASSWORD" value="Hi5Jessica!" />
<entry key="REDIS_DATABASE" value="5" />
<!-- Kafka Configuration (원격 서버) -->
<entry key="KAFKA_ENABLED" value="true" />
<entry key="KAFKA_BOOTSTRAP_SERVERS" value="20.249.182.13:9095,4.217.131.59:9095" />
<entry key="KAFKA_CONSUMER_GROUP_ID" value="analytics-service-consumers" />
<!-- Sample Data Configuration (MVP Only) -->
<!-- ⚠️ Kafka Producer로 이벤트 발행 (Consumer가 처리) -->
<entry key="SAMPLE_DATA_ENABLED" value="true" />
<!-- Server Configuration -->
<entry key="SERVER_PORT" value="8086" />
<!-- JWT Configuration -->
<entry key="JWT_SECRET" value="dev-jwt-secret-key-for-development-only-kt-event-marketing" />
<entry key="JWT_ACCESS_TOKEN_VALIDITY" value="1800" />
<entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400" />
<!-- CORS Configuration -->
<entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:*" />
<!-- Logging Configuration -->
<entry key="LOG_FILE" value="logs/analytics-service.log" />
<entry key="LOG_LEVEL_APP" value="DEBUG" />
<entry key="LOG_LEVEL_WEB" value="INFO" />
<entry key="LOG_LEVEL_SQL" value="DEBUG" />
<entry key="LOG_LEVEL_SQL_TYPE" value="TRACE" />
</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="analytics-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>

24
ai-service/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
# Multi-stage build for Spring Boot application
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
# Copy layers from builder
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8083/api/v1/ai-service/actuator/health || exit 1
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

View File

@ -1,3 +1,7 @@
bootJar {
archiveFileName = 'ai-service.jar'
}
dependencies { dependencies {
// Kafka Consumer // Kafka Consumer
implementation 'org.springframework.kafka:spring-kafka' implementation 'org.springframework.kafka:spring-kafka'

View File

@ -5,31 +5,33 @@ spring:
# Redis Configuration # Redis Configuration
data: data:
redis: redis:
host: ${REDIS_HOST:redis-external} # Production: redis-external, Local: 20.214.210.71 host: ${REDIS_HOST:20.214.210.71}
port: ${REDIS_PORT:6379} port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:} password: ${REDIS_PASSWORD:Hi5Jessica!}
database: ${REDIS_DATABASE:0} # AI Service uses database 3 database: ${REDIS_DATABASE:3}
timeout: ${REDIS_TIMEOUT:3000} timeout: ${REDIS_TIMEOUT:3000}
lettuce: lettuce:
pool: pool:
max-active: 8 max-active: ${REDIS_POOL_MAX:8}
max-idle: 8 max-idle: ${REDIS_POOL_IDLE:8}
min-idle: 2 min-idle: ${REDIS_POOL_MIN:2}
max-wait: -1ms max-wait: ${REDIS_POOL_WAIT:-1ms}
# Kafka Consumer Configuration # Kafka Consumer Configuration
kafka: kafka:
bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:localhost:9092} bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS:20.249.182.13:9095,4.217.131.59:9095}
consumer: consumer:
group-id: ai-service-consumers group-id: ${KAFKA_CONSUMER_GROUP:ai-service-consumers}
auto-offset-reset: earliest auto-offset-reset: earliest
enable-auto-commit: false enable-auto-commit: false
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
properties: properties:
spring.json.trusted.packages: "*" spring.json.trusted.packages: "*"
max.poll.records: ${KAFKA_MAX_POLL_RECORDS:10} spring.json.use.type.headers: false
session.timeout.ms: ${KAFKA_SESSION_TIMEOUT:30000} spring.json.value.default.type: com.kt.ai.kafka.message.AIJobMessage
max.poll.records: 10
session.timeout.ms: 30000
listener: listener:
ack-mode: manual ack-mode: manual
@ -37,7 +39,7 @@ spring:
server: server:
port: ${SERVER_PORT:8083} port: ${SERVER_PORT:8083}
servlet: servlet:
context-path: / context-path: /api/v1/ai-service
encoding: encoding:
charset: UTF-8 charset: UTF-8
enabled: true enabled: true
@ -45,13 +47,13 @@ server:
# JWT Configuration # JWT Configuration
jwt: jwt:
secret: ${JWT_SECRET:} secret: ${JWT_SECRET:kt-event-marketing-secret-key-for-development-only-please-change-in-production}
access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:1800} access-token-validity: ${JWT_ACCESS_TOKEN_VALIDITY:604800000}
refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400} refresh-token-validity: ${JWT_REFRESH_TOKEN_VALIDITY:86400}
# CORS Configuration # CORS Configuration
cors: cors:
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080} allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:*}
allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS,PATCH} allowed-methods: ${CORS_ALLOWED_METHODS:GET,POST,PUT,DELETE,OPTIONS,PATCH}
allowed-headers: ${CORS_ALLOWED_HEADERS:*} allowed-headers: ${CORS_ALLOWED_HEADERS:*}
allow-credentials: ${CORS_ALLOW_CREDENTIALS:true} allow-credentials: ${CORS_ALLOW_CREDENTIALS:true}
@ -91,45 +93,39 @@ springdoc:
# Logging Configuration # Logging Configuration
logging: logging:
level: level:
root: INFO root: ${LOG_LEVEL_ROOT:INFO}
com.kt.ai: DEBUG com.kt.ai: ${LOG_LEVEL_AI:DEBUG}
org.springframework.kafka: INFO org.springframework.kafka: ${LOG_LEVEL_KAFKA:INFO}
org.springframework.data.redis: INFO org.springframework.data.redis: ${LOG_LEVEL_REDIS:INFO}
io.github.resilience4j: DEBUG io.github.resilience4j: ${LOG_LEVEL_RESILIENCE4J:DEBUG}
pattern: pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: file:
name: ${LOG_FILE:logs/ai-service.log} name: ${LOG_FILE_NAME:logs/ai-service.log}
logback: logback:
rollingpolicy: rollingpolicy:
max-file-size: 10MB max-file-size: ${LOG_FILE_MAX_SIZE:10MB}
max-history: 7 max-history: ${LOG_FILE_MAX_HISTORY:7}
total-size-cap: 100MB total-size-cap: ${LOG_FILE_TOTAL_CAP:100MB}
# Kafka Topics Configuration # Kafka Topics Configuration
kafka: kafka:
topics: topics:
ai-job: ${KAFKA_TOPIC_AI_JOB:ai-event-generation-job} ai-job: ${KAFKA_TOPICS_AI_JOB:ai-event-generation-job}
ai-job-dlq: ${KAFKA_TOPIC_AI_JOB_DLQ:ai-event-generation-job-dlq} ai-job-dlq: ${KAFKA_TOPICS_AI_JOB_DLQ:ai-event-generation-job-dlq}
# AI External API Configuration # AI API Configuration (실제 API 사용)
ai: ai:
provider: ${AI_PROVIDER:CLAUDE}
claude: claude:
api-url: ${CLAUDE_API_URL:https://api.anthropic.com/v1/messages} api-url: ${AI_CLAUDE_API_URL:https://api.anthropic.com/v1/messages}
api-key: ${CLAUDE_API_KEY:} api-key: ${AI_CLAUDE_API_KEY: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}
anthropic-version: ${CLAUDE_ANTHROPIC_VERSION:2023-06-01} anthropic-version: ${AI_CLAUDE_ANTHROPIC_VERSION:2023-06-01}
model: ${CLAUDE_MODEL:claude-3-5-sonnet-20241022} model: ${AI_CLAUDE_MODEL:claude-sonnet-4-5-20250929}
max-tokens: ${CLAUDE_MAX_TOKENS:4096} max-tokens: ${AI_CLAUDE_MAX_TOKENS:4096}
temperature: ${CLAUDE_TEMPERATURE:0.7} temperature: ${AI_CLAUDE_TEMPERATURE:0.7}
timeout: ${CLAUDE_TIMEOUT:300000} # 5 minutes timeout: ${AI_CLAUDE_TIMEOUT:300000}
gpt4:
api-url: ${GPT4_API_URL:https://api.openai.com/v1/chat/completions}
api-key: ${GPT4_API_KEY:}
model: ${GPT4_MODEL:gpt-4-turbo-preview}
max-tokens: ${GPT4_MAX_TOKENS:4096}
timeout: ${GPT4_TIMEOUT:300000} # 5 minutes
provider: ${AI_PROVIDER:CLAUDE} # CLAUDE or GPT4
# Circuit Breaker Configuration # Circuit Breaker Configuration
resilience4j: resilience4j:

View File

@ -12,7 +12,7 @@
<entry key="DB_PASSWORD" value="Hi5Jessica!" /> <entry key="DB_PASSWORD" value="Hi5Jessica!" />
<!-- JPA Configuration --> <!-- JPA Configuration -->
<entry key="DDL_AUTO" value="update" /> <entry key="DDL_AUTO" value="create" />
<entry key="SHOW_SQL" value="true" /> <entry key="SHOW_SQL" value="true" />
<!-- Redis Configuration --> <!-- Redis Configuration -->
@ -39,7 +39,7 @@
<entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400" /> <entry key="JWT_REFRESH_TOKEN_VALIDITY" value="86400" />
<!-- CORS Configuration --> <!-- CORS Configuration -->
<entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:*" /> <entry key="CORS_ALLOWED_ORIGINS" value="http://localhost:*,http://*.nip.io:*" />
<!-- Logging Configuration --> <!-- Logging Configuration -->
<entry key="LOG_FILE" value="logs/analytics-service.log" /> <entry key="LOG_FILE" value="logs/analytics-service.log" />

View File

@ -0,0 +1,24 @@
# Multi-stage build for Spring Boot application
FROM eclipse-temurin:21-jre-alpine AS builder
WORKDIR /app
COPY build/libs/*.jar app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM eclipse-temurin:21-jre-alpine
WORKDIR /app
# Create non-root user
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
# Copy layers from builder
COPY --from=builder /app/dependencies/ ./
COPY --from=builder /app/spring-boot-loader/ ./
COPY --from=builder /app/snapshot-dependencies/ ./
COPY --from=builder /app/application/ ./
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8086/api/v1/analytics/actuator/health || exit 1
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

View File

@ -1,3 +1,7 @@
bootJar {
archiveFileName = 'analytics-service.jar'
}
dependencies { dependencies {
// Kafka Consumer // Kafka Consumer
implementation 'org.springframework.kafka:spring-kafka' implementation 'org.springframework.kafka:spring-kafka'

View File

@ -0,0 +1,108 @@
# 백엔드-프론트엔드 API 연동 검증 및 수정 결과
**작업일시**: 2025-10-28
**브랜치**: feature/analytics
**작업 범위**: Analytics Service 백엔드 DTO 및 Service 수정
---
## 📝 수정 요약
### 1⃣ 필드명 통일 (프론트엔드 호환)
**목적**: 프론트엔드 Mock 데이터 필드명과 백엔드 Response DTO 필드명 일치
| 수정 전 (백엔드) | 수정 후 (백엔드) | 프론트엔드 |
|-----------------|----------------|-----------|
| `summary.totalParticipants` | `summary.participants` | `summary.participants` ✅ |
| `channelPerformance[].channelName` | `channelPerformance[].channel` | `channelPerformance[].channel` ✅ |
| `roi.totalInvestment` | `roi.totalCost` | `roiDetail.totalCost` ✅ |
### 2⃣ 증감 데이터 추가
**목적**: 프론트엔드에서 요구하는 증감 표시 및 목표값 제공
| 필드 | 타입 | 설명 | 현재 값 |
|-----|------|------|---------|
| `summary.participantsDelta` | `Integer` | 참여자 증감 (이전 기간 대비) | `0` (TODO: 계산 로직 필요) |
| `summary.targetRoi` | `Double` | 목표 ROI (%) | EventStats에서 가져옴 |
---
## 🔧 수정 파일 목록
### DTO (Response 구조 변경)
1. **AnalyticsSummary.java**
- ✅ `totalParticipants``participants`
- ✅ `participantsDelta` 필드 추가
- ✅ `targetRoi` 필드 추가
2. **ChannelSummary.java**
- ✅ `channelName``channel`
3. **RoiSummary.java**
- ✅ `totalInvestment``totalCost`
### Entity (데이터베이스 스키마 변경)
4. **EventStats.java**
- ✅ `targetRoi` 필드 추가 (`BigDecimal`, default: 0)
### Service (비즈니스 로직 수정)
5. **AnalyticsService.java**
- ✅ `.participants()` 사용
- ✅ `.participantsDelta(0)` 추가 (TODO 마킹)
- ✅ `.targetRoi()` 추가
- ✅ `.channel()` 사용
6. **ROICalculator.java**
- ✅ `.totalCost()` 사용
7. **UserAnalyticsService.java**
- ✅ `.participants()` 사용
- ✅ `.participantsDelta(0)` 추가
- ✅ `.channel()` 사용
- ✅ `.totalCost()` 사용
---
## ✅ 검증 결과
### 컴파일 성공
\`\`\`bash
$ ./gradlew analytics-service:compileJava
BUILD SUCCESSFUL in 8s
\`\`\`
---
## 📊 데이터베이스 스키마 변경
### EventStats 테이블
\`\`\`sql
ALTER TABLE event_stats
ADD COLUMN target_roi DECIMAL(10,2) DEFAULT 0.00;
\`\`\`
**⚠️ 주의사항**
- Spring Boot JPA `ddl-auto` 설정에 따라 자동 적용됨
---
## 📌 다음 단계
### 우선순위 HIGH
1. **프론트엔드 API 연동 테스트**
2. **participantsDelta 계산 로직 구현**
3. **targetRoi 데이터 입력** (Event Service 연동)
### 우선순위 MEDIUM
4. 시간대별 분석 구현
5. 참여자 프로필 구현
6. ROI 세분화 구현

View File

@ -0,0 +1,71 @@
package com.kt.event.analytics.controller;
import com.kt.event.analytics.dto.response.UserAnalyticsDashboardResponse;
import com.kt.event.analytics.service.UserAnalyticsService;
import com.kt.event.common.dto.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
/**
* User Analytics Dashboard Controller
*
* 사용자 전체 이벤트 통합 성과 대시보드 API
*/
@Tag(name = "User Analytics", description = "사용자 전체 이벤트 통합 성과 분석 API")
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserAnalyticsDashboardController {
private final UserAnalyticsService userAnalyticsService;
/**
* 사용자 전체 성과 대시보드 조회
*
* @param userId 사용자 ID
* @param startDate 조회 시작 날짜
* @param endDate 조회 종료 날짜
* @param refresh 캐시 갱신 여부
* @return 전체 통합 성과 대시보드
*/
@Operation(
summary = "사용자 전체 성과 대시보드 조회",
description = "사용자의 모든 이벤트 성과를 통합하여 조회합니다."
)
@GetMapping("/{userId}/analytics")
public ResponseEntity<ApiResponse<UserAnalyticsDashboardResponse>> getUserAnalytics(
@Parameter(description = "사용자 ID", required = true)
@PathVariable String userId,
@Parameter(description = "조회 시작 날짜 (ISO 8601 format)")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime startDate,
@Parameter(description = "조회 종료 날짜 (ISO 8601 format)")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime endDate,
@Parameter(description = "캐시 갱신 여부")
@RequestParam(required = false, defaultValue = "false")
Boolean refresh
) {
log.info("사용자 전체 성과 대시보드 조회 API 호출: userId={}, refresh={}", userId, refresh);
UserAnalyticsDashboardResponse response = userAnalyticsService.getUserDashboardData(
userId, startDate, endDate, refresh
);
return ResponseEntity.ok(ApiResponse.success(response));
}
}

View File

@ -0,0 +1,78 @@
package com.kt.event.analytics.controller;
import com.kt.event.analytics.dto.response.UserChannelAnalyticsResponse;
import com.kt.event.analytics.service.UserChannelAnalyticsService;
import com.kt.event.common.dto.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
* User Channel Analytics Controller
*/
@Tag(name = "User Channels", description = "사용자 전체 이벤트 채널별 성과 분석 API")
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserChannelAnalyticsController {
private final UserChannelAnalyticsService userChannelAnalyticsService;
@Operation(
summary = "사용자 전체 채널별 성과 분석",
description = "사용자의 모든 이벤트 채널 성과를 통합하여 분석합니다."
)
@GetMapping("/{userId}/analytics/channels")
public ResponseEntity<ApiResponse<UserChannelAnalyticsResponse>> getUserChannelAnalytics(
@Parameter(description = "사용자 ID", required = true)
@PathVariable String userId,
@Parameter(description = "조회할 채널 목록 (쉼표로 구분)")
@RequestParam(required = false)
String channels,
@Parameter(description = "정렬 기준")
@RequestParam(required = false, defaultValue = "participants")
String sortBy,
@Parameter(description = "정렬 순서")
@RequestParam(required = false, defaultValue = "desc")
String order,
@Parameter(description = "조회 시작 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime startDate,
@Parameter(description = "조회 종료 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime endDate,
@Parameter(description = "캐시 갱신 여부")
@RequestParam(required = false, defaultValue = "false")
Boolean refresh
) {
log.info("사용자 채널 분석 API 호출: userId={}, sortBy={}", userId, sortBy);
List<String> channelList = channels != null && !channels.isBlank()
? Arrays.asList(channels.split(","))
: null;
UserChannelAnalyticsResponse response = userChannelAnalyticsService.getUserChannelAnalytics(
userId, channelList, sortBy, order, startDate, endDate, refresh
);
return ResponseEntity.ok(ApiResponse.success(response));
}
}

View File

@ -0,0 +1,64 @@
package com.kt.event.analytics.controller;
import com.kt.event.analytics.dto.response.UserRoiAnalyticsResponse;
import com.kt.event.analytics.service.UserRoiAnalyticsService;
import com.kt.event.common.dto.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
/**
* User ROI Analytics Controller
*/
@Tag(name = "User ROI", description = "사용자 전체 이벤트 ROI 분석 API")
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserRoiAnalyticsController {
private final UserRoiAnalyticsService userRoiAnalyticsService;
@Operation(
summary = "사용자 전체 ROI 상세 분석",
description = "사용자의 모든 이벤트 ROI를 통합하여 분석합니다."
)
@GetMapping("/{userId}/analytics/roi")
public ResponseEntity<ApiResponse<UserRoiAnalyticsResponse>> getUserRoiAnalytics(
@Parameter(description = "사용자 ID", required = true)
@PathVariable String userId,
@Parameter(description = "예상 수익 포함 여부")
@RequestParam(required = false, defaultValue = "true")
Boolean includeProjection,
@Parameter(description = "조회 시작 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime startDate,
@Parameter(description = "조회 종료 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime endDate,
@Parameter(description = "캐시 갱신 여부")
@RequestParam(required = false, defaultValue = "false")
Boolean refresh
) {
log.info("사용자 ROI 분석 API 호출: userId={}, includeProjection={}", userId, includeProjection);
UserRoiAnalyticsResponse response = userRoiAnalyticsService.getUserRoiAnalytics(
userId, includeProjection, startDate, endDate, refresh
);
return ResponseEntity.ok(ApiResponse.success(response));
}
}

View File

@ -0,0 +1,74 @@
package com.kt.event.analytics.controller;
import com.kt.event.analytics.dto.response.UserTimelineAnalyticsResponse;
import com.kt.event.analytics.service.UserTimelineAnalyticsService;
import com.kt.event.common.dto.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
* User Timeline Analytics Controller
*/
@Tag(name = "User Timeline", description = "사용자 전체 이벤트 시간대별 분석 API")
@Slf4j
@RestController
@RequestMapping("/api/v1/users")
@RequiredArgsConstructor
public class UserTimelineAnalyticsController {
private final UserTimelineAnalyticsService userTimelineAnalyticsService;
@Operation(
summary = "사용자 전체 시간대별 참여 추이",
description = "사용자의 모든 이벤트 시간대별 데이터를 통합하여 분석합니다."
)
@GetMapping("/{userId}/analytics/timeline")
public ResponseEntity<ApiResponse<UserTimelineAnalyticsResponse>> getUserTimelineAnalytics(
@Parameter(description = "사용자 ID", required = true)
@PathVariable String userId,
@Parameter(description = "시간 간격 단위 (hourly, daily, weekly, monthly)")
@RequestParam(required = false, defaultValue = "daily")
String interval,
@Parameter(description = "조회 시작 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime startDate,
@Parameter(description = "조회 종료 날짜")
@RequestParam(required = false)
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDateTime endDate,
@Parameter(description = "조회할 지표 목록 (쉼표로 구분)")
@RequestParam(required = false)
String metrics,
@Parameter(description = "캐시 갱신 여부")
@RequestParam(required = false, defaultValue = "false")
Boolean refresh
) {
log.info("사용자 타임라인 분석 API 호출: userId={}, interval={}", userId, interval);
List<String> metricList = metrics != null && !metrics.isBlank()
? Arrays.asList(metrics.split(","))
: null;
UserTimelineAnalyticsResponse response = userTimelineAnalyticsService.getUserTimelineAnalytics(
userId, interval, startDate, endDate, metricList, refresh
);
return ResponseEntity.ok(ApiResponse.success(response));
}
}

View File

@ -17,7 +17,12 @@ public class AnalyticsSummary {
/** /**
* 참여자 * 참여자
*/ */
private Integer totalParticipants; private Integer participants;
/**
* 참여자 증감 (이전 기간 대비)
*/
private Integer participantsDelta;
/** /**
* 조회수 * 조회수
@ -44,6 +49,11 @@ public class AnalyticsSummary {
*/ */
private Integer averageEngagementTime; private Integer averageEngagementTime;
/**
* 목표 ROI (%)
*/
private Double targetRoi;
/** /**
* SNS 반응 통계 * SNS 반응 통계
*/ */

View File

@ -17,7 +17,7 @@ public class ChannelSummary {
/** /**
* 채널명 * 채널명
*/ */
private String channelName; private String channel;
/** /**
* 조회수 * 조회수

View File

@ -19,7 +19,7 @@ public class RoiSummary {
/** /**
* 투자 비용 () * 투자 비용 ()
*/ */
private BigDecimal totalInvestment; private BigDecimal totalCost;
/** /**
* 예상 매출 증대 () * 예상 매출 증대 ()

View File

@ -0,0 +1,87 @@
package com.kt.event.analytics.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
/**
* 사용자 전체 이벤트 통합 대시보드 응답
*
* 사용자 ID 기반으로 모든 이벤트의 성과를 통합하여 제공
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserAnalyticsDashboardResponse {
/**
* 사용자 ID
*/
private String userId;
/**
* 조회 기간 정보
*/
private PeriodInfo period;
/**
* 전체 이벤트
*/
private Integer totalEvents;
/**
* 활성 이벤트
*/
private Integer activeEvents;
/**
* 전체 성과 요약 (모든 이벤트 통합)
*/
private AnalyticsSummary overallSummary;
/**
* 채널별 성과 요약 (모든 이벤트 통합)
*/
private List<ChannelSummary> channelPerformance;
/**
* 전체 ROI 요약
*/
private RoiSummary overallRoi;
/**
* 이벤트별 성과 목록 (간략)
*/
private List<EventPerformanceSummary> eventPerformances;
/**
* 마지막 업데이트 시간
*/
private LocalDateTime lastUpdatedAt;
/**
* 데이터 출처 (real-time, cached, fallback)
*/
private String dataSource;
/**
* 이벤트별 성과 요약
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class EventPerformanceSummary {
private String eventId;
private String eventTitle;
private Integer participants;
private Integer views;
private Double roi;
private String status;
}
}

Some files were not shown because too many files have changed in this diff Show More