1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
|
// 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<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',
'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 }
);
}
}
|