mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 10:16:25 +00:00
Participation API를 client.ts로 통합 및 환경변수 설정 개선
- api-client.ts 삭제하고 client.ts의 participationClient 사용 - 마이크로서비스별 호스트 환경변수 지원 추가 - API_VERSION 환경변수로 api prefix 관리 - .env.local 파일 생성 (개발 환경 설정) - CORS 해결을 위해 백엔드에서 직접 호출하는 방식으로 단순화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bace9476b1
commit
37e5a76c50
@ -10,7 +10,8 @@
|
|||||||
"Bash(netstat:*)",
|
"Bash(netstat:*)",
|
||||||
"Bash(taskkill:*)",
|
"Bash(taskkill:*)",
|
||||||
"Bash(ls:*)",
|
"Bash(ls:*)",
|
||||||
"Bash(git add:*)"
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@ -7,5 +7,5 @@ NEXT_PUBLIC_PARTICIPATION_HOST=http://localhost:8084
|
|||||||
NEXT_PUBLIC_DISTRIBUTION_HOST=http://localhost:8085
|
NEXT_PUBLIC_DISTRIBUTION_HOST=http://localhost:8085
|
||||||
NEXT_PUBLIC_ANALYTICS_HOST=http://localhost:8086
|
NEXT_PUBLIC_ANALYTICS_HOST=http://localhost:8086
|
||||||
|
|
||||||
# API Version
|
# API Version prefix
|
||||||
NEXT_PUBLIC_API_VERSION=v1
|
NEXT_PUBLIC_API_VERSION=api
|
||||||
|
|||||||
@ -1,92 +0,0 @@
|
|||||||
import axios, { AxiosError, AxiosInstance, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 베이스 URL
|
|
||||||
* 개발 환경: Next.js 프록시를 통해 CORS 우회 (/api/proxy -> localhost:8084)
|
|
||||||
* 프로덕션: 직접 백엔드 서버 호출
|
|
||||||
*/
|
|
||||||
const API_BASE_URL = process.env.NODE_ENV === 'production'
|
|
||||||
? process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8084'
|
|
||||||
: '/api/proxy';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Axios 인스턴스 생성
|
|
||||||
*/
|
|
||||||
const apiClient: AxiosInstance = axios.create({
|
|
||||||
baseURL: API_BASE_URL,
|
|
||||||
timeout: 30000, // 30초
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 요청 인터셉터
|
|
||||||
* - 요청 전에 공통 처리 로직 추가 (예: 인증 토큰)
|
|
||||||
*/
|
|
||||||
apiClient.interceptors.request.use(
|
|
||||||
(config: InternalAxiosRequestConfig) => {
|
|
||||||
// TODO: 필요 시 인증 토큰 추가
|
|
||||||
// const token = localStorage.getItem('accessToken');
|
|
||||||
// if (token && config.headers) {
|
|
||||||
// config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
console.log(`[API Request] ${config.method?.toUpperCase()} ${config.url}`);
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error: AxiosError) => {
|
|
||||||
console.error('[API Request Error]', error);
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 응답 인터셉터
|
|
||||||
* - 응답 후 공통 처리 로직 추가 (예: 에러 핸들링)
|
|
||||||
*/
|
|
||||||
apiClient.interceptors.response.use(
|
|
||||||
(response: AxiosResponse) => {
|
|
||||||
console.log(`[API Response] ${response.config.method?.toUpperCase()} ${response.config.url}`, response.status);
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
(error: AxiosError) => {
|
|
||||||
// 에러 응답 처리
|
|
||||||
if (error.response) {
|
|
||||||
const { status, data } = error.response;
|
|
||||||
console.error(`[API Error] ${status}:`, data);
|
|
||||||
|
|
||||||
// 상태 코드별 처리
|
|
||||||
switch (status) {
|
|
||||||
case 400:
|
|
||||||
console.error('잘못된 요청입니다.');
|
|
||||||
break;
|
|
||||||
case 401:
|
|
||||||
console.error('인증이 필요합니다.');
|
|
||||||
// TODO: 로그인 페이지로 리다이렉트
|
|
||||||
break;
|
|
||||||
case 403:
|
|
||||||
console.error('접근 권한이 없습니다.');
|
|
||||||
break;
|
|
||||||
case 404:
|
|
||||||
console.error('요청한 리소스를 찾을 수 없습니다.');
|
|
||||||
break;
|
|
||||||
case 500:
|
|
||||||
console.error('서버 오류가 발생했습니다.');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('알 수 없는 오류가 발생했습니다.');
|
|
||||||
}
|
|
||||||
} else if (error.request) {
|
|
||||||
// 요청은 전송되었으나 응답을 받지 못한 경우
|
|
||||||
console.error('[API Network Error]', error.message);
|
|
||||||
} else {
|
|
||||||
// 요청 설정 중 오류 발생
|
|
||||||
console.error('[API Setup Error]', error.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export default apiClient;
|
|
||||||
@ -1,67 +1,95 @@
|
|||||||
import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://20.196.65.160:8081';
|
// 마이크로서비스별 호스트 설정
|
||||||
|
const API_HOSTS = {
|
||||||
|
user: process.env.NEXT_PUBLIC_USER_HOST || 'http://localhost:8081',
|
||||||
|
event: process.env.NEXT_PUBLIC_EVENT_HOST || 'http://localhost:8080',
|
||||||
|
content: process.env.NEXT_PUBLIC_CONTENT_HOST || 'http://localhost:8082',
|
||||||
|
ai: process.env.NEXT_PUBLIC_AI_HOST || 'http://localhost:8083',
|
||||||
|
participation: process.env.NEXT_PUBLIC_PARTICIPATION_HOST || 'http://localhost:8084',
|
||||||
|
distribution: process.env.NEXT_PUBLIC_DISTRIBUTION_HOST || 'http://localhost:8085',
|
||||||
|
analytics: process.env.NEXT_PUBLIC_ANALYTICS_HOST || 'http://localhost:8086',
|
||||||
|
};
|
||||||
|
|
||||||
|
const API_VERSION = process.env.NEXT_PUBLIC_API_VERSION || 'api';
|
||||||
|
|
||||||
|
// 기본 User API 클라이언트 (기존 호환성 유지)
|
||||||
|
const API_BASE_URL = API_HOSTS.user;
|
||||||
|
|
||||||
export const apiClient: AxiosInstance = axios.create({
|
export const apiClient: AxiosInstance = axios.create({
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
timeout: 90000, // 30초로 증가
|
timeout: 90000,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Request interceptor - JWT 토큰 추가
|
// Participation API 전용 클라이언트
|
||||||
apiClient.interceptors.request.use(
|
export const participationClient: AxiosInstance = axios.create({
|
||||||
(config: InternalAxiosRequestConfig) => {
|
baseURL: `${API_HOSTS.participation}/${API_VERSION}`,
|
||||||
console.log('🚀 API Request:', {
|
timeout: 90000,
|
||||||
method: config.method?.toUpperCase(),
|
headers: {
|
||||||
url: config.url,
|
'Content-Type': 'application/json',
|
||||||
baseURL: config.baseURL,
|
|
||||||
data: config.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = localStorage.getItem('accessToken');
|
|
||||||
if (token && config.headers) {
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
console.log('🔑 Token added to request');
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
},
|
||||||
(error: AxiosError) => {
|
});
|
||||||
console.error('❌ Request Error:', error);
|
|
||||||
return Promise.reject(error);
|
// 공통 Request interceptor 함수
|
||||||
|
const requestInterceptor = (config: InternalAxiosRequestConfig) => {
|
||||||
|
console.log('🚀 API Request:', {
|
||||||
|
method: config.method?.toUpperCase(),
|
||||||
|
url: config.url,
|
||||||
|
baseURL: config.baseURL,
|
||||||
|
data: config.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
const token = localStorage.getItem('accessToken');
|
||||||
|
if (token && config.headers) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
console.log('🔑 Token added to request');
|
||||||
}
|
}
|
||||||
);
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
// Response interceptor - 에러 처리
|
const requestErrorInterceptor = (error: AxiosError) => {
|
||||||
apiClient.interceptors.response.use(
|
console.error('❌ Request Error:', error);
|
||||||
(response) => {
|
return Promise.reject(error);
|
||||||
console.log('✅ API Response:', {
|
};
|
||||||
status: response.status,
|
|
||||||
url: response.config.url,
|
|
||||||
data: response.data,
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
(error: AxiosError) => {
|
|
||||||
console.error('❌ API Error:', {
|
|
||||||
message: error.message,
|
|
||||||
status: error.response?.status,
|
|
||||||
statusText: error.response?.statusText,
|
|
||||||
url: error.config?.url,
|
|
||||||
data: error.response?.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (error.response?.status === 401) {
|
// 공통 Response interceptor 함수
|
||||||
console.warn('🔒 401 Unauthorized - Redirecting to login');
|
const responseInterceptor = (response: any) => {
|
||||||
// 인증 실패 시 토큰 삭제 및 로그인 페이지로 리다이렉트
|
console.log('✅ API Response:', {
|
||||||
localStorage.removeItem('accessToken');
|
status: response.status,
|
||||||
if (typeof window !== 'undefined') {
|
url: response.config.url,
|
||||||
window.location.href = '/login';
|
data: response.data,
|
||||||
}
|
});
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseErrorInterceptor = (error: AxiosError) => {
|
||||||
|
console.error('❌ API Error:', {
|
||||||
|
message: error.message,
|
||||||
|
status: error.response?.status,
|
||||||
|
statusText: error.response?.statusText,
|
||||||
|
url: error.config?.url,
|
||||||
|
data: error.response?.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
console.warn('🔒 401 Unauthorized - Redirecting to login');
|
||||||
|
localStorage.removeItem('accessToken');
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.location.href = '/login';
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
);
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
// User API Client 인터셉터 적용
|
||||||
|
apiClient.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
||||||
|
apiClient.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
||||||
|
|
||||||
|
// Participation API Client 인터셉터 적용
|
||||||
|
participationClient.interceptors.request.use(requestInterceptor, requestErrorInterceptor);
|
||||||
|
participationClient.interceptors.response.use(responseInterceptor, responseErrorInterceptor);
|
||||||
|
|
||||||
export default apiClient;
|
export default apiClient;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import apiClient from './api-client';
|
import { participationClient } from './client';
|
||||||
import type {
|
import type {
|
||||||
ApiResponse,
|
ApiResponse,
|
||||||
PageResponse,
|
PageResponse,
|
||||||
@ -20,7 +20,7 @@ export const participate = async (
|
|||||||
eventId: string,
|
eventId: string,
|
||||||
data: ParticipationRequest
|
data: ParticipationRequest
|
||||||
): Promise<ApiResponse<ParticipationResponse>> => {
|
): Promise<ApiResponse<ParticipationResponse>> => {
|
||||||
const response = await apiClient.post<ApiResponse<ParticipationResponse>>(
|
const response = await participationClient.post<ApiResponse<ParticipationResponse>>(
|
||||||
`/v1/events/${eventId}/participate`,
|
`/v1/events/${eventId}/participate`,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
@ -36,7 +36,7 @@ export const getParticipants = async (
|
|||||||
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
|
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
|
||||||
const { eventId, storeVisited, page = 0, size = 20, sort = ['createdAt,DESC'] } = params;
|
const { eventId, storeVisited, page = 0, size = 20, sort = ['createdAt,DESC'] } = params;
|
||||||
|
|
||||||
const response = await apiClient.get<ApiResponse<PageResponse<ParticipationResponse>>>(
|
const response = await participationClient.get<ApiResponse<PageResponse<ParticipationResponse>>>(
|
||||||
`/v1/events/${eventId}/participants`,
|
`/v1/events/${eventId}/participants`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
@ -58,7 +58,7 @@ export const getParticipant = async (
|
|||||||
eventId: string,
|
eventId: string,
|
||||||
participantId: string
|
participantId: string
|
||||||
): Promise<ApiResponse<ParticipationResponse>> => {
|
): Promise<ApiResponse<ParticipationResponse>> => {
|
||||||
const response = await apiClient.get<ApiResponse<ParticipationResponse>>(
|
const response = await participationClient.get<ApiResponse<ParticipationResponse>>(
|
||||||
`/v1/events/${eventId}/participants/${participantId}`
|
`/v1/events/${eventId}/participants/${participantId}`
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
@ -119,7 +119,7 @@ export const drawWinners = async (
|
|||||||
winnerCount: number,
|
winnerCount: number,
|
||||||
applyStoreVisitBonus?: boolean
|
applyStoreVisitBonus?: boolean
|
||||||
): Promise<ApiResponse<import('../types/api.types').DrawWinnersResponse>> => {
|
): Promise<ApiResponse<import('../types/api.types').DrawWinnersResponse>> => {
|
||||||
const response = await apiClient.post<ApiResponse<import('../types/api.types').DrawWinnersResponse>>(
|
const response = await participationClient.post<ApiResponse<import('../types/api.types').DrawWinnersResponse>>(
|
||||||
`/v1/events/${eventId}/draw-winners`,
|
`/v1/events/${eventId}/draw-winners`,
|
||||||
{
|
{
|
||||||
winnerCount,
|
winnerCount,
|
||||||
@ -139,7 +139,7 @@ export const getWinners = async (
|
|||||||
size = 20,
|
size = 20,
|
||||||
sort: string[] = ['winnerRank,ASC']
|
sort: string[] = ['winnerRank,ASC']
|
||||||
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
|
): Promise<ApiResponse<PageResponse<ParticipationResponse>>> => {
|
||||||
const response = await apiClient.get<ApiResponse<PageResponse<ParticipationResponse>>>(
|
const response = await participationClient.get<ApiResponse<PageResponse<ParticipationResponse>>>(
|
||||||
`/v1/events/${eventId}/winners`,
|
`/v1/events/${eventId}/winners`,
|
||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user