diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-28 02:13:30 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-28 02:13:30 +0000 |
| commit | ef4c533ebacc2cdc97e518f30e9a9350004fcdfb (patch) | |
| tree | 345251a3ed0f4429716fa5edaa31024d8f4cb560 /app/api/vendors | |
| parent | 9ceed79cf32c896f8a998399bf1b296506b2cd4a (diff) | |
~20250428 작업사항
Diffstat (limited to 'app/api/vendors')
| -rw-r--r-- | app/api/vendors/attachments/download-all/route.ts | 108 | ||||
| -rw-r--r-- | app/api/vendors/attachments/download/route.ts | 93 | ||||
| -rw-r--r-- | app/api/vendors/erp/route.ts | 4 |
3 files changed, 203 insertions, 2 deletions
diff --git a/app/api/vendors/attachments/download-all/route.ts b/app/api/vendors/attachments/download-all/route.ts new file mode 100644 index 00000000..23f85786 --- /dev/null +++ b/app/api/vendors/attachments/download-all/route.ts @@ -0,0 +1,108 @@ +// /app/api/vendors/attachments/download-all/route.js +import { NextResponse,NextRequest } from 'next/server'; +import fs from 'fs'; +import path from 'path'; +import JSZip from 'jszip'; +import db from '@/db/db'; + +import { eq } from 'drizzle-orm'; +import { vendorAttachments, vendors } from '@/db/schema'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const vendorId = searchParams.get('vendorId'); + + if (!vendorId) { + return NextResponse.json( + { error: "필수 파라미터가 누락되었습니다." }, + { status: 400 } + ); + } + + // 협력업체 정보 조회 + const vendor = await db.query.vendors.findFirst({ + where: eq(vendors.id, parseInt(vendorId, 10)) + }); + + if (!vendor) { + return NextResponse.json( + { error: `협력업체 정보를 찾을 수 없습니다. (ID: ${vendorId})` }, + { status: 404 } + ); + } + + // 첨부파일 조회 + const attachments = await db.select() + .from(vendorAttachments) + .where(eq(vendorAttachments.vendorId, parseInt(vendorId, 10))); + + if (!attachments.length) { + return NextResponse.json( + { error: '다운로드할 첨부파일이 없습니다.' }, + { status: 404 } + ); + } + + // 업로드 기본 경로 + const basePath = process.env.UPLOAD_DIR || path.join(process.cwd(), 'public'); + + // ZIP 생성 + const zip = new JSZip(); + + // 파일 읽기 및 ZIP에 추가 + await Promise.all( + attachments.map(async (attachment) => { + const filePath = path.join(basePath, attachment.filePath); + + try { + // 파일 존재 확인 + try { + await fs.promises.access(filePath, fs.constants.F_OK); + } catch (e) { + console.warn(`파일이 존재하지 않습니다: ${filePath}`); + return; // 파일이 없으면 건너뜀 + } + + // 파일 읽기 + const fileData = await fs.promises.readFile(filePath); + + // ZIP에 파일 추가 + zip.file(attachment.fileName, fileData); + } catch (error) { + console.warn(`파일을 처리할 수 없습니다: ${filePath}`, error); + // 오류가 있더라도 계속 진행 + } + }) + ); + + // ZIP 생성 + const zipContent = await zip.generateAsync({ + type: 'nodebuffer', + compression: 'DEFLATE', + compressionOptions: { level: 9 } + }); + + // 파일명 생성 + const fileName = `${vendor.vendorName || `vendor-${vendorId}`}-attachments.zip`; + + // 응답 헤더 설정 + const headers = new Headers(); + headers.set('Content-Disposition', `attachment; filename="${fileName}"`); + headers.set('Content-Type', 'application/zip'); + headers.set('Content-Length', zipContent.length.toString()); + + // ZIP 파일 데이터와 함께 응답 + return new Response(zipContent, { + status: 200, + headers + }); + + } catch (error) { + console.error('첨부파일 다운로드 오류:', error); + return NextResponse.json( + { error: "첨부파일 다운로드 준비 중 오류가 발생했습니다." }, + { status: 500 } + ); + } +}
\ No newline at end of file diff --git a/app/api/vendors/attachments/download/route.ts b/app/api/vendors/attachments/download/route.ts new file mode 100644 index 00000000..0151a699 --- /dev/null +++ b/app/api/vendors/attachments/download/route.ts @@ -0,0 +1,93 @@ +// /app/api/vendors/attachments/download/route.js (Next.js App Router 기준) +import { NextRequest, NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; +import { eq } from 'drizzle-orm'; // 쿼리 빌더 +import { vendorAttachments } from '@/db/schema'; +import db from '@/db/db'; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + const fileId = searchParams.get('id'); + const vendorId = searchParams.get('vendorId'); + + if (!fileId || !vendorId) { + return NextResponse.json( + { error: "필수 파라미터가 누락되었습니다." }, + { status: 400 } + ); + } + + // 첨부파일 정보 조회 + const attachment = await db.query.vendorAttachments.findFirst({ + where: eq(vendorAttachments.id, parseInt(fileId, 10)) + }); + + if (!attachment) { + return NextResponse.json( + { error: "파일을 찾을 수 없습니다." }, + { status: 404 } + ); + } + + // 파일 경로 구성 + const basePath = process.env.UPLOAD_DIR || path.join(process.cwd(), 'public'); + const filePath = path.join(basePath, attachment.filePath); + + // 파일 존재 확인 + try { + await fs.promises.access(filePath, fs.constants.F_OK); + } catch (e) { + return NextResponse.json( + { error: "파일이 서버에 존재하지 않습니다." }, + { status: 404 } + ); + } + + // 파일 데이터 읽기 + const fileBuffer = await fs.promises.readFile(filePath); + + + + // 파일 MIME 타입 추정 + let contentType = 'application/octet-stream'; + if (attachment.fileName) { + const ext = path.extname(attachment.fileName).toLowerCase(); + switch (ext) { + case '.pdf': contentType = 'application/pdf'; break; + case '.jpg': + case '.jpeg': contentType = 'image/jpeg'; break; + case '.png': contentType = 'image/png'; break; + case '.doc': contentType = 'application/msword'; break; + case '.docx': contentType = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; break; + // 필요에 따라 더 많은 타입 추가 + } + } + + // 응답 헤더 설정 + const headers = new Headers(); + + // 파일명에 non-ASCII 문자가 포함될 수 있으므로 인코딩 처리 + const encodedFileName = encodeURIComponent(attachment.fileName) + .replace(/['()]/g, escape) // 추가 이스케이프 필요한 문자들 + .replace(/\*/g, '%2A'); + + // RFC 5987에 따른 인코딩 방식 적용 + headers.set('Content-Disposition', `attachment; filename*=UTF-8''${encodedFileName}`); + headers.set('Content-Type', contentType); + headers.set('Content-Length', fileBuffer.length.toString()); + // 파일 데이터와 함께 응답 + return new Response(fileBuffer, { + status: 200, + headers + }); + + } catch (error) { + console.error('파일 다운로드 오류:', error); + return NextResponse.json( + { 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 index 0724eeeb..70573592 100644 --- a/app/api/vendors/erp/route.ts +++ b/app/api/vendors/erp/route.ts @@ -3,7 +3,7 @@ import { headers } from 'next/headers'; import { getErrorMessage } from '@/lib/handle-error'; /** - * 기간계 시스템에 벤더 정보를 전송하는 API 엔드포인트 + * 기간계 시스템에 협력업체 정보를 전송하는 API 엔드포인트 * 서버 액션 내부에서 호출됨 */ export async function POST(request: NextRequest) { @@ -78,7 +78,7 @@ export async function POST(request: NextRequest) { const result = await response.json(); - // 벤더 코드 검증 + // 협력업체 코드 검증 if (!result.vendor_code) { return NextResponse.json( { success: false, message: 'Vendor code not provided in ERP response' }, |
