// app/api/document-download/route.ts import { NextRequest, NextResponse } from 'next/server'; import { readFile, access, constants } from 'fs/promises'; import { join } from 'path'; import db from '@/db/db'; import { documentAttachments } from '@/db/schema/vendorDocu'; import { eq } from 'drizzle-orm'; export async function GET(request: NextRequest) { try { // 파일 경로 파라미터 받기 const path = request.nextUrl.searchParams.get("path"); const attachmentId = request.nextUrl.searchParams.get("id"); console.log(path) if (!path && !attachmentId) { return NextResponse.json( { error: "File path or attachment ID is required" }, { status: 400 } ); } let dbRecord; // ID로 조회하는 경우 (더 안전함) if (attachmentId) { [dbRecord] = await db .select({ fileName: documentAttachments.fileName, filePath: documentAttachments.filePath, fileType: documentAttachments.fileType, fileSize: documentAttachments.fileSize }) .from(documentAttachments) .where(eq(documentAttachments.id, parseInt(attachmentId))); } // 경로로 조회하는 경우 else if (path) { [dbRecord] = await db .select({ fileName: documentAttachments.fileName, filePath: documentAttachments.filePath, fileType: documentAttachments.fileType, fileSize: documentAttachments.fileSize }) .from(documentAttachments) .where(eq(documentAttachments.filePath, path)); } // 파일 정보 설정 let fileName; let actualFilePath; if (dbRecord) { // DB에서 찾은 경우 원본 파일명과 경로 사용 fileName = dbRecord.fileName; actualFilePath = dbRecord.filePath; console.log("DB에서 파일 정보 찾음:", { fileName, filePath: actualFilePath }); } else { // DB에서 찾지 못한 경우 if (!path) { return NextResponse.json( { error: "File not found in database" }, { status: 404 } ); } fileName = path.split('/').pop() || 'download'; actualFilePath = path; } // 파일 경로 구성 const storedPath = actualFilePath.replace(/^\/+/, ""); // 앞쪽 슬래시 제거 // 파일 경로 시도 const possiblePaths = [ join(process.cwd(), "public", storedPath), join(process.cwd(), storedPath), // public 없이도 시도 ]; // 실제 파일 찾기 let foundPath = null; for (const testPath of possiblePaths) { try { await access(testPath, constants.R_OK); foundPath = testPath; console.log("✅ 파일 찾음:", testPath); break; } catch (err) { console.log("❌ 경로에 파일 없음:", testPath); } } if (!foundPath) { return NextResponse.json( { error: "File not found on server", details: { fileName: fileName, path: actualFilePath, triedPaths: possiblePaths } }, { status: 404 } ); } const fileBuffer = await readFile(foundPath); // MIME 타입 결정 const fileExtension = fileName.split('.').pop()?.toLowerCase() || ''; let contentType = dbRecord?.fileType || 'application/octet-stream'; // DB에 타입이 없거나 generic한 경우 확장자로 추론 if (!contentType || contentType === 'application/octet-stream') { 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', 'csv': 'text/csv', 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', 'dwg': 'application/acad', 'dxf': 'application/dxf', 'zip': 'application/zip', 'rar': 'application/vnd.rar', }; contentType = mimeTypes[fileExtension] || 'application/octet-stream'; } // 다운로드용 헤더 설정 const headers = new Headers(); headers.set('Content-Type', contentType); headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`); headers.set('Content-Length', fileBuffer.length.toString()); // 파일 크기 검증 (DB 정보와 비교) if (dbRecord?.fileSize && Math.abs(fileBuffer.length - dbRecord.fileSize) > 1024) { console.warn("⚠️ 파일 크기 불일치:", { dbSize: dbRecord.fileSize, actualSize: fileBuffer.length }); } console.log("✅ 파일 다운로드 성공:", { fileName, size: fileBuffer.length, contentType }); return new NextResponse(fileBuffer, { status: 200, headers, }); } catch (error) { console.error('❌ 문서 파일 다운로드 오류:', error); return NextResponse.json( { error: 'Failed to download file', details: String(error) }, { status: 500 } ); } }