import axios from 'axios'; import { logApiError } from './utils/errorLogger'; // 환경변수에서 API URL을 읽음 (Vite 기준) // 프로덕션에서는 nginx 프록시를 통해 /api 경로 사용 const API_BASE_URL = '/api'; console.log('API Base URL:', API_BASE_URL); console.log('Environment:', import.meta.env.MODE); // axios 인스턴스 생성 export const api = axios.create({ baseURL: API_BASE_URL, timeout: 30000, // 30초로 증가 headers: { 'Content-Type': 'application/json', }, }); // 재시도 로직을 위한 설정 const MAX_RETRIES = 3; const RETRY_DELAY = 1000; // 1초 // 요청 인터셉터: 토큰 자동 추가 api.interceptors.request.use( config => { const token = localStorage.getItem('access_token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); } ); // 재시도 함수 const retryRequest = async (config, retries = MAX_RETRIES) => { try { return await api(config); } catch (error) { if (retries > 0 && (error.code === 'ECONNABORTED' || error.response?.status >= 500)) { console.log(`API 재시도 중... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`); await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); return retryRequest(config, retries - 1); } throw error; } }; // 응답 인터셉터: 에러 처리 및 자동 로그아웃 api.interceptors.response.use( response => response, error => { // 오류 로깅 const endpoint = error.config?.url; const requestData = error.config?.data; logApiError(error, endpoint, requestData); console.error('API Error:', { url: error.config?.url, method: error.config?.method, status: error.response?.status, data: error.response?.data, message: error.message }); // 401/403 에러 시 자동 로그아웃 if (error.response?.status === 401 || error.response?.status === 403) { const token = localStorage.getItem('access_token'); if (token) { console.log('토큰이 유효하지 않습니다. 자동 로그아웃 처리합니다.'); localStorage.removeItem('access_token'); localStorage.removeItem('user_data'); // 페이지 새로고침으로 로그인 페이지로 이동 window.location.reload(); } } return Promise.reject(error); } ); // 예시: 파일 업로드 (multipart/form-data) export function uploadFile(formData, options = {}) { const config = { method: 'post', url: '/files/upload', data: formData, headers: { 'Content-Type': 'multipart/form-data' }, ...options, }; return retryRequest(config); } // 예시: 자재 목록 조회 (신버전 API 사용) export function fetchMaterials(params) { return api.get('/files/materials-v2', { params }); } // 예시: 자재 요약 통계 export function fetchMaterialsSummary(params) { return api.get('/files/materials/summary', { params }); } // 파일 목록 조회 export function fetchFiles(params) { return api.get('/files', { params }); } // 파일 삭제 export function deleteFile(fileId) { return api.delete(`/files/delete/${fileId}`); } // 예시: Job 목록 조회 export function fetchJobs(params) { return api.get('/jobs/', { params }); } // 예시: Job 생성 export function createJob(data) { return api.post('/jobs/', data); } // 리비전 비교 export function compareRevisions(jobNo, filename, oldRevision, newRevision) { return api.get('/files/materials/compare-revisions', { params: { job_no: jobNo, filename: filename, old_revision: oldRevision, new_revision: newRevision } }); } // 프로젝트 수정 export function updateProject(projectId, data) { return api.put(`/projects/${projectId}`, data); } // 프로젝트 삭제 export function deleteProject(projectId) { return api.delete(`/projects/${projectId}`); } // 스풀 관련 API export function fetchProjectSpools(projectId) { return api.get(`/spools/project/${projectId}/spools`); } export function validateSpoolIdentifier(identifier) { return api.post('/spools/validate-identifier', { spool_identifier: identifier }); } export function generateSpoolIdentifier(dwgName, areaNumber, spoolNumber) { return api.post('/spools/generate-identifier', { dwg_name: dwgName, area_number: areaNumber, spool_number: spoolNumber }); } // 자재 비교 관련 API export function compareMaterialRevisions(jobNo, currentRevision, previousRevision = null, saveResult = true) { return api.post('/materials/compare-revisions', null, { params: { job_no: jobNo, current_revision: currentRevision, previous_revision: previousRevision, save_result: saveResult } }); } export function getMaterialComparisonHistory(jobNo, limit = 10) { return api.get('/materials/comparison-history', { params: { job_no: jobNo, limit } }); } export function getMaterialInventoryStatus(jobNo, materialHash = null) { return api.get('/materials/inventory-status', { params: { job_no: jobNo, material_hash: materialHash } }); } export function confirmMaterialPurchase(jobNo, revision, confirmations, confirmedBy = 'user') { return api.post('/materials/confirm-purchase', null, { params: { job_no: jobNo, revision: revision, confirmed_by: confirmedBy }, data: confirmations }); } export function getMaterialPurchaseStatus(jobNo, revision = null, status = null) { return api.get('/materials/purchase-status', { params: { job_no: jobNo, revision, status } }); } // Default export for convenience export default api;