summaryrefslogtreecommitdiff
path: root/app/api/vendors
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
committerjoonhoekim <26rote@gmail.com>2025-03-25 15:55:45 +0900
commit1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch)
tree8a5587f10ca55b162d7e3254cb088b323a34c41b /app/api/vendors
initial commit
Diffstat (limited to 'app/api/vendors')
-rw-r--r--app/api/vendors/attachments/download-temp/route.ts102
-rw-r--r--app/api/vendors/erp/route.ts144
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