import axios, { AxiosInstance } from 'axios'; // Event Service API 클라이언트 const EVENT_API_BASE_URL = process.env.NEXT_PUBLIC_EVENT_HOST || 'http://localhost:8080'; const API_VERSION = process.env.NEXT_PUBLIC_API_VERSION || 'v1'; export const eventApiClient: AxiosInstance = axios.create({ baseURL: `${EVENT_API_BASE_URL}/api/${API_VERSION}`, timeout: 30000, // Job 폴링 고려 headers: { 'Content-Type': 'application/json', }, withCredentials: false, // CORS 설정 }); // Request interceptor eventApiClient.interceptors.request.use( (config) => { console.log('📅 Event API Request:', { method: config.method?.toUpperCase(), url: config.url, baseURL: config.baseURL, fullURL: `${config.baseURL}${config.url}`, }); // POST/PUT 요청일 경우 payload를 JSON 형태로 출력 if (config.data) { console.log('📦 Request Payload (JSON):', JSON.stringify(config.data, null, 2)); console.log('📦 Request Payload (Object):', config.data); } const token = localStorage.getItem('accessToken'); if (token && config.headers) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { console.error('❌ Event API Request Error:', error); return Promise.reject(error); } ); // Response interceptor eventApiClient.interceptors.response.use( (response) => { console.log('✅ Event API Response:', { status: response.status, url: response.config.url, data: response.data, }); return response; }, (error) => { console.error('❌ Event API Error:', { message: error.message, status: error.response?.status, url: error.config?.url, requestData: error.config?.data, responseData: error.response?.data, }); // 400 에러일 경우 더 상세한 정보 출력 if (error.response?.status === 400) { console.error('🚨 400 Bad Request 상세 정보:'); console.error(' 요청 URL:', `${error.config?.baseURL}${error.config?.url}`); console.error(' 요청 본문:', JSON.stringify(error.config?.data, null, 2)); console.error(' 응답 본문:', JSON.stringify(error.response?.data, null, 2)); } return Promise.reject(error); } ); // Types export interface EventObjectiveRequest { objective: string; // "신규 고객 유치", "재방문 유도", "매출 증대", "브랜드 인지도 향상" } export interface EventCreatedResponse { eventId: string; status: 'DRAFT' | 'PUBLISHED' | 'ENDED'; objective: string; createdAt: string; } export interface AiRecommendationRequest { objective: string; storeInfo: { storeId: string; storeName: string; category: string; description?: string; }; region?: string; targetAudience?: string; budget?: number; } export interface JobAcceptedResponse { jobId: string; status: 'PENDING'; message: string; } export interface EventJobStatusResponse { jobId: string; jobType: 'AI_RECOMMENDATION' | 'IMAGE_GENERATION'; status: 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED'; progress: number; resultKey?: string; errorMessage?: string; createdAt: string; completedAt?: string; } export interface TrendKeyword { keyword: string; relevance: number; description: string; } export interface TrendAnalysis { industryTrends: TrendKeyword[]; regionalTrends: TrendKeyword[]; seasonalTrends: TrendKeyword[]; } export interface ExpectedMetrics { newCustomers: { min: number; max: number; }; repeatVisits?: { min: number; max: number; }; revenueIncrease: { min: number; max: number; }; roi: { min: number; max: number; }; socialEngagement?: { estimatedPosts: number; estimatedReach: number; }; } export interface EventRecommendation { optionNumber: number; concept: string; title: string; description: string; targetAudience: string; duration: { recommendedDays: number; recommendedPeriod?: string; }; mechanics: { type: 'DISCOUNT' | 'GIFT' | 'STAMP' | 'EXPERIENCE' | 'LOTTERY' | 'COMBO'; details: string; }; promotionChannels: string[]; estimatedCost: { min: number; max: number; breakdown?: { material?: number; promotion?: number; discount?: number; }; }; expectedMetrics: ExpectedMetrics; differentiator: string; } export interface AiRecommendationResult { eventId: string; trendAnalysis: TrendAnalysis; recommendations: EventRecommendation[]; generatedAt: string; expiresAt?: string; aiProvider: 'CLAUDE' | 'GPT4'; } export interface SelectRecommendationRequest { recommendationId: string; customizations?: { eventName?: string; description?: string; startDate?: string; endDate?: string; discountRate?: number; }; } export interface ImageGenerationRequest { eventInfo: { eventName: string; description: string; promotionType: string; }; imageCount?: number; } export interface SelectChannelsRequest { channels: ('WEBSITE' | 'KAKAO' | 'INSTAGRAM' | 'FACEBOOK' | 'NAVER_BLOG')[]; } export interface ChannelDistributionResult { channel: string; success: boolean; url?: string; message: string; } export interface EventPublishedResponse { eventId: string; status: 'PUBLISHED'; publishedAt: string; channels: string[]; distributionResults: ChannelDistributionResult[]; } export interface EventSummary { eventId: string; eventName: string; objective: string; status: 'DRAFT' | 'PUBLISHED' | 'ENDED'; startDate: string; endDate: string; thumbnailUrl?: string; createdAt: string; } export interface PageInfo { page: number; size: number; totalElements: number; totalPages: number; } export interface EventListResponse { content: EventSummary[]; page: PageInfo; } export interface GeneratedImage { imageId: string; imageUrl: string; isSelected: boolean; createdAt: string; } export interface AiRecommendation { recommendationId: string; eventName: string; description: string; promotionType: string; targetAudience: string; isSelected: boolean; } export interface EventDetailResponse { eventId: string; userId: string; storeId: string; eventName: string; objective: string; description: string; targetAudience: string; 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; } export interface UpdateEventRequest { eventName?: string; description?: string; startDate?: string; endDate?: string; discountRate?: number; } export interface EndEventRequest { reason: string; } // API Functions export const eventApi = { // Step 1: 목적 선택 및 이벤트 생성 selectObjective: async (objective: string): Promise => { const response = await eventApiClient.post('/events/objectives', { objective, }); return response.data; }, // Step 2: AI 추천 요청 (POST) requestAiRecommendations: async ( eventId: string, request: AiRecommendationRequest ): Promise => { const response = await eventApiClient.post( `/events/${eventId}/ai-recommendations`, request ); console.log('✅ AI 추천 요청 성공:', response.data); return response.data; }, // AI 추천 결과 조회 (GET) getAiRecommendations: async (eventId: string): Promise => { const response = await eventApiClient.get<{ success: boolean; data: AiRecommendationResult; timestamp: string }>( `/events/${eventId}/ai-recommendations` ); console.log('✅ AI 추천 결과 조회 (전체 응답):', response.data); console.log('✅ AI 추천 데이터:', response.data.data); return response.data.data; // 래퍼에서 실제 데이터 추출 }, // AI 추천 선택 selectRecommendation: async ( eventId: string, request: SelectRecommendationRequest ): Promise => { const response = await eventApiClient.put( `/events/${eventId}/recommendations`, request ); return response.data; }, // Step 3: 이미지 생성 요청 requestImageGeneration: async ( eventId: string, request: ImageGenerationRequest ): Promise => { const response = await eventApiClient.post(`/events/${eventId}/images`, request); return response.data; }, // 이미지 선택 selectImage: async (eventId: string, imageId: string): Promise => { const response = await eventApiClient.put( `/events/${eventId}/images/${imageId}/select` ); return response.data; }, // Step 4: 이미지 편집 editImage: async ( eventId: string, imageId: string, editRequest: any ): Promise<{ imageId: string; imageUrl: string; editedAt: string }> => { const response = await eventApiClient.put(`/events/${eventId}/images/${imageId}/edit`, editRequest); return response.data; }, // Step 5: 배포 채널 선택 selectChannels: async (eventId: string, channels: string[]): Promise => { const response = await eventApiClient.put(`/events/${eventId}/channels`, { channels, }); return response.data; }, // Step 6: 최종 배포 publishEvent: async (eventId: string): Promise => { const response = await eventApiClient.post(`/events/${eventId}/publish`); return response.data; }, // 이벤트 목록 조회 getEvents: async (params?: { status?: 'DRAFT' | 'PUBLISHED' | 'ENDED'; objective?: string; search?: string; page?: number; size?: number; sort?: string; order?: 'asc' | 'desc'; }): Promise => { const response = await eventApiClient.get('/events', { params }); return response.data; }, // 이벤트 상세 조회 getEventDetail: async (eventId: string): Promise => { const response = await eventApiClient.get(`/events/${eventId}`); return response.data; }, // 이벤트 수정 updateEvent: async (eventId: string, request: UpdateEventRequest): Promise => { const response = await eventApiClient.put(`/events/${eventId}`, request); return response.data; }, // 이벤트 삭제 deleteEvent: async (eventId: string): Promise => { await eventApiClient.delete(`/events/${eventId}`); }, // 이벤트 조기 종료 endEvent: async (eventId: string, reason: string): Promise => { const response = await eventApiClient.post(`/events/${eventId}/end`, { reason, }); return response.data; }, }; export default eventApi;