diff options
Diffstat (limited to 'lib/pos/download-pos-file.ts')
| -rw-r--r-- | lib/pos/download-pos-file.ts | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/lib/pos/download-pos-file.ts b/lib/pos/download-pos-file.ts new file mode 100644 index 00000000..d285c618 --- /dev/null +++ b/lib/pos/download-pos-file.ts @@ -0,0 +1,162 @@ +'use server'; + +import fs from 'fs/promises'; +import path from 'path'; +import type { DownloadPosFileParams, DownloadPosFileResult } from './types'; +import { debugLog, debugError, debugSuccess, debugProcess } from '@/lib/debug-utils'; + +const POS_BASE_PATH = '\\\\60.100.99.123\\ECM_NAS_PRM\\Download'; + +/** + * POS API에서 반환된 경로의 파일을 내부망에서 읽어서 클라이언트에게 전달 + * 내부망 파일을 직접 접근할 수 없는 클라이언트를 위한 프록시 역할 + */ +export async function downloadPosFile( + params: DownloadPosFileParams +): Promise<DownloadPosFileResult> { + try { + const { relativePath } = params; + debugLog(`⬇️ POS 파일 다운로드 시작`, { relativePath, basePath: POS_BASE_PATH }); + + if (!relativePath) { + debugError(`❌ 파일 경로가 제공되지 않음`); + return { + success: false, + error: '파일 경로가 제공되지 않았습니다.', + }; + } + + // Windows 경로 구분자를 정규화 + const normalizedRelativePath = relativePath.replace(/\\/g, path.sep); + debugLog(`📁 경로 정규화`, { original: relativePath, normalized: normalizedRelativePath }); + + // 전체 파일 경로 구성 + const fullPath = path.join(POS_BASE_PATH, normalizedRelativePath); + debugLog(`📍 전체 파일 경로 구성`, { fullPath }); + + // 경로 보안 검증 (디렉토리 탈출 방지) + const resolvedPath = path.resolve(fullPath); + const resolvedBasePath = path.resolve(POS_BASE_PATH); + debugLog(`🔒 경로 보안 검증`, { resolvedPath, resolvedBasePath, isValid: resolvedPath.startsWith(resolvedBasePath) }); + + if (!resolvedPath.startsWith(resolvedBasePath)) { + debugError(`❌ 디렉토리 탈출 시도 감지`, { resolvedPath, resolvedBasePath }); + return { + success: false, + error: '잘못된 파일 경로입니다.', + }; + } + + // 파일 존재 여부 확인 + debugProcess(`🔍 파일 존재 여부 확인 중...`); + try { + await fs.access(resolvedPath); + debugSuccess(`✅ 파일 존재 확인됨 (${resolvedPath})`); + } catch (accessError) { + debugError(`❌ 파일을 찾을 수 없음`, { resolvedPath, error: accessError }); + return { + success: false, + error: '파일을 찾을 수 없습니다.', + }; + } + + // 파일 정보 가져오기 + debugProcess(`📊 파일 정보 조회 중...`); + const stats = await fs.stat(resolvedPath); + debugLog(`📊 파일 정보`, { + resolvedPath, + size: stats.size, + isFile: stats.isFile(), + isDirectory: stats.isDirectory(), + mtime: stats.mtime + }); + + if (!stats.isFile()) { + debugError(`❌ 대상이 파일이 아님 (디렉토리)`, { resolvedPath }); + return { + success: false, + error: '디렉토리는 다운로드할 수 없습니다.', + }; + } + + // 파일 읽기 + debugProcess(`📖 파일 읽기 시작 (크기: ${stats.size} bytes)...`); + const fileBuffer = await fs.readFile(resolvedPath); + const fileName = path.basename(resolvedPath); + debugLog(`📖 파일 읽기 완료`, { + fileName, + bufferSize: fileBuffer.length, + expectedSize: stats.size, + sizeMatch: fileBuffer.length === stats.size + }); + + // MIME 타입 추정 + debugProcess(`🔍 MIME 타입 추정 중...`); + const mimeType = await getMimeType(fileName); + debugLog(`🔍 MIME 타입 추정 완료`, { fileName, mimeType }); + + debugSuccess(`✅ POS 파일 다운로드 성공`, { + fileName, + fileSize: fileBuffer.length, + mimeType + }); + + return { + success: true, + fileName, + fileBuffer, + mimeType, + }; + } catch (error) { + debugError('❌ POS 파일 다운로드 실패', { + relativePath: params.relativePath, + error: error instanceof Error ? error.message : '알 수 없는 오류', + stack: error instanceof Error ? error.stack : undefined + }); + return { + success: false, + error: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.', + }; + } +} + +/** + * 파일 확장자를 기반으로 MIME 타입 추정 + */ +export async function getMimeType(fileName: string): Promise<string> { + const ext = path.extname(fileName).toLowerCase(); + + const mimeTypes: Record<string, string> = { + '.pdf': 'application/pdf', + '.tif': 'image/tiff', + '.tiff': 'image/tiff', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.bmp': 'image/bmp', + '.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', + '.dwg': 'application/acad', + '.dxf': 'application/dxf', + '.zip': 'application/zip', + '.rar': 'application/x-rar-compressed', + '.7z': 'application/x-7z-compressed', + '.txt': 'text/plain', + }; + + return mimeTypes[ext] || 'application/octet-stream'; +} + +/** + * 클라이언트에서 사용할 수 있는 다운로드 URL을 생성하는 헬퍼 함수 + * 실제 다운로드는 별도의 API 엔드포인트에서 처리 + */ +export async function createDownloadUrl(relativePath: string): Promise<string> { + const encodedPath = encodeURIComponent(relativePath); + return `/api/pos/download?path=${encodedPath}`; +} |
