Merge pull request #12 from ktds-dg0501/feature/distribution

Merge feature/distribution into develop
This commit is contained in:
이선민 2025-10-30 13:05:52 +09:00 committed by GitHub
commit 0117b64e0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 251 additions and 235 deletions

View File

@ -11,10 +11,10 @@ info:
- Retry 패턴 및 Fallback 처리 - Retry 패턴 및 Fallback 처리
## 배포 채널 ## 배포 채널
- **우리동네TV**: 영상 콘텐츠 업로드 - **우리동네TV** (URIDONGNETV): 영상 콘텐츠 업로드
- **링고비즈**: 연결음 업데이트 - **링고비즈** (RINGOBIZ): 연결음 업데이트
- **지니TV**: 광고 등록 - **지니TV** (GINITV): 광고 등록
- **SNS**: Instagram, Naver Blog, Kakao Channel - **SNS**: Instagram (INSTAGRAM), Naver Blog (NAVER), Kakao Channel (KAKAO)
## Resilience 패턴 ## Resilience 패턴
- Circuit Breaker: 채널별 독립적 장애 격리 - Circuit Breaker: 채널별 독립적 장애 격리
@ -79,23 +79,21 @@ paths:
summary: 다중 채널 배포 예시 summary: 다중 채널 배포 예시
value: value:
eventId: "evt-12345" eventId: "evt-12345"
title: "신규 고객 환영 이벤트"
description: "신규 고객님을 위한 특별 할인 이벤트"
imageUrl: "https://cdn.example.com/images/event-main.jpg"
channels: channels:
- type: "WOORIDONGNE_TV" - "URIDONGNETV"
config: - "INSTAGRAM"
radius: "1km" - "NAVER"
timeSlots: channelSettings:
- "weekday_evening" URIDONGNETV:
- "weekend_lunch" radius: "1km"
- type: "INSTAGRAM" timeSlot: "evening"
config: INSTAGRAM:
scheduledTime: "2025-11-01T10:00:00Z" scheduledTime: "2025-11-01T10:00:00"
- type: "NAVER_BLOG" NAVER:
config: scheduledTime: "2025-11-01T10:30:00"
scheduledTime: "2025-11-01T10:30:00Z"
contentUrls:
instagram: "https://cdn.example.com/images/event-instagram.jpg"
naverBlog: "https://cdn.example.com/images/event-naver.jpg"
kakaoChannel: "https://cdn.example.com/images/event-kakao.jpg"
responses: responses:
'200': '200':
description: 배포 완료 description: 배포 완료
@ -107,25 +105,29 @@ paths:
allSuccess: allSuccess:
summary: 모든 채널 배포 성공 summary: 모든 채널 배포 성공
value: value:
distributionId: "dist-12345"
eventId: "evt-12345" eventId: "evt-12345"
status: "COMPLETED" success: true
completedAt: "2025-11-01T09:00:00Z" channelResults:
results: - channel: "URIDONGNETV"
- channel: "WOORIDONGNE_TV" success: true
status: "SUCCESS"
distributionId: "wtv-uuid-12345" distributionId: "wtv-uuid-12345"
estimatedViews: 1000 estimatedReach: 1000
message: "배포 완료" executionTimeMs: 234
- channel: "INSTAGRAM" - channel: "INSTAGRAM"
status: "SUCCESS" success: true
postUrl: "https://instagram.com/p/generated-post-id" distributionId: "ig-uuid-12345"
postId: "ig-post-12345" estimatedReach: 500
message: "게시 완료" executionTimeMs: 456
- channel: "NAVER_BLOG" - channel: "NAVER"
status: "SUCCESS" success: true
postUrl: "https://blog.naver.com/store123/generated-post" distributionId: "naver-uuid-12345"
message: "게시 완료" estimatedReach: 300
executionTimeMs: 123
successCount: 3
failureCount: 0
completedAt: "2025-11-01T09:00:00"
totalExecutionTimeMs: 1234
message: "배포가 성공적으로 완료되었습니다"
'400': '400':
description: 잘못된 요청 description: 잘못된 요청
content: content:
@ -217,67 +219,77 @@ paths:
value: value:
eventId: "evt-12345" eventId: "evt-12345"
overallStatus: "COMPLETED" overallStatus: "COMPLETED"
completedAt: "2025-11-01T09:00:00Z" startedAt: "2025-11-01T08:58:00"
completedAt: "2025-11-01T09:00:00"
channels: channels:
- channel: "WOORIDONGNE_TV" - channel: "URIDONGNETV"
status: "COMPLETED" status: "COMPLETED"
distributionId: "wtv-uuid-12345" distributionId: "wtv-uuid-12345"
estimatedViews: 1500 estimatedViews: 1500
completedAt: "2025-11-01T09:00:00Z" completedAt: "2025-11-01T09:00:00"
- channel: "RINGO_BIZ" - channel: "RINGOBIZ"
status: "COMPLETED" status: "COMPLETED"
updateTimestamp: "2025-11-01T09:00:00Z" updateTimestamp: "2025-11-01T09:00:00"
- channel: "GENIE_TV" completedAt: "2025-11-01T09:00:00"
- channel: "GINITV"
status: "COMPLETED" status: "COMPLETED"
adId: "gtv-uuid-12345" adId: "gtv-uuid-12345"
impressionSchedule: impressionSchedule:
- "2025-11-01 18:00-20:00" - "2025-11-01 18:00-20:00"
- "2025-11-02 12:00-14:00" - "2025-11-02 12:00-14:00"
completedAt: "2025-11-01T09:00:00"
- channel: "INSTAGRAM" - channel: "INSTAGRAM"
status: "COMPLETED" status: "COMPLETED"
postUrl: "https://instagram.com/p/generated-post-id" postUrl: "https://instagram.com/p/generated-post-id"
postId: "ig-post-12345" postId: "ig-post-12345"
- channel: "NAVER_BLOG" completedAt: "2025-11-01T09:00:00"
- channel: "NAVER"
status: "COMPLETED" status: "COMPLETED"
postUrl: "https://blog.naver.com/store123/generated-post" postUrl: "https://blog.naver.com/store123/generated-post"
- channel: "KAKAO_CHANNEL" completedAt: "2025-11-01T09:00:00"
- channel: "KAKAO"
status: "COMPLETED" status: "COMPLETED"
messageId: "kakao-msg-12345" messageId: "kakao-msg-12345"
completedAt: "2025-11-01T09:00:00"
inProgress: inProgress:
summary: 배포 진행중 상태 summary: 배포 진행중 상태
value: value:
eventId: "evt-12345" eventId: "evt-12345"
overallStatus: "IN_PROGRESS" overallStatus: "IN_PROGRESS"
startedAt: "2025-11-01T08:58:00Z" startedAt: "2025-11-01T08:58:00"
channels: channels:
- channel: "WOORIDONGNE_TV" - channel: "URIDONGNETV"
status: "COMPLETED" status: "COMPLETED"
distributionId: "wtv-uuid-12345" distributionId: "wtv-uuid-12345"
estimatedViews: 1500 estimatedViews: 1500
completedAt: "2025-11-01T08:59:00"
- channel: "INSTAGRAM" - channel: "INSTAGRAM"
status: "IN_PROGRESS" status: "IN_PROGRESS"
progress: 50 progress: 50
- channel: "NAVER_BLOG" - channel: "NAVER"
status: "PENDING" status: "PENDING"
partialFailure: partialFailure:
summary: 일부 채널 실패 상태 summary: 일부 채널 실패 상태
value: value:
eventId: "evt-12345" eventId: "evt-12345"
overallStatus: "PARTIAL_FAILURE" overallStatus: "PARTIAL_FAILURE"
completedAt: "2025-11-01T09:00:00Z" startedAt: "2025-11-01T08:58:00"
completedAt: "2025-11-01T09:00:00"
channels: channels:
- channel: "WOORIDONGNE_TV" - channel: "URIDONGNETV"
status: "COMPLETED" status: "COMPLETED"
distributionId: "wtv-uuid-12345" distributionId: "wtv-uuid-12345"
estimatedViews: 1500 estimatedViews: 1500
completedAt: "2025-11-01T08:59:00"
- channel: "INSTAGRAM" - channel: "INSTAGRAM"
status: "FAILED" status: "FAILED"
errorMessage: "Instagram API 타임아웃" errorMessage: "Instagram API 타임아웃"
retries: 3 retries: 3
lastRetryAt: "2025-11-01T08:59:30Z" lastRetryAt: "2025-11-01T08:59:30"
- channel: "NAVER_BLOG" - channel: "NAVER"
status: "COMPLETED" status: "COMPLETED"
postUrl: "https://blog.naver.com/store123/generated-post" postUrl: "https://blog.naver.com/store123/generated-post"
completedAt: "2025-11-01T09:00:00"
'404': '404':
description: 배포 이력을 찾을 수 없음 description: 배포 이력을 찾을 수 없음
content: content:
@ -305,196 +317,133 @@ components:
required: required:
- eventId - eventId
- channels - channels
- contentUrls
properties: properties:
eventId: eventId:
type: string type: string
description: 이벤트 ID description: 이벤트 ID
example: "evt-12345" example: "evt-12345"
title:
type: string
description: 이벤트 제목
example: "신규 고객 환영 이벤트"
description:
type: string
description: 이벤트 설명
example: "신규 고객님을 위한 특별 할인 이벤트"
imageUrl:
type: string
description: 이미지 URL (CDN)
example: "https://cdn.example.com/images/event-main.jpg"
channels: channels:
type: array type: array
description: 배포할 채널 목록 description: 배포할 채널 목록
minItems: 1 minItems: 1
items: items:
$ref: '#/components/schemas/ChannelConfig' type: string
contentUrls: enum:
- URIDONGNETV
- RINGOBIZ
- GINITV
- INSTAGRAM
- NAVER
- KAKAO
example: ["URIDONGNETV", "INSTAGRAM", "NAVER"]
channelSettings:
type: object type: object
description: 플랫폼별 콘텐츠 URL description: 채널별 추가 설정 (Optional)
properties: additionalProperties:
wooridongneTV: type: object
type: string additionalProperties: true
description: 우리동네TV 영상 URL (15초)
example: "https://cdn.example.com/videos/event-15s.mp4"
ringoBiz:
type: string
description: 링고비즈 연결음 파일 URL
example: "https://cdn.example.com/audio/ringtone.mp3"
genieTV:
type: string
description: 지니TV 광고 영상 URL
example: "https://cdn.example.com/videos/event-ad.mp4"
instagram:
type: string
description: Instagram 이미지 URL (1080x1080)
example: "https://cdn.example.com/images/event-instagram.jpg"
naverBlog:
type: string
description: Naver Blog 이미지 URL (800x600)
example: "https://cdn.example.com/images/event-naver.jpg"
kakaoChannel:
type: string
description: Kakao Channel 이미지 URL (800x800)
example: "https://cdn.example.com/images/event-kakao.jpg"
ChannelConfig:
type: object
required:
- type
properties:
type:
type: string
description: 채널 타입
enum:
- WOORIDONGNE_TV
- RINGO_BIZ
- GENIE_TV
- INSTAGRAM
- NAVER_BLOG
- KAKAO_CHANNEL
example: "INSTAGRAM"
config:
type: object
description: 채널별 설정 (채널에 따라 다름)
additionalProperties: true
example: example:
scheduledTime: "2025-11-01T10:00:00Z" URIDONGNETV:
caption: "이벤트 안내" radius: "1km"
hashtags: timeSlot: "evening"
- "이벤트" INSTAGRAM:
- "할인" scheduledTime: "2025-11-01T10:00:00"
DistributionResponse: DistributionResponse:
type: object type: object
required: required:
- distributionId
- eventId - eventId
- status - success
- results - channelResults
- successCount
- failureCount
properties: properties:
distributionId:
type: string
description: 배포 ID
example: "dist-12345"
eventId: eventId:
type: string type: string
description: 이벤트 ID description: 이벤트 ID
example: "evt-12345" example: "evt-12345"
status: success:
type: string type: boolean
description: 전체 배포 상태 description: 배포 성공 여부 (모든 채널 또는 일부 채널 성공)
enum: example: true
- PENDING channelResults:
- IN_PROGRESS type: array
- COMPLETED description: 채널별 배포 결과
- PARTIAL_FAILURE items:
- FAILED $ref: '#/components/schemas/ChannelDistributionResult'
example: "COMPLETED" successCount:
startedAt: type: integer
type: string description: 성공한 채널 수
format: date-time example: 3
description: 배포 시작 시각 failureCount:
example: "2025-11-01T08:59:00Z" type: integer
description: 실패한 채널 수
example: 0
completedAt: completedAt:
type: string type: string
format: date-time format: date-time
description: 배포 완료 시각 description: 배포 완료 시각
example: "2025-11-01T09:00:00Z" example: "2025-11-01T09:00:00"
results: totalExecutionTimeMs:
type: array type: integer
description: 채널별 배포 결과 format: int64
items: description: 전체 배포 소요 시간 (ms)
$ref: '#/components/schemas/ChannelResult' example: 1234
message:
type: string
description: 메시지
example: "배포가 성공적으로 완료되었습니다"
ChannelResult: ChannelDistributionResult:
type: object type: object
required: required:
- channel - channel
- status - success
properties: properties:
channel: channel:
type: string type: string
description: 채널 타입 description: 채널 타입
enum: enum:
- WOORIDONGNE_TV - URIDONGNETV
- RINGO_BIZ - RINGOBIZ
- GENIE_TV - GINITV
- INSTAGRAM - INSTAGRAM
- NAVER_BLOG - NAVER
- KAKAO_CHANNEL - KAKAO
example: "INSTAGRAM" example: "INSTAGRAM"
status: success:
type: string type: boolean
description: 채널별 배포 상태 description: 배포 성공 여부
enum: example: true
- PENDING
- IN_PROGRESS
- SUCCESS
- FAILED
example: "SUCCESS"
distributionId: distributionId:
type: string type: string
description: 채널별 배포 ID (우리동네TV, 지니TV) description: 배포 ID (성공 시)
example: "wtv-uuid-12345" example: "dist-uuid-12345"
estimatedViews: estimatedReach:
type: integer type: integer
description: 예상 노출 수 (우리동네TV, 지니TV) description: 예상 노출 수 (성공 시)
example: 1500 example: 1500
updateTimestamp:
type: string
format: date-time
description: 업데이트 완료 시각 (링고비즈)
example: "2025-11-01T09:00:00Z"
adId:
type: string
description: 광고 ID (지니TV)
example: "gtv-uuid-12345"
impressionSchedule:
type: array
description: 노출 스케줄 (지니TV)
items:
type: string
example:
- "2025-11-01 18:00-20:00"
- "2025-11-02 12:00-14:00"
postUrl:
type: string
description: 게시물 URL (Instagram, Naver Blog)
example: "https://instagram.com/p/generated-post-id"
postId:
type: string
description: 게시물 ID (Instagram)
example: "ig-post-12345"
messageId:
type: string
description: 메시지 ID (Kakao Channel)
example: "kakao-msg-12345"
message:
type: string
description: 결과 메시지
example: "배포 완료"
errorMessage: errorMessage:
type: string type: string
description: 오류 메시지 (실패 시) description: 에러 메시지 (실패 시)
example: "Instagram API 타임아웃" example: "Instagram API 타임아웃"
retries: executionTimeMs:
type: integer type: integer
description: 재시도 횟수 format: int64
example: 0 description: 배포 소요 시간 (ms)
lastRetryAt: example: 234
type: string
format: date-time
description: 마지막 재시도 시각
example: "2025-11-01T08:59:30Z"
DistributionStatusResponse: DistributionStatusResponse:
type: object type: object
@ -544,12 +493,12 @@ components:
type: string type: string
description: 채널 타입 description: 채널 타입
enum: enum:
- WOORIDONGNE_TV - URIDONGNETV
- RINGO_BIZ - RINGOBIZ
- GENIE_TV - GINITV
- INSTAGRAM - INSTAGRAM
- NAVER_BLOG - NAVER
- KAKAO_CHANNEL - KAKAO
example: "INSTAGRAM" example: "INSTAGRAM"
status: status:
type: string type: string
@ -569,7 +518,7 @@ components:
distributionId: distributionId:
type: string type: string
description: 채널별 배포 ID description: 채널별 배포 ID
example: "wtv-uuid-12345" example: "dist-uuid-12345"
estimatedViews: estimatedViews:
type: integer type: integer
description: 예상 노출 수 description: 예상 노출 수
@ -578,35 +527,35 @@ components:
type: string type: string
format: date-time format: date-time
description: 업데이트 완료 시각 description: 업데이트 완료 시각
example: "2025-11-01T09:00:00Z" example: "2025-11-01T09:00:00"
adId: adId:
type: string type: string
description: 광고 ID description: 광고 ID (지니TV)
example: "gtv-uuid-12345" example: "gtv-uuid-12345"
impressionSchedule: impressionSchedule:
type: array type: array
description: 노출 스케줄 description: 노출 스케줄 (지니TV)
items: items:
type: string type: string
example: example:
- "2025-11-01 18:00-20:00" - "2025-11-01 18:00-20:00"
postUrl: postUrl:
type: string type: string
description: 게시물 URL description: 게시물 URL (Instagram, Naver Blog)
example: "https://instagram.com/p/generated-post-id" example: "https://instagram.com/p/generated-post-id"
postId: postId:
type: string type: string
description: 게시물 ID description: 게시물 ID (Instagram)
example: "ig-post-12345" example: "ig-post-12345"
messageId: messageId:
type: string type: string
description: 메시지 ID description: 메시지 ID (Kakao Channel)
example: "kakao-msg-12345" example: "kakao-msg-12345"
completedAt: completedAt:
type: string type: string
format: date-time format: date-time
description: 완료 시각 description: 완료 시각
example: "2025-11-01T09:00:00Z" example: "2025-11-01T09:00:00"
errorMessage: errorMessage:
type: string type: string
description: 오류 메시지 description: 오류 메시지
@ -619,7 +568,7 @@ components:
type: string type: string
format: date-time format: date-time
description: 마지막 재시도 시각 description: 마지막 재시도 시각
example: "2025-11-01T08:59:30Z" example: "2025-11-01T08:59:30"
ErrorResponse: ErrorResponse:
type: object type: object

