Spaces:
Running
Running
| import { NextRequest, NextResponse } from 'next/server' | |
| import fs from 'fs' | |
| import path from 'path' | |
| // Use /data for Hugging Face persistent storage, fallback to local for development | |
| const DATA_DIR = process.env.NODE_ENV === 'production' && fs.existsSync('/data') | |
| ? '/data' | |
| : path.join(process.cwd(), 'data') | |
| const DOCS_DIR = path.join(DATA_DIR, 'documents') | |
| export async function GET(request: NextRequest) { | |
| try { | |
| const searchParams = request.nextUrl.searchParams | |
| const filePath = searchParams.get('path') | |
| const preview = searchParams.get('preview') === 'true' | |
| if (!filePath) { | |
| return NextResponse.json({ error: 'File path required' }, { status: 400 }) | |
| } | |
| // Normalize the path to handle both forward and backward slashes | |
| const normalizedPath = filePath.replace(/\\/g, '/') | |
| const fullPath = path.resolve(path.join(DOCS_DIR, normalizedPath)) | |
| const resolvedDocsDir = path.resolve(DOCS_DIR) | |
| // Security check - ensure the resolved path is within the allowed directory | |
| if (!fullPath.startsWith(resolvedDocsDir)) { | |
| return NextResponse.json({ error: 'Invalid path - access denied' }, { status: 400 }) | |
| } | |
| if (!fs.existsSync(fullPath)) { | |
| return NextResponse.json({ error: 'File not found' }, { status: 404 }) | |
| } | |
| const stats = fs.statSync(fullPath) | |
| if (stats.isDirectory()) { | |
| return NextResponse.json({ error: 'Cannot download directory' }, { status: 400 }) | |
| } | |
| const fileBuffer = fs.readFileSync(fullPath) | |
| const fileName = path.basename(normalizedPath) | |
| const ext = path.extname(fileName).toLowerCase() | |
| // Determine content type | |
| let 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', | |
| '.md': 'text/markdown', | |
| '.json': 'application/json', | |
| '.html': 'text/html', | |
| '.css': 'text/css', | |
| '.js': 'text/javascript', | |
| '.ts': 'text/typescript', | |
| '.py': 'text/x-python', | |
| '.java': 'text/x-java', | |
| '.cpp': 'text/x-c++', | |
| '.jpg': 'image/jpeg', | |
| '.jpeg': 'image/jpeg', | |
| '.png': 'image/png', | |
| '.gif': 'image/gif', | |
| '.svg': 'image/svg+xml', | |
| '.mp3': 'audio/mpeg', | |
| '.mp4': 'video/mp4', | |
| '.zip': 'application/zip', | |
| '.rar': 'application/x-rar-compressed', | |
| '.tex': 'text/x-tex', | |
| '.latex': 'text/x-latex', | |
| '.dart': 'text/x-dart', | |
| '.flutter': 'text/x-dart', | |
| '.yaml': 'text/yaml', | |
| '.yml': 'text/yaml', | |
| '.xml': 'text/xml', | |
| '.csv': 'text/csv', | |
| '.rtf': 'application/rtf', | |
| '.odt': 'application/vnd.oasis.opendocument.text', | |
| '.ods': 'application/vnd.oasis.opendocument.spreadsheet', | |
| '.odp': 'application/vnd.oasis.opendocument.presentation' | |
| } | |
| if (mimeTypes[ext]) { | |
| contentType = mimeTypes[ext] | |
| } | |
| const headers = new Headers({ | |
| 'Content-Type': contentType, | |
| 'Content-Length': fileBuffer.length.toString(), | |
| }) | |
| // If not preview mode, add download header | |
| if (!preview) { | |
| headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`) | |
| } else { | |
| // For preview, use inline disposition for supported types | |
| if (['application/pdf', 'text/plain', 'text/markdown', 'application/json'].includes(contentType) || | |
| contentType.startsWith('image/') || contentType.startsWith('text/')) { | |
| headers.set('Content-Disposition', `inline; filename="${encodeURIComponent(fileName)}"`) | |
| } else { | |
| headers.set('Content-Disposition', `attachment; filename="${encodeURIComponent(fileName)}"`) | |
| } | |
| } | |
| return new NextResponse(fileBuffer, { headers }) | |
| } catch (error) { | |
| console.error('Error downloading file:', error) | |
| return NextResponse.json( | |
| { error: 'Failed to download file' }, | |
| { status: 500 } | |
| ) | |
| } | |
| } |