summaryrefslogtreecommitdiff
path: root/app/api/pos/download-on-demand-partners/route.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-04 17:30:27 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-04 17:30:27 +0900
commita1710296dbc1881a7ed86093872904a529901430 (patch)
tree62e4be5dabba4bf7c337769f391d11ce59154cc4 /app/api/pos/download-on-demand-partners/route.ts
parent680da9b323db8b8d7cf27c674ab0016ec87bfe81 (diff)
(김준회) RFQ 테이블 POS 다운로드할 수 있도록 변경, 벤더측은 다운로드시 암호화 해제 추가, item dialog 통일처리
Diffstat (limited to 'app/api/pos/download-on-demand-partners/route.ts')
-rw-r--r--app/api/pos/download-on-demand-partners/route.ts243
1 files changed, 243 insertions, 0 deletions
diff --git a/app/api/pos/download-on-demand-partners/route.ts b/app/api/pos/download-on-demand-partners/route.ts
new file mode 100644
index 00000000..d2941537
--- /dev/null
+++ b/app/api/pos/download-on-demand-partners/route.ts
@@ -0,0 +1,243 @@
+'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<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';
+}
+
+/**
+ * DRM 복호화 함수
+ * Spring Boot DRM Proxy 서버를 통해 파일 복호화
+ */
+async function decryptBuffer(buffer: Buffer, fileName: string): Promise<Buffer> {
+ 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 }
+ );
+ }
+}
+