summaryrefslogtreecommitdiff
path: root/app/api
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-10-30 10:35:26 +0900
committerjoonhoekim <26rote@gmail.com>2025-10-30 10:35:26 +0900
commit284f9f40d9494168f3e68eedd9af067c38362eea (patch)
treeac26b3e9fab46b996c16bfa7a7add8f26ddb5042 /app/api
parent88f13ab89d2250e52c3375b077328a933a5762ec (diff)
(김준회) refactor: POS: 온디맨드로 다운로드받도록 변경, 매핑로직에선 pos 관련 로직 제거
Diffstat (limited to 'app/api')
-rw-r--r--app/api/pos/download-on-demand/route.ts196
1 files changed, 196 insertions, 0 deletions
diff --git a/app/api/pos/download-on-demand/route.ts b/app/api/pos/download-on-demand/route.ts
new file mode 100644
index 00000000..f88e8858
--- /dev/null
+++ b/app/api/pos/download-on-demand/route.ts
@@ -0,0 +1,196 @@
+/**
+ * POS 파일 온디맨드 다운로드 API
+ *
+ * 자재코드(MATNR)로 POS 파일을 찾아서 NFS에서 직접 스트리밍합니다.
+ * 서버 스토리지에 저장하지 않고 실시간으로 다운로드합니다.
+ */
+
+import { NextRequest, NextResponse } from 'next/server';
+import { getDcmtmIdByMaterialCode, getEncryptDocumentumFile, downloadPosFile } from '@/lib/pos';
+
+// 허용된 파일 확장자
+const ALLOWED_EXTENSIONS = new Set([
+ 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
+ 'txt', 'csv', 'png', 'jpg', 'jpeg', 'gif', 'bmp', 'svg',
+ 'dwg', 'dxf', 'zip', 'rar', '7z'
+]);
+
+// 최대 파일 크기 (10240MB)
+const MAX_FILE_SIZE = 10240 * 1024 * 1024;
+
+// 파일 확장자 검증
+function validateFileExtension(fileName: string): boolean {
+ const extension = fileName.split('.').pop()?.toLowerCase() || '';
+ return ALLOWED_EXTENSIONS.has(extension);
+}
+
+// MIME 타입 결정
+function getMimeType(fileName: string): string {
+ const fileExtension = fileName.split('.').pop()?.toLowerCase() || '';
+ const mimeTypes: Record<string, string> = {
+ 'pdf': 'application/pdf',
+ 'doc': 'application/msword',
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'xls': 'application/vnd.ms-excel',
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'ppt': 'application/vnd.ms-powerpoint',
+ 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'txt': 'text/plain; charset=utf-8',
+ 'csv': 'text/csv; charset=utf-8',
+ 'png': 'image/png',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'gif': 'image/gif',
+ 'bmp': 'image/bmp',
+ 'svg': 'image/svg+xml',
+ 'dwg': 'application/acad',
+ 'dxf': 'application/dxf',
+ 'zip': 'application/zip',
+ 'rar': 'application/x-rar-compressed',
+ '7z': 'application/x-7z-compressed',
+ };
+ return mimeTypes[fileExtension] || 'application/octet-stream';
+}
+
+export async function GET(request: NextRequest) {
+ try {
+ const searchParams = request.nextUrl.searchParams;
+ const materialCode = searchParams.get('materialCode');
+ const fileIndex = parseInt(searchParams.get('fileIndex') || '0');
+
+ // 파라미터 검증
+ if (!materialCode || materialCode.trim() === '') {
+ return NextResponse.json(
+ { error: '자재코드(materialCode)가 제공되지 않았습니다.' },
+ { status: 400 }
+ );
+ }
+
+ console.log(`🔍 POS 파일 온디맨드 다운로드 시작 (자재코드: ${materialCode})`);
+
+ // 1. 자재코드로 DCMTM_ID 조회
+ console.log(`📋 DCMTM_ID 조회 중... (자재코드: ${materialCode})`);
+ const dcmtmResult = await getDcmtmIdByMaterialCode({ materialCode });
+
+ if (!dcmtmResult.success || !dcmtmResult.files || dcmtmResult.files.length === 0) {
+ console.warn(`⚠️ POS 파일을 찾을 수 없음 (자재코드: ${materialCode})`);
+ return NextResponse.json(
+ {
+ error: dcmtmResult.error || '해당 자재코드에 대한 POS 파일을 찾을 수 없습니다.',
+ materialCode
+ },
+ { status: 404 }
+ );
+ }
+
+ // 파일 인덱스 범위 검증
+ if (fileIndex >= dcmtmResult.files.length) {
+ return NextResponse.json(
+ {
+ error: `파일 인덱스가 범위를 벗어났습니다. 사용 가능한 파일 수: ${dcmtmResult.files.length}`,
+ availableFilesCount: dcmtmResult.files.length
+ },
+ { status: 400 }
+ );
+ }
+
+ const posFile = dcmtmResult.files[fileIndex];
+ console.log(`✅ DCMTM_ID 조회 완료:`, {
+ materialCode,
+ dcmtmId: posFile.dcmtmId,
+ fileName: posFile.fileName,
+ totalFiles: dcmtmResult.files.length
+ });
+
+ // 2. POS API로 파일 경로 가져오기
+ console.log(`🌐 POS API 호출 중... (DCMTM_ID: ${posFile.dcmtmId})`);
+ const posResult = await getEncryptDocumentumFile({
+ objectID: posFile.dcmtmId
+ });
+
+ if (!posResult.success || !posResult.result) {
+ console.error(`❌ POS API 호출 실패:`, posResult.error);
+ return NextResponse.json(
+ {
+ error: posResult.error || 'POS 파일 경로를 가져올 수 없습니다.',
+ materialCode,
+ dcmtmId: posFile.dcmtmId
+ },
+ { status: 500 }
+ );
+ }
+
+ console.log(`✅ POS API 호출 완료 (경로: ${posResult.result})`);
+
+ // 3. NFS에서 파일 다운로드
+ console.log(`⬇️ 파일 다운로드 중... (NFS 경로: ${posResult.result})`);
+ const downloadResult = await downloadPosFile({
+ relativePath: posResult.result
+ });
+
+ if (!downloadResult.success || !downloadResult.fileBuffer) {
+ console.error(`❌ 파일 다운로드 실패:`, downloadResult.error);
+ return NextResponse.json(
+ {
+ error: downloadResult.error || '파일 다운로드에 실패했습니다.',
+ materialCode,
+ nfsPath: posResult.result
+ },
+ { status: 500 }
+ );
+ }
+
+ const fileName = downloadResult.fileName || posFile.fileName;
+ const fileBuffer = downloadResult.fileBuffer;
+
+ console.log(`✅ 파일 다운로드 완료 (크기: ${fileBuffer.length} bytes)`);
+
+ // 파일 확장자 검증
+ if (!validateFileExtension(fileName)) {
+ console.warn(`🚨 허용되지 않은 파일 확장자: ${fileName}`);
+ return NextResponse.json(
+ { error: '지원하지 않는 파일 형식입니다.', fileName },
+ { status: 403 }
+ );
+ }
+
+ // 파일 크기 검증
+ if (fileBuffer.length > MAX_FILE_SIZE) {
+ console.warn(`🚨 파일 크기 초과: ${fileBuffer.length} bytes`);
+ return NextResponse.json(
+ { error: '파일 크기가 너무 큽니다.', fileSize: fileBuffer.length },
+ { status: 413 }
+ );
+ }
+
+ // MIME 타입 결정
+ const contentType = downloadResult.mimeType || getMimeType(fileName);
+
+ // 파일 스트리밍 응답 생성
+ const response = new NextResponse(fileBuffer);
+
+ response.headers.set('Content-Type', contentType);
+ response.headers.set('Content-Disposition', `attachment; filename*=UTF-8''${encodeURIComponent(fileName)}`);
+ response.headers.set('Content-Length', fileBuffer.length.toString());
+
+ // 보안 헤더
+ response.headers.set('Cache-Control', 'no-cache, no-store, must-revalidate');
+ response.headers.set('Pragma', 'no-cache');
+ response.headers.set('Expires', '0');
+ response.headers.set('X-Content-Type-Options', 'nosniff');
+
+ console.log(`✅ POS 파일 다운로드 성공: ${fileName} (${fileBuffer.length} bytes)`);
+
+ return response;
+ } catch (error) {
+ console.error('❌ POS 파일 온디맨드 다운로드 API 오류:', error);
+ return NextResponse.json(
+ {
+ error: '서버 내부 오류가 발생했습니다.',
+ details: error instanceof Error ? error.message : '알 수 없는 오류'
+ },
+ { status: 500 }
+ );
+ }
+}
+