openapi: 3.0.3 info: title: Event Service API version: 1.0.0 description: | KT AI 기반 소상공인 이벤트 자동 생성 서비스의 Event Service API 명세입니다. **주요 기능:** - 이벤트 대시보드 조회 - 이벤트 목적 선택 및 초안 생성 - AI 이벤트 추천 요청 및 결과 조회 - 이미지 생성 요청 및 결과 조회 - 콘텐츠 선택 및 편집 - 최종 승인 및 배포 - 이벤트 상세 조회 및 목록 관리 servers: - url: http://localhost:8080 description: 로컬 개발 서버 - url: https://api-dev.kt-event-marketing.com description: 개발 서버 - url: https://api.kt-event-marketing.com description: 프로덕션 서버 tags: - name: Dashboard description: 대시보드 관리 - name: EventDraft description: 이벤트 초안 관리 - name: AIRecommendation description: AI 추천 관리 - name: ContentGeneration description: 콘텐츠 생성 관리 - name: EventPublish description: 이벤트 발행 관리 - name: EventManagement description: 이벤트 조회 및 관리 - name: Job description: 비동기 작업 관리 paths: /api/events/dashboard: get: tags: - Dashboard summary: 대시보드 이벤트 목록 조회 description: | UFR-EVENT-010: 소상공인의 대시보드에서 진행중/예정/종료된 이벤트를 조회합니다. **비즈니스 로직:** - 상태별로 최대 5개씩 표시 (최신순) - Redis 캐시 우선 조회 (TTL 1분) - 참여자 수, 조회수 등 기본 통계 포함 operationId: getDashboard security: - bearerAuth: [] responses: '200': description: 대시보드 데이터 조회 성공 content: application/json: schema: $ref: '#/components/schemas/DashboardResponse' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/events/purposes: post: tags: - EventDraft summary: 이벤트 목적 선택 및 초안 생성 description: | UFR-EVENT-020: 이벤트 목적을 선택하고 초안을 생성합니다. **비즈니스 로직:** - 목적 유효성 검증 (신규 고객 유치, 재방문 유도, 매출 증대, 인지도 향상) - EventDraft 엔티티 생성 및 DB 저장 - Redis 캐시 저장 (TTL 30분) - Kafka EventDraftCreated 이벤트 발행 operationId: createEventDraft security: - bearerAuth: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateEventDraftRequest' responses: '200': description: 이벤트 초안 생성 성공 content: application/json: schema: $ref: '#/components/schemas/EventDraftResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/events/{id}/ai-recommendations: post: tags: - AIRecommendation summary: AI 이벤트 추천 요청 description: | UFR-EVENT-030: AI 트렌드 분석 및 이벤트 추천을 요청합니다. **비동기 처리:** - Kafka ai-job-topic에 Job 발행 - Redis에 Job 상태 저장 (TTL 1시간) - 202 Accepted 응답 (jobId 반환) - 클라이언트는 폴링으로 결과 조회 operationId: requestAIRecommendation security: - bearerAuth: [] parameters: - name: id in: path required: true description: 이벤트 초안 ID schema: type: string format: uuid responses: '202': description: AI 추천 요청 접수 content: application/json: schema: $ref: '#/components/schemas/JobResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /api/jobs/{jobId}/status: get: tags: - Job summary: Job 상태 조회 (폴링) description: | AI 추천 또는 이미지 생성 Job의 상태를 조회합니다. **폴링 패턴:** - AI 추천: 2초 간격, 최대 30초 (15회) - 이미지 생성: 3초 간격, 최대 30초 (10회) - 상태: PENDING, PROCESSING, COMPLETED, FAILED operationId: getJobStatus security: - bearerAuth: [] parameters: - name: jobId in: path required: true description: Job ID schema: type: string format: uuid responses: '200': description: Job 상태 조회 성공 content: application/json: schema: oneOf: - $ref: '#/components/schemas/AIRecommendationJobResult' - $ref: '#/components/schemas/ImageGenerationJobResult' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /api/events/{id}/content-generation: post: tags: - ContentGeneration summary: 이미지 생성 요청 description: | UFR-CONT-010: SNS용 이미지 생성을 요청합니다. **비동기 처리:** - Kafka image-job-topic에 Job 발행 - Redis에 Job 상태 저장 (TTL 1시간) - 202 Accepted 응답 (jobId 반환) - Content Service가 3가지 스타일 이미지 생성 operationId: requestImageGeneration security: - bearerAuth: [] parameters: - name: id in: path required: true description: 이벤트 초안 ID schema: type: string format: uuid responses: '202': description: 이미지 생성 요청 접수 content: application/json: schema: $ref: '#/components/schemas/JobResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /api/events/drafts/{id}/content: put: tags: - ContentGeneration summary: 선택한 콘텐츠 저장 description: | UFR-CONT-020: 선택한 이미지와 편집한 콘텐츠를 저장합니다. **비즈니스 로직:** - 선택한 이미지 URL 검증 - 편집 내용 적용 (텍스트, 색상) - EventDraft 업데이트 - Redis 캐시 무효화 operationId: updateEventContent security: - bearerAuth: [] parameters: - name: id in: path required: true description: 이벤트 초안 ID schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateContentRequest' responses: '200': description: 콘텐츠 저장 성공 content: application/json: schema: $ref: '#/components/schemas/EventContentResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /api/events/{id}/publish: post: tags: - EventPublish summary: 이벤트 최종 승인 및 배포 description: | UFR-EVENT-050: 이벤트를 최종 승인하고 배포를 시작합니다. **비즈니스 로직:** - 발행 준비 검증 (목적, AI 추천, 콘텐츠, 채널 선택) - 상태 변경: DRAFT → APPROVED → ACTIVE - Kafka EventCreated 이벤트 발행 - Distribution Service 동기 호출 (Timeout 70초, Circuit Breaker 적용) - Redis 캐시 무효화 operationId: publishEvent security: - bearerAuth: [] parameters: - name: id in: path required: true description: 이벤트 초안 ID schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PublishEventRequest' responses: '200': description: 이벤트 발행 성공 content: application/json: schema: $ref: '#/components/schemas/PublishEventResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' '503': description: Distribution Service 호출 실패 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /api/events/{id}: get: tags: - EventManagement summary: 이벤트 상세 조회 description: | UFR-EVENT-060: 이벤트의 상세 정보를 조회합니다. **비즈니스 로직:** - Redis 캐시 우선 조회 (TTL 5분) - 경품 정보 및 배포 이력 JOIN 조회 - 사용자 권한 검증 operationId: getEventDetail security: - bearerAuth: [] parameters: - name: id in: path required: true description: 이벤트 ID schema: type: string format: uuid responses: '200': description: 이벤트 상세 조회 성공 content: application/json: schema: $ref: '#/components/schemas/EventDetailResponse' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': $ref: '#/components/responses/NotFound' '500': $ref: '#/components/responses/InternalServerError' /api/events: get: tags: - EventManagement summary: 이벤트 목록 조회 (필터/검색) description: | UFR-EVENT-070: 이벤트 목록을 조회하고 필터링/검색합니다. **비즈니스 로직:** - Redis 캐시 우선 조회 (TTL 1분) - 상태별 필터링 - 키워드 검색 (제목, 설명) - 페이지네이션 (기본 20개/페이지) - 정렬 (최신순, 참여자순, ROI순) operationId: getEventList security: - bearerAuth: [] parameters: - name: status in: query description: 이벤트 상태 필터 schema: type: string enum: [DRAFT, APPROVED, ACTIVE, COMPLETED] - name: keyword in: query description: 검색 키워드 (제목, 설명) schema: type: string - name: page in: query description: 페이지 번호 (0부터 시작) schema: type: integer minimum: 0 default: 0 - name: size in: query description: 페이지 크기 schema: type: integer minimum: 1 maximum: 100 default: 20 - name: sort in: query description: 정렬 기준 schema: type: string enum: [createdAt,desc, participantCount,desc, roi,desc] default: createdAt,desc responses: '200': description: 이벤트 목록 조회 성공 content: application/json: schema: $ref: '#/components/schemas/EventListResponse' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: JWT 토큰을 사용한 인증 (User Service에서 발급) schemas: # Dashboard Schemas DashboardResponse: type: object required: - active - approved - completed properties: active: type: array description: 진행중 이벤트 목록 (최대 5개) maxItems: 5 items: $ref: '#/components/schemas/EventSummary' approved: type: array description: 배포 대기중 이벤트 목록 (최대 5개) maxItems: 5 items: $ref: '#/components/schemas/EventSummary' completed: type: array description: 종료된 이벤트 목록 (최대 5개) maxItems: 5 items: $ref: '#/components/schemas/EventSummary' EventSummary: type: object required: - eventId - title - status - createdAt properties: eventId: type: string format: uuid description: 이벤트 ID title: type: string description: 이벤트 제목 maxLength: 100 period: $ref: '#/components/schemas/EventPeriod' status: type: string enum: [DRAFT, APPROVED, ACTIVE, COMPLETED] description: 이벤트 상태 participantCount: type: integer description: 참여자 수 default: 0 viewCount: type: integer description: 조회수 default: 0 createdAt: type: string format: date-time description: 생성일시 EventPeriod: type: object properties: startDate: type: string format: date description: 시작일 endDate: type: string format: date description: 종료일 # Event Draft Schemas CreateEventDraftRequest: type: object required: - objective - storeInfo properties: objective: type: string enum: [NEW_CUSTOMER, REVISIT, SALES_INCREASE, BRAND_AWARENESS] description: | 이벤트 목적 - NEW_CUSTOMER: 신규 고객 유치 - REVISIT: 재방문 유도 - SALES_INCREASE: 매출 증대 - BRAND_AWARENESS: 인지도 향상 storeInfo: $ref: '#/components/schemas/StoreInfo' StoreInfo: type: object required: - storeName - industry - region properties: storeName: type: string description: 매장명 maxLength: 50 industry: type: string description: 업종 maxLength: 30 region: type: string description: 지역 maxLength: 50 address: type: string description: 주소 maxLength: 200 EventDraftResponse: type: object required: - eventDraftId - objective - status properties: eventDraftId: type: string format: uuid description: 이벤트 초안 ID objective: type: string enum: [NEW_CUSTOMER, REVISIT, SALES_INCREASE, BRAND_AWARENESS] description: 이벤트 목적 status: type: string enum: [DRAFT] description: 초안 상태 createdAt: type: string format: date-time description: 생성일시 # Job Schemas JobResponse: type: object required: - jobId - status properties: jobId: type: string format: uuid description: Job ID status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] description: Job 상태 AIRecommendationJobResult: type: object required: - jobId - status properties: jobId: type: string format: uuid status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] recommendations: type: array description: AI 추천 결과 (3가지) minItems: 3 maxItems: 3 items: $ref: '#/components/schemas/EventRecommendation' error: type: string description: 에러 메시지 (FAILED 상태일 때만) EventRecommendation: type: object required: - title - prize - participationMethod - estimatedCost - estimatedParticipants - estimatedROI properties: title: type: string description: 이벤트 제목 (수정 가능) maxLength: 50 prize: type: string description: 경품 (수정 가능) maxLength: 100 participationMethod: type: string description: 참여 방법 maxLength: 200 estimatedCost: type: integer description: 예상 비용 (원) minimum: 0 estimatedParticipants: type: integer description: 예상 참여자 수 minimum: 0 estimatedROI: type: number format: double description: 예상 투자 대비 수익률 (%) ImageGenerationJobResult: type: object required: - jobId - status properties: jobId: type: string format: uuid status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] progress: type: integer description: 진행률 (%) - PROCESSING 상태일 때 minimum: 0 maximum: 100 imageUrls: type: object description: 생성된 이미지 URL (3가지 스타일) - COMPLETED 상태일 때 properties: simple: type: string format: uri description: 심플 스타일 이미지 URL fancy: type: string format: uri description: 화려한 스타일 이미지 URL trendy: type: string format: uri description: 트렌디 스타일 이미지 URL error: type: string description: 에러 메시지 (FAILED 상태일 때만) # Content Schemas UpdateContentRequest: type: object required: - selectedImageUrl - editedContent properties: selectedImageUrl: type: string format: uri description: 선택한 이미지 URL (simple, fancy, trendy 중 하나) editedContent: $ref: '#/components/schemas/EditedContent' EditedContent: type: object properties: title: type: string description: 편집된 제목 maxLength: 100 prizeText: type: string description: 편집된 경품 정보 텍스트 maxLength: 200 participationText: type: string description: 편집된 참여 안내 텍스트 maxLength: 300 backgroundColor: type: string pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' description: 배경색 (Hex 코드) textColor: type: string pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' description: 텍스트 색상 (Hex 코드) accentColor: type: string pattern: '^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' description: 강조 색상 (Hex 코드) EventContentResponse: type: object required: - eventDraftId - selectedImageUrl - editedContent properties: eventDraftId: type: string format: uuid selectedImageUrl: type: string format: uri editedContent: $ref: '#/components/schemas/EditedContent' # Publish Schemas PublishEventRequest: type: object required: - selectedChannels properties: selectedChannels: type: array description: 배포 채널 목록 (최소 1개) minItems: 1 items: $ref: '#/components/schemas/DistributionChannel' DistributionChannel: type: object required: - channelType properties: channelType: type: string enum: [URINEIGHBOR_TV, RINGO_BIZ, GENIE_TV, INSTAGRAM, NAVER_BLOG, KAKAO_CHANNEL] description: | 배포 채널 타입 - URINEIGHBOR_TV: 우리동네TV - RINGO_BIZ: 링고비즈 - GENIE_TV: 지니TV - INSTAGRAM: Instagram - NAVER_BLOG: Naver Blog - KAKAO_CHANNEL: Kakao Channel settings: type: object description: 채널별 설정 (채널마다 다름) additionalProperties: true PublishEventResponse: type: object required: - eventId - status - distributionResults properties: eventId: type: string format: uuid description: 발행된 이벤트 ID status: type: string enum: [ACTIVE] description: 이벤트 상태 distributionResults: type: array description: 채널별 배포 결과 items: $ref: '#/components/schemas/ChannelDistributionResult' ChannelDistributionResult: type: object required: - channelType - status properties: channelType: type: string enum: [URINEIGHBOR_TV, RINGO_BIZ, GENIE_TV, INSTAGRAM, NAVER_BLOG, KAKAO_CHANNEL] status: type: string enum: [SUCCESS, FAILED] description: 배포 상태 distributionId: type: string description: 배포 ID (채널에서 발급) estimatedReach: type: integer description: 예상 노출 수 error: type: string description: 에러 메시지 (FAILED 상태일 때) # Event Management Schemas EventDetailResponse: type: object required: - event - prizes - distributionStatus properties: event: $ref: '#/components/schemas/EventDetail' prizes: type: array description: 경품 목록 items: $ref: '#/components/schemas/Prize' distributionStatus: $ref: '#/components/schemas/DistributionStatus' EventDetail: type: object required: - eventId - title - objective - status - createdAt properties: eventId: type: string format: uuid title: type: string maxLength: 100 objective: type: string enum: [NEW_CUSTOMER, REVISIT, SALES_INCREASE, BRAND_AWARENESS] period: $ref: '#/components/schemas/EventPeriod' status: type: string enum: [DRAFT, APPROVED, ACTIVE, COMPLETED] participationMethod: type: string description: 참여 방법 maxLength: 500 selectedImageUrl: type: string format: uri description: 선택한 이미지 URL editedContent: $ref: '#/components/schemas/EditedContent' createdAt: type: string format: date-time publishedAt: type: string format: date-time description: 발행일시 Prize: type: object required: - prizeId - prizeName - quantity properties: prizeId: type: string format: uuid prizeName: type: string description: 경품명 maxLength: 100 quantity: type: integer description: 수량 minimum: 1 DistributionStatus: type: object properties: channels: type: array description: 배포된 채널 목록 items: $ref: '#/components/schemas/ChannelStatus' ChannelStatus: type: object required: - channelType - status properties: channelType: type: string enum: [URINEIGHBOR_TV, RINGO_BIZ, GENIE_TV, INSTAGRAM, NAVER_BLOG, KAKAO_CHANNEL] status: type: string enum: [PENDING, DISTRIBUTING, ACTIVE, FAILED] distributionId: type: string estimatedReach: type: integer actualReach: type: integer description: 실제 노출 수 EventListResponse: type: object required: - events - totalCount - totalPages - currentPage properties: events: type: array items: $ref: '#/components/schemas/EventListItem' totalCount: type: integer description: 전체 이벤트 수 totalPages: type: integer description: 전체 페이지 수 currentPage: type: integer description: 현재 페이지 (0부터 시작) EventListItem: type: object required: - eventId - title - status - createdAt properties: eventId: type: string format: uuid title: type: string maxLength: 100 period: $ref: '#/components/schemas/EventPeriod' status: type: string enum: [DRAFT, APPROVED, ACTIVE, COMPLETED] participantCount: type: integer description: 참여자 수 default: 0 roi: type: number format: double description: 투자 대비 수익률 (%) createdAt: type: string format: date-time # Error Schemas ErrorResponse: type: object required: - error - message - timestamp properties: error: type: string description: 에러 코드 message: type: string description: 에러 메시지 timestamp: type: string format: date-time description: 에러 발생 시각 details: type: string description: 상세 에러 정보 responses: BadRequest: description: 잘못된 요청 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: BAD_REQUEST message: 요청 파라미터가 유효하지 않습니다 timestamp: '2025-10-22T10:00:00Z' Unauthorized: description: 인증 실패 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: UNAUTHORIZED message: 인증 토큰이 유효하지 않습니다 timestamp: '2025-10-22T10:00:00Z' Forbidden: description: 권한 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: FORBIDDEN message: 해당 리소스에 접근 권한이 없습니다 timestamp: '2025-10-22T10:00:00Z' NotFound: description: 리소스를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: NOT_FOUND message: 요청한 리소스를 찾을 수 없습니다 timestamp: '2025-10-22T10:00:00Z' InternalServerError: description: 서버 내부 오류 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' example: error: INTERNAL_SERVER_ERROR message: 서버 내부 오류가 발생했습니다 timestamp: '2025-10-22T10:00:00Z'