210 lines
5.6 KiB
JavaScript
210 lines
5.6 KiB
JavaScript
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;
|