'use server'; /** * POS 파일 온디맨드 다운로드 API (Partners용 - 복호화 포함) * * 자재코드(MATNR)로 POS 파일을 찾아서 NFS에서 다운로드 후 복호화하여 제공합니다. * EVCP 직원용과 달리 협력사는 복호화된 파일을 다운로드합니다. */ 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 = { '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'; } /** * DRM 복호화 함수 * Spring Boot DRM Proxy 서버를 통해 파일 복호화 */ async function decryptBuffer(buffer: Buffer, fileName: string): Promise { try { console.log(`🔐 DRM 복호화 시작: ${fileName} (크기: ${buffer.length} bytes)`); // 파일을 Blob/File로 변환 const blob = new Blob([buffer]); const file = new File([blob], fileName); const formData = new FormData(); formData.append('file', file); // DRM Proxy 서버 엔드포인트 const backendUrl = process.env.DRM_PROXY_URL || "http://localhost:6543/api/drm-proxy/decrypt"; const response = await fetch(backendUrl, { method: "POST", body: formData, }); if (!response.ok) { const errorText = await response.text().catch(() => '응답 텍스트를 가져올 수 없음'); throw new Error(`DRM 서버 응답 오류 [${response.status}]: ${errorText}`); } const arrayBuffer = await response.arrayBuffer(); const decryptedBuffer = Buffer.from(arrayBuffer); console.log(`✅ DRM 복호화 완료: ${fileName} (결과 크기: ${decryptedBuffer.length} bytes)`); return decryptedBuffer; } catch (error) { console.error(`❌ DRM 복호화 실패: ${fileName}`, error); // 복호화 실패 시 원본 반환 (폴백) console.warn(`⚠️ 복호화 실패로 원본 파일 반환: ${fileName}`); return buffer; } } 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 파일 온디맨드 다운로드 시작 [Partners - 복호화] (자재코드: ${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; let 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 } ); } // 4. DRM 복호화 (Partners용 추가 로직) fileBuffer = await decryptBuffer(fileBuffer, fileName); // 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 파일 다운로드 성공 [Partners - 복호화됨]: ${fileName} (${fileBuffer.length} bytes)`); return response; } catch (error) { console.error('❌ POS 파일 온디맨드 다운로드 API 오류 [Partners]:', error); return NextResponse.json( { error: '서버 내부 오류가 발생했습니다.', details: error instanceof Error ? error.message : '알 수 없는 오류' }, { status: 500 } ); } }