View File

@ -23,6 +23,7 @@ import { cardStyles, colors, responsiveText } from '@/shared/lib/button-styles';
import { eventApi } from '@/entities/event/api/eventApi'; import { eventApi } from '@/entities/event/api/eventApi';
import type { EventObjective } from '@/entities/event/model/types'; import type { EventObjective } from '@/entities/event/model/types';
interface ApprovalStepProps { interface ApprovalStepProps {
eventData: EventData; eventData: EventData;
onApprove: () => void; onApprove: () => void;
@ -34,6 +35,7 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
const [termsDialogOpen, setTermsDialogOpen] = useState(false); const [termsDialogOpen, setTermsDialogOpen] = useState(false);
const [successDialogOpen, setSuccessDialogOpen] = useState(false); const [successDialogOpen, setSuccessDialogOpen] = useState(false);
const [isDeploying, setIsDeploying] = useState(false); const [isDeploying, setIsDeploying] = useState(false);
const DISTRIBUTION_API_BASE_URL = process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || 'http://kt-event-marketing-api.20.214.196.128.nip.io';
const handleApprove = async () => { const handleApprove = async () => {
if (!agreeTerms) return; if (!agreeTerms) return;
@ -86,39 +88,46 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
}); });
console.log('✅ Event details updated'); console.log('✅ Event details updated');
// 3. 배포 채널 선택 // 채널명 매핑 (Frontend → Backend)
if (eventData.channels && eventData.channels.length > 0) { const channelMap: Record<string, string[]> = {
console.log('📞 Selecting channels:', eventData.channels); uriTV: ['URIDONGNETV'],
ringoBiz: ['RINGOBIZ'],
genieTV: ['GINITV'],
sns: ['INSTAGRAM', 'NAVER', 'KAKAO'],
};
// 채널명 매핑 (Frontend → Backend) const apiChannels = eventData.channels?.flatMap(ch => channelMap[ch] || []) || [];
const channelMap: Record<string, string> = {
'uriTV': 'WEBSITE',
'ringoBiz': 'EMAIL',
'genieTV': 'KAKAO',
'sns': 'INSTAGRAM',
};
const backendChannels = eventData.channels.map(ch => channelMap[ch] || ch.toUpperCase()); const distributionRequest = {
eventId: eventId,
title: eventName,
description: eventData.contentEdit?.guide || eventData.recommendation?.recommendation?.description || '',
imageUrl: '', // TODO: 이미지 URL 연동 필요
channels: apiChannels,
channelSettings: {},
};
await eventApi.selectChannels(eventId, { console.log('🚀 Distributing event:', distributionRequest);
channels: backendChannels,
}); const response = await fetch(`${DISTRIBUTION_API_BASE_URL}/api/v1/distribution/distribute`, {
console.log('✅ Channels selected'); method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(distributionRequest),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || '배포 중 오류가 발생했습니다');
} }
// 4. TODO: 이미지 선택 const data = await response.json();
// 현재 frontend에서 selectedImageId를 추적하지 않음 console.log('✅ Distribution completed:', data);
// 향후 contentPreview 단계에서 선택된 이미지 ID를 eventData에 저장 필요
console.log('⚠️ Image selection skipped - imageId not tracked in frontend');
// 5. 이벤트 배포 API 호출
console.log('📞 Publishing event:', eventId);
const publishResponse = await eventApi.publishEvent(eventId);
console.log('✅ Event published:', publishResponse);
// 성공 다이얼로그 표시
setIsDeploying(false); setIsDeploying(false);
setSuccessDialogOpen(true); setSuccessDialogOpen(true);
} else { } else {
throw new Error('Event creation failed: No event ID returned'); throw new Error('Event creation failed: No event ID returned');
} }
@ -129,6 +138,7 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
} }
}; };
const handleSaveDraft = () => { const handleSaveDraft = () => {
// TODO: 임시저장 API 연동 // TODO: 임시저장 API 연동
alert('임시저장되었습니다'); alert('임시저장되었습니다');

View File

@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from 'next/server';
const DISTRIBUTION_API_BASE_URL = process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || 'http://kt-event-marketing-api.20.214.196.128.nip.io';
export async function GET(
request: NextRequest,
{ params }: { params: { eventId: string } }
) {
try {
const { eventId } = params;
console.log('🔄 Proxying distribution status request to Distribution API:', {
url: `${DISTRIBUTION_API_BASE_URL}/api/v1/distribution/${eventId}/status`,
eventId,
});
const response = await fetch(`${DISTRIBUTION_API_BASE_URL}/distribution/${eventId}/status`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
const errorText = await response.text();
console.error('❌ Distribution API error:', response.status, errorText);
return NextResponse.json(
{ error: 'Failed to get distribution status', details: errorText },
{ status: response.status }
);
}
const data = await response.json();
console.log('✅ Distribution status retrieved:', data);
return NextResponse.json(data);
} catch (error) {
console.error('❌ Proxy error:', error);
return NextResponse.json(
{ error: 'Internal server error', details: error instanceof Error ? error.message : 'Unknown error' },
{ status: 500 }
);
}
}

View File

@ -33,6 +33,15 @@ export const participationClient: AxiosInstance = axios.create({
}, },
}); });
// Distribution API 전용 클라이언트
export const distributionClient: AxiosInstance = axios.create({
baseURL: `${API_HOSTS.distribution}`,
timeout: 90000,
headers: {
'Content-Type': 'application/json',
},
});
// 공통 Request interceptor 함수 // 공통 Request interceptor 함수
const requestInterceptor = (config: InternalAxiosRequestConfig) => { const requestInterceptor = (config: InternalAxiosRequestConfig) => {
console.log('🚀 API Request:', { console.log('🚀 API Request:', {
@ -92,4 +101,8 @@ apiClient.interceptors.response.use(responseInterceptor, responseErrorIntercepto
participationClient.interceptors.request.use(requestInterceptor, requestErrorInterceptor); participationClient.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
participationClient.interceptors.response.use(responseInterceptor, responseErrorInterceptor); participationClient.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
// Distribution API Client 인터셉터 적용
distributionClient.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
distributionClient.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
export default apiClient; export default apiClient;