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 = { '.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 } ) } }