cherry2250 ca4dff559c 7개 마이크로서비스 반영하여 프론트엔드 설계서 수정 (ia.md, api-mapping.md)
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 10:37:11 +09:00

32 KiB

API 매핑 설계서

목차

  1. API 경로 매핑
  2. 화면별 API 매핑
  3. API 호출 예시

1. API 경로 매핑

1.1 Runtime 환경 변수 설정

public/runtime-env.js

/**
 * 런타임 환경 변수 설정
 * - 빌드 시점이 아닌 실행 시점에 환경별 API 호스트 설정
 * - 도커 컨테이너 환경에서 환경변수 주입 가능
 */
window.__runtime_config__ = {
  // API 그룹 경로 (버전 포함)
  API_GROUP: "/api/v1",

  // 7개 마이크로서비스 호스트
  USER_HOST: process.env.NEXT_PUBLIC_USER_HOST || "http://localhost:8081",
  EVENT_HOST: process.env.NEXT_PUBLIC_EVENT_HOST || "http://localhost:8080",
  CONTENT_HOST: process.env.NEXT_PUBLIC_CONTENT_HOST || "http://localhost:8082",
  AI_HOST: process.env.NEXT_PUBLIC_AI_HOST || "http://localhost:8083",
  PARTICIPATION_HOST: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || "http://localhost:8084",
  DISTRIBUTION_HOST: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || "http://localhost:8085",
  ANALYTICS_HOST: process.env.NEXT_PUBLIC_ANALYTICS_HOST || "http://localhost:8086",
};

1.2 API 클라이언트 초기화

src/lib/api/client.ts

import axios, { AxiosInstance } from 'axios';

// 런타임 환경 변수 타입 정의
declare global {
  interface Window {
    __runtime_config__: {
      API_GROUP: string;
      USER_HOST: string;
      EVENT_HOST: string;
      CONTENT_HOST: string;
      AI_HOST: string;
      PARTICIPATION_HOST: string;
      DISTRIBUTION_HOST: string;
      ANALYTICS_HOST: string;
    };
  }
}

const config = typeof window !== 'undefined' ? window.__runtime_config__ : {
  API_GROUP: '/api/v1',
  USER_HOST: process.env.NEXT_PUBLIC_USER_HOST || 'http://localhost:8081',
  EVENT_HOST: process.env.NEXT_PUBLIC_EVENT_HOST || 'http://localhost:8080',
  CONTENT_HOST: process.env.NEXT_PUBLIC_CONTENT_HOST || 'http://localhost:8082',
  AI_HOST: process.env.NEXT_PUBLIC_AI_HOST || 'http://localhost:8083',
  PARTICIPATION_HOST: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || 'http://localhost:8084',
  DISTRIBUTION_HOST: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || 'http://localhost:8085',
  ANALYTICS_HOST: process.env.NEXT_PUBLIC_ANALYTICS_HOST || 'http://localhost:8086',
};

// JWT 토큰 가져오기 헬퍼
const getAuthToken = (): string | null => {
  if (typeof window === 'undefined') return null;
  return localStorage.getItem('accessToken');
};

// Request Interceptor (JWT 토큰 자동 추가)
const authInterceptor = (instance: AxiosInstance) => {
  instance.interceptors.request.use(
    (config) => {
      const token = getAuthToken();
      if (token) {
        config.headers.Authorization = `Bearer ${token}`;
      }
      return config;
    },
    (error) => Promise.reject(error)
  );

  // Response Interceptor (401 처리)
  instance.interceptors.response.use(
    (response) => response,
    (error) => {
      if (error.response?.status === 401) {
        // 토큰 만료 또는 인증 실패
        localStorage.removeItem('accessToken');
        window.location.href = '/login';
      }
      return Promise.reject(error);
    }
  );
};

