diff options
Diffstat (limited to 'app/api/vendors')
| -rw-r--r-- | app/api/vendors/attachments/download-temp/route.ts | 102 | ||||
| -rw-r--r-- | app/api/vendors/erp/route.ts | 144 |
2 files changed, 246 insertions, 0 deletions
diff --git a/app/api/vendors/attachments/download-temp/route.ts b/app/api/vendors/attachments/download-temp/route.ts new file mode 100644 index 00000000..987e421d --- /dev/null +++ b/app/api/vendors/attachments/download-temp/route.ts @@ -0,0 +1,102 @@ +// app/api/vendors/attachments/download-temp/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; +import { promises as fsPromises } from 'fs'; +import { cleanupTempFiles } from '@/lib/vendors/service'; + +export async function GET(request: NextRequest) { + try { + // 파일명 파라미터 추출 + const searchParams = request.nextUrl.searchParams; + const fileName = searchParams.get('file'); + + if (!fileName) { + return NextResponse.json( + { success: false, error: 'File name is required' }, + { status: 400 } + ); + } + + // 보안: 파일명에 경로 문자가 포함되어 있는지 확인 (경로 탐색 공격 방지) + if (fileName.includes('/') || fileName.includes('\\')) { + return NextResponse.json( + { success: false, error: 'Invalid file name' }, + { status: 400 } + ); + } + + // 임시 디렉토리의 파일 경로 생성 + const tempDir = path.join(process.cwd(), 'tmp'); + const filePath = path.join(tempDir, fileName); + + // 파일 존재 확인 + try { + await fsPromises.access(filePath, fs.constants.F_OK); + } catch { + return NextResponse.json( + { success: false, error: 'File not found' }, + { status: 404 } + ); + } + + // 파일 읽기 + const fileBuffer = await fsPromises.readFile(filePath); + + // 파일명에서 UUID 부분 제거하여 표시용 이름 생성 + const uuidPattern = /-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.zip$/i; + const displayName = fileName.replace(uuidPattern, '.zip'); + + // 파일 응답 반환 + const response = new NextResponse(fileBuffer, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': `attachment; filename="${encodeURIComponent(displayName)}"`, + }, + }); + + // 비동기적으로 파일 정리 요청 (별도 API 호출) + // Note: Next.js 환경에 따라 작동하지 않을 수 있음 + try { + fetch(`${request.nextUrl.origin}/api/vendors/cleanup-temp-files?file=${encodeURIComponent(fileName)}`, { + method: 'POST', + }).catch(e => console.error('임시 파일 정리 요청 실패:', e)); + } catch (e) { + console.error('파일 정리 요청 오류:', e); + } + + return response; + } catch (error) { + console.error('임시 파일 다운로드 오류:', error); + return NextResponse.json( + { success: false, error: 'Failed to download file' }, + { status: 500 } + ); + } +} + +// 임시 파일 정리 API 엔드포인트 +// app/api/vendors/cleanup-temp-files/route.ts +export async function POST(request: NextRequest) { + try { + const searchParams = request.nextUrl.searchParams; + const fileName = searchParams.get('file'); + + if (!fileName) { + return NextResponse.json({ success: false, error: 'File name is required' }, { status: 400 }); + } + + // 보안 검증 + if (fileName.includes('/') || fileName.includes('\\')) { + return NextResponse.json({ success: false, error: 'Invalid file name' }, { status: 400 }); + } + + // 서버 액션 호출하여 파일 정리 + await cleanupTempFiles(fileName); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('임시 파일 정리 API 오류:', error); + return NextResponse.json({ success: false, error: '임시 파일 정리 중 오류가 발생했습니다.' }, { status: 500 }); + } +}
\ No newline at end of file diff --git a/app/api/vendors/erp/route.ts b/app/api/vendors/erp/route.ts new file mode 100644 index 00000000..0724eeeb --- /dev/null +++ b/app/api/vendors/erp/route.ts @@ -0,0 +1,144 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { headers } from 'next/headers'; +import { getErrorMessage } from '@/lib/handle-error'; + +/** + * 기간계 시스템에 벤더 정보를 전송하는 API 엔드포인트 + * 서버 액션 내부에서 호출됨 + */ +export async function POST(request: NextRequest) { + try { + + // 요청 본문 파싱 + const vendorData = await request.json(); + + // 기간계 시스템 API 설정 + const erpApiUrl = process.env.ERP_API_URL; + const erpApiKey = process.env.ERP_API_KEY; + + if (!erpApiUrl || !erpApiKey) { + return NextResponse.json( + { success: false, message: 'ERP API configuration is missing' }, + { status: 500 } + ); + } + + // 기간계 시스템이 요구하는 형식으로 데이터 변환 + const erpRequestData = { + vendor: { + name: vendorData.vendorName, + tax_id: vendorData.taxId, + address: vendorData.address || "", + country: vendorData.country || "", + phone: vendorData.phone || "", + email: vendorData.email || "", + website: vendorData.website || "", + external_id: vendorData.id.toString(), + }, + contacts: vendorData.contacts.map((contact: any) => ({ + name: contact.contactName, + position: contact.contactPosition || "", + email: contact.contactEmail, + phone: contact.contactPhone || "", + is_primary: contact.isPrimary ? 1 : 0, + })), + items: vendorData.possibleItems.map((item: any) => ({ + item_code: item.itemCode, + description: item.description || "", + })), + attachments: vendorData.attachments.map((attach: any) => ({ + file_name: attach.fileName, + file_path: attach.filePath, + })), + }; + + // 기간계 시스템 API 호출 + const response = await fetch(erpApiUrl, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${erpApiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(erpRequestData), + // Next.js의 fetch는 기본 30초 타임아웃 + }); + + // 응답 처리 + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + return NextResponse.json( + { + success: false, + message: `ERP system error: ${response.status} ${response.statusText}`, + details: errorData + }, + { status: 502 } // Bad Gateway (외부 서버 오류) + ); + } + + const result = await response.json(); + + // 벤더 코드 검증 + if (!result.vendor_code) { + return NextResponse.json( + { success: false, message: 'Vendor code not provided in ERP response' }, + { status: 502 } + ); + } + + // 성공 응답 + return NextResponse.json({ + success: true, + vendorCode: result.vendor_code, + message: 'Vendor successfully registered in ERP system', + ...result + }); + } catch (error) { + console.error('Error in ERP API:', error); + return NextResponse.json( + { + success: false, + message: getErrorMessage(error) + }, + { status: 500 } + ); + } +} + +/** + * 기간계 시스템 연결 상태 확인 (헬스 체크) + */ +export async function GET() { + try { + const healthCheckUrl = process.env.ERP_HEALTH_CHECK_URL; + + if (!healthCheckUrl) { + return NextResponse.json( + { success: false, message: 'ERP health check URL not configured' }, + { status: 500 } + ); + } + + const response = await fetch(healthCheckUrl, { + method: 'GET', + next: { revalidate: 60 } // 1분마다 재검증 + }); + + const isAvailable = response.ok; + + return NextResponse.json({ + success: true, + available: isAvailable, + status: response.status, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('ERP health check error:', error); + return NextResponse.json({ + success: false, + available: false, + error: getErrorMessage(error), + timestamp: new Date().toISOString() + }); + } +}
\ No newline at end of file |
