kt-event-marketing/design/backend/api/event-service-api.yaml
2025-10-22 16:37:32 +09:00

1059 lines
29 KiB
YAML

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'