openapi: 3.0.3 info: title: Participation Service API description: | 이벤트 참여 및 당첨자 관리 서비스 API - 이벤트 참여 등록 - 참여자 목록 조회 및 관리 - 당첨자 추첨 및 관리 version: 1.0.0 contact: name: Digital Garage Team email: support@kt-event-marketing.com servers: - url: http://localhost:8084 description: Local Development Server - url: https://dev-api.kt-event-marketing.com/participation/v1 description: Development Server - url: https://api.kt-event-marketing.com/participation/v1 description: Production Server tags: - name: participation description: 이벤트 참여 관리 - name: participant description: 참여자 조회 및 관리 - name: winner description: 당첨자 추첨 및 관리 paths: /events/{eventId}/participate: post: tags: - participation summary: 이벤트 참여 description: | 고객이 이벤트에 참여합니다. - 중복 참여 검증 (전화번호 기반) - 이벤트 진행 상태 검증 - Kafka 이벤트 발행 (ParticipantRegistered) operationId: participateEvent x-user-story: UFR-PART-010 x-controller: ParticipationController parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt_20250123_001" requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ParticipationRequest' examples: standard: summary: 일반 참여 value: name: "홍길동" phoneNumber: "010-1234-5678" email: "hong@example.com" agreeMarketing: true agreePrivacy: true storeVisited: false storeVisit: summary: 매장 방문 참여 value: name: "김철수" phoneNumber: "010-9876-5432" email: "kim@example.com" agreeMarketing: false agreePrivacy: true storeVisited: true responses: '201': description: 참여 성공 content: application/json: schema: $ref: '#/components/schemas/ParticipationResponse' examples: success: summary: 참여 성공 value: success: true message: "이벤트 참여가 완료되었습니다" data: participantId: "prt_20250123_001" eventId: "evt_20250123_001" name: "홍길동" phoneNumber: "010-1234-5678" email: "hong@example.com" participatedAt: "2025-01-23T10:30:00Z" storeVisited: false bonusEntries: 1 '400': description: 잘못된 요청 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalidPhone: summary: 유효하지 않은 전화번호 value: success: false error: code: "INVALID_PHONE_NUMBER" message: "유효하지 않은 전화번호 형식입니다" duplicateParticipation: summary: 중복 참여 value: success: false error: code: "DUPLICATE_PARTICIPATION" message: "이미 참여하신 이벤트입니다" '404': description: 이벤트를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: notFound: summary: 이벤트 없음 value: success: false error: code: "EVENT_NOT_FOUND" message: "이벤트를 찾을 수 없습니다" '409': description: 이벤트 진행 불가 상태 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: notActive: summary: 진행중이 아닌 이벤트 value: success: false error: code: "EVENT_NOT_ACTIVE" message: "현재 참여할 수 없는 이벤트입니다" /events/{eventId}/participants: get: tags: - participant summary: 참여자 목록 조회 description: | 이벤트의 참여자 목록을 조회합니다. - 페이징 지원 - 참여일시 기준 정렬 - 매장 방문 여부 필터링 operationId: getParticipants x-user-story: UFR-PART-020 x-controller: ParticipantController parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt_20250123_001" - name: page in: query description: 페이지 번호 (0부터 시작) schema: type: integer default: 0 minimum: 0 example: 0 - name: size in: query description: 페이지 크기 schema: type: integer default: 20 minimum: 1 maximum: 100 example: 20 - name: storeVisited in: query description: 매장 방문 여부 필터 schema: type: boolean example: true responses: '200': description: 조회 성공 content: application/json: schema: $ref: '#/components/schemas/ParticipantListResponse' examples: success: summary: 참여자 목록 value: success: true message: "참여자 목록을 조회했습니다" data: participants: - participantId: "prt_20250123_001" name: "홍길동" phoneNumber: "010-1234-5678" email: "hong@example.com" participatedAt: "2025-01-23T10:30:00Z" storeVisited: false bonusEntries: 1 isWinner: false - participantId: "prt_20250123_002" name: "김철수" phoneNumber: "010-9876-5432" email: "kim@example.com" participatedAt: "2025-01-23T11:15:00Z" storeVisited: true bonusEntries: 2 isWinner: true pagination: currentPage: 0 pageSize: 20 totalElements: 156 totalPages: 8 hasNext: true hasPrevious: false '404': description: 이벤트를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /events/{eventId}/participants/{participantId}: get: tags: - participant summary: 참여자 상세 조회 description: 특정 참여자의 상세 정보를 조회합니다. operationId: getParticipantDetail x-user-story: UFR-PART-020 x-controller: ParticipantController parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt_20250123_001" - name: participantId in: path required: true description: 참여자 ID schema: type: string example: "prt_20250123_001" responses: '200': description: 조회 성공 content: application/json: schema: $ref: '#/components/schemas/ParticipantDetailResponse' examples: success: summary: 참여자 상세 정보 value: success: true message: "참여자 정보를 조회했습니다" data: participantId: "prt_20250123_001" eventId: "evt_20250123_001" name: "홍길동" phoneNumber: "010-1234-5678" email: "hong@example.com" participatedAt: "2025-01-23T10:30:00Z" storeVisited: false bonusEntries: 1 agreeMarketing: true agreePrivacy: true isWinner: false winnerInfo: null '404': description: 참여자를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: notFound: summary: 참여자 없음 value: success: false error: code: "PARTICIPANT_NOT_FOUND" message: "참여자를 찾을 수 없습니다" /events/{eventId}/draw-winners: post: tags: - winner summary: 당첨자 추첨 description: | 이벤트 당첨자를 추첨합니다. - 랜덤 추첨 알고리즘 사용 - 매장 방문 보너스 가중치 적용 - 중복 당첨 방지 operationId: drawWinners x-user-story: UFR-PART-030 x-controller: WinnerController parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt_20250123_001" requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DrawWinnersRequest' examples: standard: summary: 일반 추첨 value: winnerCount: 10 applyStoreVisitBonus: true responses: '200': description: 추첨 성공 content: application/json: schema: $ref: '#/components/schemas/DrawWinnersResponse' examples: success: summary: 추첨 완료 value: success: true message: "당첨자 추첨이 완료되었습니다" data: eventId: "evt_20250123_001" totalParticipants: 156 winnerCount: 10 drawnAt: "2025-01-24T15:00:00Z" winners: - participantId: "prt_20250123_002" name: "김철수" phoneNumber: "010-9876-5432" rank: 1 - participantId: "prt_20250123_045" name: "이영희" phoneNumber: "010-5555-1234" rank: 2 - participantId: "prt_20250123_089" name: "박민수" phoneNumber: "010-7777-8888" rank: 3 '400': description: 잘못된 요청 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: invalidCount: summary: 잘못된 당첨자 수 value: success: false error: code: "INVALID_WINNER_COUNT" message: "당첨자 수가 참여자 수보다 많습니다" '404': description: 이벤트를 찾을 수 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '409': description: 이미 추첨 완료 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: alreadyDrawn: summary: 추첨 완료 상태 value: success: false error: code: "ALREADY_DRAWN" message: "이미 당첨자 추첨이 완료되었습니다" /events/{eventId}/winners: get: tags: - winner summary: 당첨자 목록 조회 description: | 이벤트의 당첨자 목록을 조회합니다. - 당첨 순위별 정렬 - 페이징 지원 operationId: getWinners x-user-story: UFR-PART-030 x-controller: WinnerController parameters: - name: eventId in: path required: true description: 이벤트 ID schema: type: string example: "evt_20250123_001" - name: page in: query description: 페이지 번호 (0부터 시작) schema: type: integer default: 0 minimum: 0 example: 0 - name: size in: query description: 페이지 크기 schema: type: integer default: 20 minimum: 1 maximum: 100 example: 20 responses: '200': description: 조회 성공 content: application/json: schema: $ref: '#/components/schemas/WinnerListResponse' examples: success: summary: 당첨자 목록 value: success: true message: "당첨자 목록을 조회했습니다" data: eventId: "evt_20250123_001" drawnAt: "2025-01-24T15:00:00Z" totalWinners: 10 winners: - participantId: "prt_20250123_002" name: "김철수" phoneNumber: "010-9876-5432" email: "kim@example.com" rank: 1 wonAt: "2025-01-24T15:00:00Z" - participantId: "prt_20250123_045" name: "이영희" phoneNumber: "010-5555-1234" email: "lee@example.com" rank: 2 wonAt: "2025-01-24T15:00:00Z" pagination: currentPage: 0 pageSize: 20 totalElements: 10 totalPages: 1 hasNext: false hasPrevious: false '404': description: 이벤트를 찾을 수 없음 또는 당첨자가 없음 content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' examples: noWinners: summary: 당첨자 없음 value: success: false error: code: "NO_WINNERS_YET" message: "아직 당첨자 추첨이 진행되지 않았습니다" components: schemas: ParticipationRequest: type: object required: - name - phoneNumber - agreePrivacy properties: name: type: string description: 참여자 이름 minLength: 2 maxLength: 50 example: "홍길동" phoneNumber: type: string description: 참여자 전화번호 (하이픈 포함) pattern: '^\d{3}-\d{3,4}-\d{4}$' example: "010-1234-5678" email: type: string format: email description: 참여자 이메일 example: "hong@example.com" agreeMarketing: type: boolean description: 마케팅 정보 수신 동의 default: false example: true agreePrivacy: type: boolean description: 개인정보 수집 및 이용 동의 (필수) example: true storeVisited: type: boolean description: 매장 방문 여부 default: false example: false ParticipationResponse: type: object properties: success: type: boolean example: true message: type: string example: "이벤트 참여가 완료되었습니다" data: $ref: '#/components/schemas/ParticipantInfo' ParticipantInfo: type: object properties: participantId: type: string description: 참여자 ID example: "prt_20250123_001" eventId: type: string description: 이벤트 ID example: "evt_20250123_001" name: type: string description: 참여자 이름 example: "홍길동" phoneNumber: type: string description: 참여자 전화번호 example: "010-1234-5678" email: type: string description: 참여자 이메일 example: "hong@example.com" participatedAt: type: string format: date-time description: 참여 일시 example: "2025-01-23T10:30:00Z" storeVisited: type: boolean description: 매장 방문 여부 example: false bonusEntries: type: integer description: 보너스 응모권 수 (매장 방문 시 +1) minimum: 1 example: 1 isWinner: type: boolean description: 당첨 여부 example: false ParticipantDetailInfo: allOf: - $ref: '#/components/schemas/ParticipantInfo' - type: object properties: agreeMarketing: type: boolean description: 마케팅 정보 수신 동의 example: true agreePrivacy: type: boolean description: 개인정보 수집 및 이용 동의 example: true winnerInfo: $ref: '#/components/schemas/WinnerInfo' nullable: true ParticipantListResponse: type: object properties: success: type: boolean example: true message: type: string example: "참여자 목록을 조회했습니다" data: type: object properties: participants: type: array items: $ref: '#/components/schemas/ParticipantInfo' pagination: $ref: '#/components/schemas/Pagination' ParticipantDetailResponse: type: object properties: success: type: boolean example: true message: type: string example: "참여자 정보를 조회했습니다" data: $ref: '#/components/schemas/ParticipantDetailInfo' DrawWinnersRequest: type: object required: - winnerCount properties: winnerCount: type: integer description: 당첨자 수 minimum: 1 example: 10 applyStoreVisitBonus: type: boolean description: 매장 방문 보너스 적용 여부 default: true example: true DrawWinnersResponse: type: object properties: success: type: boolean example: true message: type: string example: "당첨자 추첨이 완료되었습니다" data: type: object properties: eventId: type: string description: 이벤트 ID example: "evt_20250123_001" totalParticipants: type: integer description: 전체 참여자 수 example: 156 winnerCount: type: integer description: 당첨자 수 example: 10 drawnAt: type: string format: date-time description: 추첨 일시 example: "2025-01-24T15:00:00Z" winners: type: array description: 당첨자 목록 items: $ref: '#/components/schemas/WinnerSummary' WinnerSummary: type: object properties: participantId: type: string description: 참여자 ID example: "prt_20250123_002" name: type: string description: 당첨자 이름 example: "김철수" phoneNumber: type: string description: 당첨자 전화번호 example: "010-9876-5432" rank: type: integer description: 당첨 순위 minimum: 1 example: 1 WinnerInfo: type: object properties: participantId: type: string description: 참여자 ID example: "prt_20250123_002" name: type: string description: 당첨자 이름 example: "김철수" phoneNumber: type: string description: 당첨자 전화번호 example: "010-9876-5432" email: type: string description: 당첨자 이메일 example: "kim@example.com" rank: type: integer description: 당첨 순위 minimum: 1 example: 1 wonAt: type: string format: date-time description: 당첨 일시 example: "2025-01-24T15:00:00Z" WinnerListResponse: type: object properties: success: type: boolean example: true message: type: string example: "당첨자 목록을 조회했습니다" data: type: object properties: eventId: type: string description: 이벤트 ID example: "evt_20250123_001" drawnAt: type: string format: date-time description: 추첨 일시 example: "2025-01-24T15:00:00Z" totalWinners: type: integer description: 전체 당첨자 수 example: 10 winners: type: array items: $ref: '#/components/schemas/WinnerInfo' pagination: $ref: '#/components/schemas/Pagination' Pagination: type: object properties: currentPage: type: integer description: 현재 페이지 번호 (0부터 시작) minimum: 0 example: 0 pageSize: type: integer description: 페이지 크기 minimum: 1 example: 20 totalElements: type: integer description: 전체 요소 수 minimum: 0 example: 156 totalPages: type: integer description: 전체 페이지 수 minimum: 0 example: 8 hasNext: type: boolean description: 다음 페이지 존재 여부 example: true hasPrevious: type: boolean description: 이전 페이지 존재 여부 example: false ErrorResponse: type: object properties: success: type: boolean example: false error: type: object properties: code: type: string description: 에러 코드 example: "DUPLICATE_PARTICIPATION" message: type: string description: 에러 메시지 example: "이미 참여하신 이벤트입니다" details: type: object description: 추가 에러 상세 정보 additionalProperties: true nullable: true