summaryrefslogtreecommitdiff
path: root/lib/pos/download-pos-file.ts
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-09-22 19:33:16 +0900
committerjoonhoekim <26rote@gmail.com>2025-09-22 19:33:16 +0900
commit480ac58010604140d1a52fa2b839aedb6ac15941 (patch)
tree4cc45c96ea174991d59c1a058ed9da05a2a3ac8c /lib/pos/download-pos-file.ts
parentba35e67845f935c8ce0151c9ef1fefa0b0510faf (diff)
(김준회) POS I/F 로직 및 UI 처리 구현
Diffstat (limited to 'lib/pos/download-pos-file.ts')
-rw-r--r--lib/pos/download-pos-file.ts162
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}`;
+}