File size: 5,207 Bytes
10ac46e ccf1a85 10ac46e ccf1a85 10ac46e |
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 174 175 176 177 178 179 180 181 182 |
import multer from 'multer';
import path from 'path';
import fs from 'fs';
import crypto from 'crypto';
import { Request } from 'express';
// Use /tmp for uploads in production environments (like Hugging Face Spaces)
// This ensures we have write permissions
const uploadsDir = process.env.NODE_ENV === 'production'
? path.join('/tmp', 'uploads')
: path.join(process.cwd(), 'uploads');
// Safely create uploads directory with error handling
try {
if (!fs.existsSync(uploadsDir)) {
fs.mkdirSync(uploadsDir, { recursive: true });
}
} catch (error) {
console.warn(`Failed to create uploads directory at ${uploadsDir}:`, error);
console.log('File uploads will be disabled');
}
// Configure multer for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
try {
// 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);
} catch (error) {
console.error('Failed to create upload directory:', error);
// Fallback to base uploads directory
cb(null, uploadsDir);
}
},
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; |