openapi: 3.0.3 info: title: Distribution Service API description: | KT AI 기반 소상공인 이벤트 자동 생성 서비스 - 배포 관리 서비스 API **주요 기능:** - 다중 채널 배포 관리 - 배포 상태 모니터링 - 채널별 배포 결과 추적 **지원 배포 채널:** - 우리동네TV - 링고비즈 (연결음) - 지니TV 광고 - Instagram - Naver Blog - Kakao Channel version: 1.0.0 contact: name: KT Event Marketing Team email: support@kt-event-marketing.com servers: - url: http://localhost:8085 description: Local Development Server - url: https://api-dev.kt-event-marketing.com description: Development Server - url: https://api.kt-event-marketing.com description: Production Server tags: - name: Distribution description: 다중 채널 배포 관리 - name: Status description: 배포 상태 조회 및 모니터링 paths: /api/distribution/distribute: post: tags: - Distribution summary: 다중 채널 배포 실행 description: | 선택된 모든 채널에 동시 배포를 실행합니다. **배포 프로세스:** 1. 배포 이력 초기화 (상태: PENDING) 2. 각 채널별 병렬 배포 처리 3. 배포 결과 집계 및 저장 4. Kafka 이벤트 발행 (Analytics Service 구독) 5. Redis 캐시 저장 (TTL: 1시간) **Sprint 2 제약사항:** - 외부 API 호출 없음 (Mock 처리) - 모든 배포 요청은 성공으로 처리 - 배포 로그만 DB에 기록 **유저스토리:** UFR-DIST-010 operationId: distributeToChannels requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DistributionRequest' examples: multiChannel: summary: 다중 채널 배포 예시 value: eventId: "evt-12345" channels: - type: "WOORIDONGNE_TV" config: radius: "1km" timeSlots: - "weekday_evening" - "weekend_lunch" - type: "INSTAGRAM" config: scheduledTime: "2025-11-01T10:00:00Z" - type: "NAVER_BLOG" config: scheduledTime: "2025-11-01T10:30:00Z" contentUrls: instagram: "https://cdn.example.com/images/event-instagram.jpg" naverBlog: "https://cdn.example.com/images/event-naver.jpg" kakaoChannel: "https://cdn.example.com/images/event-kakao.jpg" responses: '200': description: 배포 완료 content: application/json: schema: $ref: '#/components/schemas/DistributionResponse' examples: allSuccess: summary: 모든 채널 배포 성공 value: distributionId: "dist-12345" eventId: "evt-12345" status: "COMPLETED" completedAt: "2025-11-01T09:00:00Z" results: - channel: "WOORIDONGNE_TV" status: "SUCCESS" distributionId: "wtv-uuid-12345" estimatedViews: 1000 message: "배포 완료" - channel: "INSTAGRAM" status: "SUCCESS" postUrl: "https://instagram.com/p/generated-post-id" postId: "ig-post-12345" message: "게시 완료" - channel: "NAVER_BLOG" status: "SUCCESS" postUrl: "https://blog.naver.com/store123/generated-post" message: "게시 완료" '400': description: 잘못된 요청 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalidEventId: summary: 유효하지 않은 이벤트 ID value: error: "BAD_REQUEST" message: "유효하지 않은 이벤트 ID입니다" timestamp: "2025-11-01T09:00:00Z" noChannels: summary: 선택된 채널 없음 value: error: "BAD_REQUEST" message: "최소 1개 이상의 채널을 선택해야 합니다" timestamp: "2025-11-01T09:00:00Z" '404': description: 이벤트를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: eventNotFound: summary: 존재하지 않는 이벤트 value: error: "NOT_FOUND" message: "이벤트를 찾을 수 없습니다: evt-12345" timestamp: "2025-11-01T09:00:00Z" '500': description: 서버 내부 오류 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: internalError: summary: 서버 오류 value: error: "INTERNAL_SERVER_ERROR" message: "배포 처리 중 오류가 발생했습니다" timestamp: "2025-11-01T09:00:00Z" /api/distribution/{eventId}/status: get: tags: - Status summary: 배포 상태 조회 description: | 이벤트의 배포 상태를 조회합니다. **조회 프로세스:** 1. Cache-Aside 패턴 적용 (Redis 캐시 우선 조회) 2. 캐시 MISS 시 DB에서 배포 이력 조회 3. 진행중(IN_PROGRESS) 상태일 때만 외부 API로 실시간 상태 확인 4. Circuit Breaker 패턴 적용 (외부 API 호출 시) 5. 배포 상태 캐싱 (TTL: 1시간) **응답 시간:** - 캐시 HIT: 0.1초 - 캐시 MISS: 0.5초 ~ 2초 **유저스토리:** UFR-DIST-020 operationId: getDistributionStatus parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt-12345" responses: '200': description: 배포 상태 조회 성공 content: application/json: schema: $ref: '#/components/schemas/DistributionStatusResponse' examples: completed: summary: 배포 완료 상태 value: eventId: "evt-12345" overallStatus: "COMPLETED" completedAt: "2025-11-01T09:00:00Z" channels: - channel: "WOORIDONGNE_TV" status: "COMPLETED" distributionId: "wtv-uuid-12345" estimatedViews: 1500 completedAt: "2025-11-01T09:00:00Z" - channel: "RINGO_BIZ" status: "COMPLETED" updateTimestamp: "2025-11-01T09:00:00Z" - channel: "GENIE_TV" status: "COMPLETED" adId: "gtv-uuid-12345" impressionSchedule: - "2025-11-01 18:00-20:00" - "2025-11-02 12:00-14:00" - channel: "INSTAGRAM" status: "COMPLETED" postUrl: "https://instagram.com/p/generated-post-id" postId: "ig-post-12345" - channel: "NAVER_BLOG" status: "COMPLETED" postUrl: "https://blog.naver.com/store123/generated-post" - channel: "KAKAO_CHANNEL" status: "COMPLETED" messageId: "kakao-msg-12345" inProgress: summary: 배포 진행중 상태 value: eventId: "evt-12345" overallStatus: "IN_PROGRESS" startedAt: "2025-11-01T08:58:00Z" channels: - channel: "WOORIDONGNE_TV" status: "COMPLETED" distributionId: "wtv-uuid-12345" estimatedViews: 1500 - channel: "INSTAGRAM" status: "IN_PROGRESS" progress: 50 - channel: "NAVER_BLOG" status: "PENDING" partialFailure: summary: 일부 채널 실패 상태 value: eventId: "evt-12345" overallStatus: "PARTIAL_FAILURE" completedAt: "2025-11-01T09:00:00Z" channels: - channel: "WOORIDONGNE_TV" status: "COMPLETED" distributionId: "wtv-uuid-12345" estimatedViews: 1500 - channel: "INSTAGRAM" status: "FAILED" errorMessage: "Instagram API 타임아웃" retries: 3 lastRetryAt: "2025-11-01T08:59:30Z" - channel: "NAVER_BLOG" status: "COMPLETED" postUrl: "https://blog.naver.com/store123/generated-post" '404': description: 배포 이력을 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: notFound: summary: 배포 이력 없음 value: error: "NOT_FOUND" message: "배포 이력을 찾을 수 없습니다: evt-12345" timestamp: "2025-11-01T09:00:00Z" '500': description: 서버 내부 오류 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/distribution/{eventId}/retry: post: tags: - Distribution summary: 실패한 채널 재시도 description: | 실패한 채널에 대해 배포를 재시도합니다. **재시도 프로세스:** 1. 실패한 채널 목록 검증 2. 새로운 배포 시도 로그 생성 3. Circuit Breaker 및 Retry 로직 적용 4. 캐시 무효화 5. 재시도 결과 반환 **재시도 제한:** - 최대 재시도 횟수: 3회 - Circuit Breaker가 OPEN 상태일 경우 30초 대기 후 시도 operationId: retryDistribution parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt-12345" requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RetryRequest' examples: retryFailedChannels: summary: 실패한 채널 재시도 value: channels: - "INSTAGRAM" - "KAKAO_CHANNEL" responses: '200': description: 재시도 완료 content: application/json: schema: $ref: '#/components/schemas/RetryResponse' examples: success: summary: 재시도 성공 value: eventId: "evt-12345" retryStatus: "COMPLETED" retriedAt: "2025-11-01T09:05:00Z" results: - channel: "INSTAGRAM" status: "SUCCESS" postUrl: "https://instagram.com/p/retry-post-id" - channel: "KAKAO_CHANNEL" status: "SUCCESS" messageId: "kakao-retry-12345" '400': description: 잘못된 요청 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '404': description: 배포 이력을 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' components: schemas: DistributionRequest: type: object required: - eventId - channels - contentUrls properties: eventId: type: string description: 이벤트 ID example: "evt-12345" channels: type: array description: 배포할 채널 목록 minItems: 1 items: $ref: '#/components/schemas/ChannelConfig' contentUrls: type: object description: 플랫폼별 콘텐츠 URL properties: wooridongneTV: type: string description: 우리동네TV 영상 URL (15초) example: "https://cdn.example.com/videos/event-15s.mp4" ringoBiz: type: string description: 링고비즈 연결음 파일 URL example: "https://cdn.example.com/audio/ringtone.mp3" genieTV: type: string description: 지니TV 광고 영상 URL example: "https://cdn.example.com/videos/event-ad.mp4" instagram: type: string description: Instagram 이미지 URL (1080x1080) example: "https://cdn.example.com/images/event-instagram.jpg" naverBlog: type: string description: Naver Blog 이미지 URL (800x600) example: "https://cdn.example.com/images/event-naver.jpg" kakaoChannel: type: string description: Kakao Channel 이미지 URL (800x800) example: "https://cdn.example.com/images/event-kakao.jpg" ChannelConfig: type: object required: - type properties: type: type: string description: 채널 타입 enum: - WOORIDONGNE_TV - RINGO_BIZ - GENIE_TV - INSTAGRAM - NAVER_BLOG - KAKAO_CHANNEL example: "INSTAGRAM" config: type: object description: 채널별 설정 (채널에 따라 다름) additionalProperties: true example: scheduledTime: "2025-11-01T10:00:00Z" caption: "이벤트 안내" hashtags: - "이벤트" - "할인" DistributionResponse: type: object required: - distributionId - eventId - status - results properties: distributionId: type: string description: 배포 ID example: "dist-12345" eventId: type: string description: 이벤트 ID example: "evt-12345" status: type: string description: 전체 배포 상태 enum: - PENDING - IN_PROGRESS - COMPLETED - PARTIAL_FAILURE - FAILED example: "COMPLETED" startedAt: type: string format: date-time description: 배포 시작 시각 example: "2025-11-01T08:59:00Z" completedAt: type: string format: date-time description: 배포 완료 시각 example: "2025-11-01T09:00:00Z" results: type: array description: 채널별 배포 결과 items: $ref: '#/components/schemas/ChannelResult' ChannelResult: type: object required: - channel - status properties: channel: type: string description: 채널 타입 enum: - WOORIDONGNE_TV - RINGO_BIZ - GENIE_TV - INSTAGRAM - NAVER_BLOG - KAKAO_CHANNEL example: "INSTAGRAM" status: type: string description: 채널별 배포 상태 enum: - PENDING - IN_PROGRESS - SUCCESS - FAILED example: "SUCCESS" distributionId: type: string description: 채널별 배포 ID (우리동네TV, 지니TV) example: "wtv-uuid-12345" estimatedViews: type: integer description: 예상 노출 수 (우리동네TV, 지니TV) example: 1500 updateTimestamp: type: string format: date-time description: 업데이트 완료 시각 (링고비즈) example: "2025-11-01T09:00:00Z" adId: type: string description: 광고 ID (지니TV) example: "gtv-uuid-12345" impressionSchedule: type: array description: 노출 스케줄 (지니TV) items: type: string example: - "2025-11-01 18:00-20:00" - "2025-11-02 12:00-14:00" postUrl: type: string description: 게시물 URL (Instagram, Naver Blog) example: "https://instagram.com/p/generated-post-id" postId: type: string description: 게시물 ID (Instagram) example: "ig-post-12345" messageId: type: string description: 메시지 ID (Kakao Channel) example: "kakao-msg-12345" message: type: string description: 결과 메시지 example: "배포 완료" errorMessage: type: string description: 오류 메시지 (실패 시) example: "Instagram API 타임아웃" retries: type: integer description: 재시도 횟수 example: 0 lastRetryAt: type: string format: date-time description: 마지막 재시도 시각 example: "2025-11-01T08:59:30Z" DistributionStatusResponse: type: object required: - eventId - overallStatus - channels properties: eventId: type: string description: 이벤트 ID example: "evt-12345" overallStatus: type: string description: 전체 배포 상태 enum: - PENDING - IN_PROGRESS - COMPLETED - PARTIAL_FAILURE - FAILED - NOT_FOUND example: "COMPLETED" startedAt: type: string format: date-time description: 배포 시작 시각 example: "2025-11-01T08:59:00Z" completedAt: type: string format: date-time description: 배포 완료 시각 example: "2025-11-01T09:00:00Z" channels: type: array description: 채널별 배포 상태 items: $ref: '#/components/schemas/ChannelStatus' ChannelStatus: type: object required: - channel - status properties: channel: type: string description: 채널 타입 enum: - WOORIDONGNE_TV - RINGO_BIZ - GENIE_TV - INSTAGRAM - NAVER_BLOG - KAKAO_CHANNEL example: "INSTAGRAM" status: type: string description: 채널별 배포 상태 enum: - PENDING - IN_PROGRESS - COMPLETED - FAILED example: "COMPLETED" progress: type: integer description: 진행률 (0-100, IN_PROGRESS 상태일 때) minimum: 0 maximum: 100 example: 75 distributionId: type: string description: 채널별 배포 ID example: "wtv-uuid-12345" estimatedViews: type: integer description: 예상 노출 수 example: 1500 updateTimestamp: type: string format: date-time description: 업데이트 완료 시각 example: "2025-11-01T09:00:00Z" adId: type: string description: 광고 ID example: "gtv-uuid-12345" impressionSchedule: type: array description: 노출 스케줄 items: type: string example: - "2025-11-01 18:00-20:00" postUrl: type: string description: 게시물 URL example: "https://instagram.com/p/generated-post-id" postId: type: string description: 게시물 ID example: "ig-post-12345" messageId: type: string description: 메시지 ID example: "kakao-msg-12345" completedAt: type: string format: date-time description: 완료 시각 example: "2025-11-01T09:00:00Z" errorMessage: type: string description: 오류 메시지 example: "Instagram API 타임아웃" retries: type: integer description: 재시도 횟수 example: 3 lastRetryAt: type: string format: date-time description: 마지막 재시도 시각 example: "2025-11-01T08:59:30Z" RetryRequest: type: object required: - channels properties: channels: type: array description: 재시도할 채널 목록 minItems: 1 items: type: string enum: - WOORIDONGNE_TV - RINGO_BIZ - GENIE_TV - INSTAGRAM - NAVER_BLOG - KAKAO_CHANNEL example: - "INSTAGRAM" - "KAKAO_CHANNEL" RetryResponse: type: object required: - eventId - retryStatus - results properties: eventId: type: string description: 이벤트 ID example: "evt-12345" retryStatus: type: string description: 재시도 전체 상태 enum: - COMPLETED - PARTIAL_FAILURE - FAILED example: "COMPLETED" retriedAt: type: string format: date-time description: 재시도 시각 example: "2025-11-01T09:05:00Z" results: type: array description: 채널별 재시도 결과 items: $ref: '#/components/schemas/ChannelResult' ErrorResponse: type: object required: - error - message - timestamp properties: error: type: string description: 오류 코드 enum: - BAD_REQUEST - NOT_FOUND - INTERNAL_SERVER_ERROR example: "BAD_REQUEST" message: type: string description: 오류 메시지 example: "유효하지 않은 이벤트 ID입니다" timestamp: type: string format: date-time description: 오류 발생 시각 example: "2025-11-01T09:00:00Z" details: type: object description: 추가 오류 정보 (선택 사항) additionalProperties: true