// 1. User Service API Client (인증, 프로필 관리)
export const userClient = axios.create({
  baseURL: `${config.USER_HOST}`,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(userClient);

// 2. Event Service API Client (이벤트 생명주기 관리)
export const eventClient = axios.create({
  baseURL: `${config.EVENT_HOST}`,
  timeout: 30000, // Job 폴링 고려
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(eventClient);

// 3. Content Service API Client (이미지 생성 및 편집)
export const contentClient = axios.create({
  baseURL: `${config.CONTENT_HOST}`,
  timeout: 30000, // 이미지 생성 Job 폴링
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(contentClient);

// 4. AI Service API Client (AI 추천 생성)
export const aiClient = axios.create({
  baseURL: `${config.AI_HOST}`,
  timeout: 30000, // AI 생성 Job 폴링
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(aiClient);

// 5. Participation Service API Client (참여자/당첨자 관리)
export const participationClient = axios.create({
  baseURL: `${config.PARTICIPATION_HOST}`,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(participationClient);

// 6. Distribution Service API Client (다중 채널 배포)
export const distributionClient = axios.create({
  baseURL: `${config.DISTRIBUTION_HOST}`,
  timeout: 20000, // 다중 채널 배포 시간 고려
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(distributionClient);

// 7. Analytics Service API Client (성과 분석 및 대시보드)
export const analyticsClient = axios.create({
  baseURL: `${config.ANALYTICS_HOST}`,
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json',
  },
});
authInterceptor(analyticsClient);

1.3 환경별 설정

환경 User Service Event Service Content Service AI Service Participation Service Distribution Service Analytics Service
로컬 개발 http://localhost:8081 http://localhost:8080 http://localhost:8082 http://localhost:8083 http://localhost:8084 http://localhost:8085 http://localhost:8086
개발 서버 https://dev-api.kt-event-marketing.com/user/v1 https://dev-api.kt-event-marketing.com/event/v1 https://dev-api.kt-event-marketing.com/content/v1 https://dev-api.kt-event-marketing.com/ai/v1 https://dev-api.kt-event-marketing.com/participation/v1 https://dev-api.kt-event-marketing.com/distribution/v1 https://dev-api.kt-event-marketing.com/analytics/v1
프로덕션 https://api.kt-event-marketing.com/user/v1 https://api.kt-event-marketing.com/event/v1 https://api.kt-event-marketing.com/content/v1 https://api.kt-event-marketing.com/ai/v1 https://api.kt-event-marketing.com/participation/v1 https://api.kt-event-marketing.com/distribution/v1 https://api.kt-event-marketing.com/analytics/v1

2. 화면별 API 매핑

2.1 인증 영역

AUTH-01: 로그인 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
로그인 User Service /users/login POST

요청 데이터

interface LoginRequest {
  phoneNumber: string;  // "010XXXXXXXX" 형식
  password: string;     // 최소 8자
}

응답 데이터

interface LoginResponse {
  token: string;        // JWT 토큰 (7일 만료)
  userId: number;
  userName: string;
  role: 'OWNER' | 'ADMIN';
  email: string;
}

AUTH-02: 회원가입 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
회원가입 User Service /users/register POST

요청 데이터

interface RegisterRequest {
  name: string;              // 2-50자
  phoneNumber: string;       // "010XXXXXXXX"
  email: string;             // 이메일 형식
  password: string;          // 8자 이상, 영문/숫자/특수문자
  storeName: string;         // 2-100자
  industry: string;          // 업종 (예: 음식점, 카페)
  address: string;           // 5-200자
  businessHours?: string;    // 영업시간 (선택)
}

응답 데이터

interface RegisterResponse {
  token: string;        // JWT 토큰 (자동 로그인)
  userId: number;
  userName: string;
  storeId: number;
  storeName: string;
}

AUTH-03: 프로필 관리 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
프로필 조회 User Service /users/profile GET
프로필 수정 User Service /users/profile PUT
비밀번호 변경 User Service /users/password PUT

프로필 조회 응답

interface ProfileResponse {
  userId: number;
  userName: string;
  phoneNumber: string;
  email: string;
  role: 'OWNER' | 'ADMIN';
  storeId: number;
  storeName: string;
  industry: string;
  address: string;
  businessHours?: string;
  createdAt: string;        // ISO 8601
  lastLoginAt: string;      // ISO 8601
}

프로필 수정 요청

interface UpdateProfileRequest {
  name?: string;
  phoneNumber?: string;
  email?: string;
  storeName?: string;
  industry?: string;
  address?: string;
  businessHours?: string;
}

비밀번호 변경 요청

interface ChangePasswordRequest {
  currentPassword: string;
  newPassword: string;      // 8자 이상, 영문/숫자/특수문자
}

AUTH-04: 로그아웃

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
로그아웃 User Service /users/logout POST

응답 데이터

interface LogoutResponse {
  success: boolean;
  message: string;  // "안전하게 로그아웃되었습니다"
}

2.2 대시보드 영역

DASH-01: 메인 대시보드

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이벤트 요약 조회 Event Service /events?status=PUBLISHED&page=0&size=5 GET
최근 이벤트 조회 Event Service /events?sort=createdAt&order=desc&page=0&size=3 GET

이벤트 목록 응답

interface EventListResponse {
  content: EventSummary[];
  page: PageInfo;
}

interface EventSummary {
  eventId: string;          // UUID
  eventName: string;
  objective: string;        // "신규 고객 유치", "재방문 유도" 등
  status: 'DRAFT' | 'PUBLISHED' | 'ENDED';
  startDate: string;        // "YYYY-MM-DD"
  endDate: string;          // "YYYY-MM-DD"
  thumbnailUrl?: string;
  createdAt: string;        // ISO 8601
}

interface PageInfo {
  page: number;             // 현재 페이지 (0부터 시작)
  size: number;             // 페이지 크기
  totalElements: number;    // 전체 요소 수
  totalPages: number;       // 전체 페이지 수
}

DASH-02: 이벤트 목록 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
전체 이벤트 조회 Event Service /events GET
상태별 필터링 Event Service /events?status={status} GET
검색 Event Service /events?search={keyword} GET

Query Parameters

  • status: DRAFT, PUBLISHED, ENDED
  • objective: 이벤트 목적 필터
  • search: 이벤트명 검색
  • page: 페이지 번호 (기본 0)
  • size: 페이지 크기 (기본 20, 최대 100)
  • sort: 정렬 기준 (createdAt, startDate, endDate)
  • order: 정렬 순서 (asc, desc)

2.3 이벤트 생성 플로우 (Funnel)

EVENT-01: 목적 선택 (Step 1)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
목적 선택 및 이벤트 생성 Event Service /events/objectives POST

요청 데이터

interface SelectObjectiveRequest {
  objective: string;  // "신규 고객 유치", "재방문 유도", "매출 증대", "브랜드 인지도 향상"
}

응답 데이터

interface EventCreatedResponse {
  eventId: string;          // UUID (생성된 이벤트 ID)
  status: 'DRAFT';          // 항상 DRAFT
  objective: string;
  createdAt: string;        // ISO 8601
}

EVENT-02: AI 추천 확인 (Step 2)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
AI 추천 요청 Event Service /events/{eventId}/ai-recommendations POST
Job 상태 폴링 Event Service /jobs/{jobId} GET
AI 추천 선택 Event Service /events/{eventId}/recommendations PUT

AI 추천 요청

interface AiRecommendationRequest {
  storeInfo: {
    storeId: string;
    storeName: string;
    category: string;
    description?: string;
  };
}

Job 접수 응답 (202 Accepted)

interface JobAcceptedResponse {
  jobId: string;            // UUID
  status: 'PENDING';
  message: string;          // "AI 추천 생성 요청이 접수되었습니다..."
}

Job 상태 조회 응답

interface JobStatusResponse {
  jobId: string;
  jobType: 'AI_RECOMMENDATION' | 'IMAGE_GENERATION';
  status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED';
  progress: number;         // 0-100 (%)
  resultKey?: string;       // Redis 결과 키 (COMPLETED 시)
  errorMessage?: string;    // 에러 메시지 (FAILED 시)
  createdAt: string;
  completedAt?: string;
}

AI 추천 선택 요청

interface SelectRecommendationRequest {
  recommendationId: string;  // 선택한 추천 ID
  customizations?: {
    eventName?: string;
    description?: string;
    startDate?: string;      // "YYYY-MM-DD"
    endDate?: string;        // "YYYY-MM-DD"
    discountRate?: number;
  };
}

EVENT-03: 콘텐츠 미리보기 (Step 3)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이미지 생성 요청 Event Service /events/{eventId}/images POST
Job 상태 폴링 Event Service /jobs/{jobId} GET
이미지 선택 Event Service /events/{eventId}/images/{imageId}/select PUT

이미지 생성 요청

interface ImageGenerationRequest {
  eventInfo: {
    eventName: string;
    description: string;
    promotionType: string;   // "할인", "증정", "쿠폰" 등
  };
  imageCount?: number;       // 1-5, 기본 3
}

EVENT-04: 콘텐츠 편집 (Step 4)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이미지 편집 Event Service /events/{eventId}/images/{imageId}/edit PUT

이미지 편집 요청

interface ImageEditRequest {
  editType: 'TEXT_OVERLAY' | 'COLOR_ADJUST' | 'CROP' | 'FILTER';
  parameters: {
    // TEXT_OVERLAY 예시
    text?: string;
    fontSize?: number;
    color?: string;           // HEX 코드
    position?: 'center' | 'top' | 'bottom';

    // COLOR_ADJUST 예시
    brightness?: number;      // -100 ~ 100
    contrast?: number;        // -100 ~ 100
    saturation?: number;      // -100 ~ 100
  };
}

이미지 편집 응답

interface ImageEditResponse {
  imageId: string;
  imageUrl: string;          // 편집된 이미지 URL
  editedAt: string;
}

EVENT-05: 배포 채널 선택 (Step 5)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
배포 채널 선택 Event Service /events/{eventId}/channels PUT

배포 채널 선택 요청

interface SelectChannelsRequest {
  channels: ('WEBSITE' | 'KAKAO' | 'INSTAGRAM' | 'FACEBOOK' | 'NAVER_BLOG')[];
  // 최소 1개 이상 선택
}

EVENT-06: 최종 승인 (Step 6)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이벤트 배포 Event Service /events/{eventId}/publish POST

이벤트 배포 응답

interface EventPublishedResponse {
  eventId: string;
  status: 'PUBLISHED';
  publishedAt: string;
  channels: string[];        // 배포된 채널 목록
  distributionResults: DistributionResult[];
}

interface DistributionResult {
  channel: string;           // "WEBSITE", "KAKAO" 등
  success: boolean;
  url?: string;              // 배포된 URL
  message: string;
}

2.4 이벤트 관리 영역

MANAGE-01: 이벤트 상세 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이벤트 상세 조회 Event Service /events/{eventId} GET
이벤트 수정 Event Service /events/{eventId} PUT
이벤트 삭제 Event Service /events/{eventId} DELETE
이벤트 조기 종료 Event Service /events/{eventId}/end POST

이벤트 상세 응답

interface EventDetailResponse {
  eventId: string;
  userId: string;
  storeId: string;
  eventName: string;
  objective: string;
  description: string;
  targetAudience: string;    // "20-30대 여성" 등
  promotionType: string;     // "할인", "증정", "쿠폰"
  discountRate?: number;     // 할인율 (%)
  startDate: string;
  endDate: string;
  status: 'DRAFT' | 'PUBLISHED' | 'ENDED';
  selectedImageId?: string;
  selectedImageUrl?: string;
  generatedImages?: GeneratedImage[];
  channels?: string[];
  aiRecommendations?: AiRecommendation[];
  createdAt: string;
  updatedAt: string;
}

interface GeneratedImage {
  imageId: string;
  imageUrl: string;
  isSelected: boolean;
  createdAt: string;
}

interface AiRecommendation {
  recommendationId: string;
  eventName: string;
  description: string;
  promotionType: string;
  targetAudience: string;
  isSelected: boolean;
}

이벤트 수정 요청

interface UpdateEventRequest {
  eventName?: string;
  description?: string;
  startDate?: string;
  endDate?: string;
  discountRate?: number;
}

이벤트 조기 종료 요청

interface EndEventRequest {
  reason: string;            // "목표 달성으로 조기 종료" 등
}

MANAGE-02: 참여자 목록 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
참여자 목록 조회 Participation Service /events/{eventId}/participants GET
참여자 상세 조회 Participation Service /events/{eventId}/participants/{participantId} GET

참여자 목록 응답

interface ParticipantListResponse {
  success: boolean;
  message: string;
  data: {
    participants: ParticipantInfo[];
    pagination: Pagination;
  };
}

interface ParticipantInfo {
  participantId: string;
  eventId: string;
  name: string;
  phoneNumber: string;       // "010-1234-5678" 형식
  email?: string;
  participatedAt: string;    // ISO 8601
  storeVisited: boolean;
  bonusEntries: number;      // 보너스 응모권 (매장 방문 시 +1)
  isWinner: boolean;
}

interface Pagination {
  currentPage: number;
  pageSize: number;
  totalElements: number;
  totalPages: number;
  hasNext: boolean;
  hasPrevious: boolean;
}

Query Parameters

  • page: 페이지 번호 (기본 0)
  • size: 페이지 크기 (기본 20, 최대 100)
  • storeVisited: 매장 방문 여부 필터 (true/false)

MANAGE-03: 고객 참여 화면 (Public)

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이벤트 참여 Participation Service /events/{eventId}/participate POST
이벤트 정보 조회 Event Service /events/{eventId} GET

참여 요청

interface ParticipationRequest {
  name: string;              // 2-50자
  phoneNumber: string;       // "010-XXXX-XXXX" 형식
  email?: string;
  agreeMarketing: boolean;   // 마케팅 정보 수신 동의
  agreePrivacy: boolean;     // 개인정보 수집 동의 (필수)
  storeVisited: boolean;     // 매장 방문 여부 (보너스 응모권)
}

참여 응답

interface ParticipationResponse {
  success: boolean;
  message: string;           // "이벤트 참여가 완료되었습니다"
  data: ParticipantInfo;
}

MANAGE-04: 당첨자 추첨 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
당첨자 추첨 Participation Service /events/{eventId}/draw-winners POST
당첨자 목록 조회 Participation Service /events/{eventId}/winners GET

당첨자 추첨 요청

interface DrawWinnersRequest {
  winnerCount: number;              // 당첨자 수 (최소 1)
  applyStoreVisitBonus?: boolean;   // 매장 방문 보너스 적용 (기본 true)
}

당첨자 추첨 응답

interface DrawWinnersResponse {
  success: boolean;
  message: string;
  data: {
    eventId: string;
    totalParticipants: number;
    winnerCount: number;
    drawnAt: string;
    winners: WinnerSummary[];
  };
}

interface WinnerSummary {
  participantId: string;
  name: string;
  phoneNumber: string;
  rank: number;              // 당첨 순위 (1등, 2등, ...)
}

당첨자 목록 응답

interface WinnerListResponse {
  success: boolean;
  message: string;
  data: {
    eventId: string;
    drawnAt: string;
    totalWinners: number;
    winners: WinnerInfo[];
    pagination: Pagination;
  };
}

interface WinnerInfo {
  participantId: string;
  name: string;
  phoneNumber: string;
  email?: string;
  rank: number;
  wonAt: string;
}

MANAGE-05: 성과 분석 화면

기능 백엔드 서비스 API 경로 HTTP 메서드 인증 필요
이벤트 상세 조회 Event Service /events/{eventId} GET
참여자 통계 Participation Service /events/{eventId}/participants GET

성과 분석용 데이터 조합

  • 이벤트 기본 정보 (Event Service)
  • 참여자 수, 매장 방문율 (Participation Service)
  • 당첨자 수 (Participation Service)

계산 지표

interface EventAnalytics {
  // 이벤트 정보
  eventName: string;
  eventPeriod: { start: string; end: string };
  status: string;

  // 참여 지표
  totalParticipants: number;
  storeVisitCount: number;
  storeVisitRate: number;        // (매장 방문 / 전체 참여) * 100

  // 당첨 지표
  totalWinners: number;
  winnerDrawnDate?: string;

  // 채널별 배포 현황
  distributionChannels: string[];

  // 시간대별 참여 추이 (프론트엔드에서 계산)
  participationTrend: {
    date: string;
    count: number;
  }[];
}

3. API 호출 예시

3.1 로그인 플로우

// src/lib/api/auth.ts
import { userClient } from './client';

export const authApi = {
  // 로그인
  async login(phoneNumber: string, password: string) {
    const response = await userClient.post('/users/login', {
      phoneNumber,
      password,
    });

    // JWT 토큰 저장
    localStorage.setItem('accessToken', response.data.token);

    return response.data;
  },

  // 로그아웃
  async logout() {
    const response = await userClient.post('/users/logout');

    // 토큰 삭제
    localStorage.removeItem('accessToken');

    return response.data;
  },

  // 회원가입
  async register(data: RegisterRequest) {
    const response = await userClient.post('/users/register', data);

    // 자동 로그인 (JWT 토큰 저장)
    localStorage.setItem('accessToken', response.data.token);

    return response.data;
  },
};

호출 예시

// 컴포넌트에서 사용
const handleLogin = async () => {
  try {
    const result = await authApi.login('01012345678', 'Password123!');
    console.log('로그인 성공:', result.userName);
    router.push('/'); // 대시보드로 이동
  } catch (error) {
    console.error('로그인 실패:', error);
    toast.error('로그인에 실패했습니다');
  }
};

3.2 이벤트 생성 플로우

// src/lib/api/events.ts
import { eventClient } from './client';

export const eventApi = {
  // Step 1: 목적 선택
  async selectObjective(objective: string) {
    const response = await eventClient.post('/events/objectives', {
      objective,
    });
    return response.data; // { eventId, status, objective, createdAt }
  },

  // Step 2: AI 추천 요청
  async requestAiRecommendations(eventId: string, storeInfo: any) {
    const response = await eventClient.post(
      `/events/${eventId}/ai-recommendations`,
      { storeInfo }
    );
    return response.data; // { jobId, status, message }
  },

  // Job 상태 폴링
  async getJobStatus(jobId: string) {
    const response = await eventClient.get(`/jobs/${jobId}`);
    return response.data;
  },

  // AI 추천 선택
  async selectRecommendation(
    eventId: string,
    recommendationId: string,
    customizations?: any
  ) {
    const response = await eventClient.put(
      `/events/${eventId}/recommendations`,
      { recommendationId, customizations }
    );
    return response.data;
  },

  // Step 3: 이미지 생성 요청
  async requestImageGeneration(eventId: string, eventInfo: any) {
    const response = await eventClient.post(`/events/${eventId}/images`, {
      eventInfo,
      imageCount: 3,
    });
    return response.data; // { jobId, status, message }
  },

  // 이미지 선택
  async selectImage(eventId: string, imageId: string) {
    const response = await eventClient.put(
      `/events/${eventId}/images/${imageId}/select`
    );
    return response.data;
  },

  // Step 4: 이미지 편집
  async editImage(eventId: string, imageId: string, editRequest: any) {
    const response = await eventClient.put(
      `/events/${eventId}/images/${imageId}/edit`,
      editRequest
    );
    return response.data;
  },

  // Step 5: 배포 채널 선택
  async selectChannels(eventId: string, channels: string[]) {
    const response = await eventClient.put(`/events/${eventId}/channels`, {
      channels,
    });
    return response.data;
  },

  // Step 6: 최종 배포
  async publishEvent(eventId: string) {
    const response = await eventClient.post(`/events/${eventId}/publish`);
    return response.data;
  },
};

Funnel 컴포넌트에서 사용 예시

// Step 2: AI 추천 컴포넌트
const RecommendationStep = ({ eventId, onNext }) => {
  const [jobId, setJobId] = useState<string | null>(null);
  const [recommendations, setRecommendations] = useState([]);

  // AI 추천 요청
  const requestRecommendations = async () => {
    const storeInfo = { /* User Service에서 조회한 매장 정보 */ };
    const result = await eventApi.requestAiRecommendations(eventId, storeInfo);
    setJobId(result.jobId);

    // Job 폴링 시작
    pollJobStatus(result.jobId);
  };

  // Job 상태 폴링 (3초 간격)
  const pollJobStatus = async (jobId: string) => {
    const interval = setInterval(async () => {
      const status = await eventApi.getJobStatus(jobId);

      if (status.status === 'COMPLETED') {
        clearInterval(interval);
        // Redis에서 결과 조회 (resultKey 사용)
        fetchRecommendations(status.resultKey);
      } else if (status.status === 'FAILED') {
        clearInterval(interval);
        toast.error('AI 추천 생성에 실패했습니다');
      }
    }, 3000);
  };

  // 추천 선택 및 다음 단계
  const handleSelectRecommendation = async (recommendationId: string) => {
    await eventApi.selectRecommendation(eventId, recommendationId);
    onNext(); // Step 3으로 이동
  };

  return (
    <div>
      {/* AI 추천 UI */}
      {recommendations.map(rec => (
        <RecommendationCard
          key={rec.recommendationId}
          recommendation={rec}
          onSelect={() => handleSelectRecommendation(rec.recommendationId)}
        />
      ))}
    </div>
  );
};

3.3 참여자 관리

// src/lib/api/participants.ts
import { participationClient } from './client';

export const participantApi = {
  // 참여자 목록 조회
  async getParticipants(
    eventId: string,
    page: number = 0,
    size: number = 20,
    storeVisited?: boolean
  ) {
    const params = new URLSearchParams({
      page: page.toString(),
      size: size.toString(),
    });

    if (storeVisited !== undefined) {
      params.append('storeVisited', storeVisited.toString());
    }

    const response = await participationClient.get(
      `/events/${eventId}/participants?${params}`
    );
    return response.data.data;
  },

  // 참여자 상세 조회
  async getParticipantDetail(eventId: string, participantId: string) {
    const response = await participationClient.get(
      `/events/${eventId}/participants/${participantId}`
    );
    return response.data.data;
  },

  // 당첨자 추첨
  async drawWinners(
    eventId: string,
    winnerCount: number,
    applyStoreVisitBonus: boolean = true
  ) {
    const response = await participationClient.post(
      `/events/${eventId}/draw-winners`,
      { winnerCount, applyStoreVisitBonus }
    );
    return response.data.data;
  },

  // 당첨자 목록 조회
  async getWinners(eventId: string, page: number = 0, size: number = 20) {
    const response = await participationClient.get(
      `/events/${eventId}/winners?page=${page}&size=${size}`
    );
    return response.data.data;
  },

  // 고객 참여 (인증 불필요)
  async participate(eventId: string, data: ParticipationRequest) {
    const response = await participationClient.post(
      `/events/${eventId}/participate`,
      data
    );
    return response.data.data;
  },
};

React Query를 사용한 데이터 패칭 예시

// src/hooks/useParticipants.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { participantApi } from '@/lib/api/participants';

export const useParticipants = (eventId: string, page: number = 0) => {
  return useQuery({
    queryKey: ['participants', eventId, page],
    queryFn: () => participantApi.getParticipants(eventId, page),
    enabled: !!eventId,
  });
};

export const useDrawWinners = (eventId: string) => {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: ({ winnerCount }: { winnerCount: number }) =>
      participantApi.drawWinners(eventId, winnerCount),
    onSuccess: () => {
      // 참여자 목록 갱신
      queryClient.invalidateQueries({ queryKey: ['participants', eventId] });
      // 당첨자 목록 갱신
      queryClient.invalidateQueries({ queryKey: ['winners', eventId] });
    },
  });
};

3.4 에러 처리

// src/lib/api/errorHandler.ts
import { AxiosError } from 'axios';

export interface ApiError {
  code: string;
  message: string;
  timestamp: string;
  details?: string[];
}

export const handleApiError = (error: unknown): string => {
  if (error instanceof AxiosError) {
    const apiError = error.response?.data as ApiError;

    // 에러 코드별 처리
    switch (apiError.code) {
      case 'USER_001':
        return '이미 가입된 전화번호입니다';
      case 'AUTH_001':
        return '전화번호 또는 비밀번호를 확인해주세요';
      case 'DUPLICATE_PARTICIPATION':
        return '이미 참여하신 이벤트입니다';
      case 'INVALID_WINNER_COUNT':
        return '당첨자 수가 참여자 수보다 많습니다';
      default:
        return apiError.message || '요청 처리 중 오류가 발생했습니다';
    }
  }

  return '알 수 없는 오류가 발생했습니다';
};

4. 요약

4.1 주요 API 엔드포인트 요약

서비스 주요 기능 엔드포인트 수
User Service 인증, 프로필 관리 6개
Event Service 이벤트 생성/관리, Job 폴링 14개
Participation Service 참여자/당첨자 관리 5개

4.2 비동기 작업 처리

AI 추천 생성 & 이미지 생성

  • 요청 시 202 Accepted 응답과 함께 jobId 반환
  • /jobs/{jobId}로 3초 간격 폴링
  • status: COMPLETEDresultKey로 Redis 결과 조회
  • status: FAILED 시 에러 메시지 표시

4.3 인증 처리

JWT 토큰 관리

  • 로그인/회원가입 성공 시 localStorage에 저장
  • 모든 API 요청 시 Authorization: Bearer {token} 헤더 자동 추가
  • 401 응답 시 자동 로그아웃 및 로그인 페이지 리다이렉트

4.4 데이터 캐싱 전략

React Query 캐싱 키

['user', 'profile']               // 프로필 정보
['events', { status, page }]      // 이벤트 목록
['event', eventId]                // 이벤트 상세
['participants', eventId, page]   // 참여자 목록
['winners', eventId]              // 당첨자 목록
['job', jobId]                    // Job 상태

문서 버전: 1.0 작성일: 2025-01-24 작성자: Frontend Design Team