Spaces:
Runtime error
Runtime error
| import multer from 'multer'; | |
| import path from 'path'; | |
| import fs from 'fs'; | |
| import crypto from 'crypto'; | |
| import { Request } from 'express'; | |
| // Ensure uploads directory exists | |
| const uploadsDir = path.join(process.cwd(), 'uploads'); | |
| if (!fs.existsSync(uploadsDir)) { | |
| fs.mkdirSync(uploadsDir, { recursive: true }); | |
| } | |
| // Configure multer for file uploads | |
| const storage = multer.diskStorage({ | |
| destination: (req, file, cb) => { | |
| // Create subdirectories by date for organization | |
| const dateDir = new Date().toISOString().split('T')[0]; | |
| const fullPath = path.join(uploadsDir, dateDir); | |
| if (!fs.existsSync(fullPath)) { | |
| fs.mkdirSync(fullPath, { recursive: true }); | |
| } | |
| cb(null, fullPath); | |
| }, | |
| filename: (req, file, cb) => { | |
| // Generate unique filename with timestamp and random string | |
| const timestamp = Date.now(); | |
| const randomString = crypto.randomBytes(8).toString('hex'); | |
| const ext = path.extname(file.originalname); | |
| const baseName = path.basename(file.originalname, ext); | |
| // Sanitize filename | |
| const sanitizedBaseName = baseName.replace(/[^a-zA-Z0-9-_]/g, '_'); | |
| const filename = `${timestamp}_${randomString}_${sanitizedBaseName}${ext}`; | |
| cb(null, filename); | |
| } | |
| }); | |
| // File filter to accept specific file types | |
| const fileFilter = (req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => { | |
| const allowedMimeTypes = [ | |
| 'application/pdf', | |
| 'image/jpeg', | |
| 'image/jpg', | |
| 'image/png', | |
| 'image/gif', | |
| 'image/webp', | |
| 'text/plain', | |
| 'text/markdown', | |
| 'application/msword', | |
| 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |
| 'application/json' | |
| ]; | |
| if (allowedMimeTypes.includes(file.mimetype)) { | |
| cb(null, true); | |
| } else { | |
| cb(new Error(`Unsupported file type: ${file.mimetype}. Allowed types: PDF, images (JPEG, PNG, GIF, WebP), text files, Word documents, JSON`)); | |
| } | |
| }; | |
| // Configure multer with size limits | |
| export const upload = multer({ | |
| storage, | |
| fileFilter, | |
| limits: { | |
| fileSize: 50 * 1024 * 1024, // 50MB limit | |
| files: 10 // Maximum 10 files per upload | |
| } | |
| }); | |
| // File processing utilities | |
| export class FileProcessor { | |
| static async getFileInfo(filePath: string): Promise<{ | |
| size: number; | |
| mimeType: string; | |
| exists: boolean; | |
| }> { | |
| try { | |
| const stats = await fs.promises.stat(filePath); | |
| return { | |
| size: stats.size, | |
| mimeType: await this.getMimeType(filePath), | |
| exists: true | |
| }; | |
| } catch (error) { | |
| return { | |
| size: 0, | |
| mimeType: '', | |
| exists: false | |
| }; | |
| } | |
| } | |
| static async getMimeType(filePath: string): Promise<string> { | |
| const ext = path.extname(filePath).toLowerCase(); | |
| const mimeTypes: Record<string, string> = { | |
| '.pdf': 'application/pdf', | |
| '.jpg': 'image/jpeg', | |
| '.jpeg': 'image/jpeg', | |
| '.png': 'image/png', | |
| '.gif': 'image/gif', | |
| '.webp': 'image/webp', | |
| '.txt': 'text/plain', | |
| '.md': 'text/markdown', | |
| '.doc': 'application/msword', | |
| '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |
| '.json': 'application/json' | |
| }; | |
| return mimeTypes[ext] || 'application/octet-stream'; | |
| } | |
| static async readTextFile(filePath: string): Promise<string> { | |
| try { | |
| const content = await fs.promises.readFile(filePath, 'utf-8'); | |
| return content; | |
| } catch (error) { | |
| throw new Error(`Failed to read text file: ${error}`); | |
| } | |
| } | |
| static async deleteFile(filePath: string): Promise<boolean> { | |
| try { | |
| await fs.promises.unlink(filePath); | |
| return true; | |
| } catch (error) { | |
| console.error(`Failed to delete file ${filePath}:`, error); | |
| return false; | |
| } | |
| } | |
| static isTextFile(mimeType: string): boolean { | |
| return mimeType.startsWith('text/') || | |
| mimeType === 'application/json' || | |
| mimeType.includes('document'); | |
| } | |
| static isImageFile(mimeType: string): boolean { | |
| return mimeType.startsWith('image/'); | |
| } | |
| static isPdfFile(mimeType: string): boolean { | |
| return mimeType === 'application/pdf'; | |
| } | |
| static requiresOCR(mimeType: string): boolean { | |
| return this.isImageFile(mimeType) || this.isPdfFile(mimeType); | |
| } | |
| } | |
| // Upload validation middleware | |
| export const validateUpload = (req: Request, res: any, next: any) => { | |
| if (!req.files || (Array.isArray(req.files) && req.files.length === 0)) { | |
| return res.status(400).json({ | |
| error: 'No files uploaded', | |
| message: 'Please select at least one file to upload' | |
| }); | |
| } | |
| next(); | |
| }; | |
| export default upload; |