File size: 5,833 Bytes
10ac46e ccf1a85 353a9b9 ccf1a85 353a9b9 ccf1a85 353a9b9 10ac46e ccf1a85 353a9b9 ccf1a85 353a9b9 ccf1a85 353a9b9 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 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
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 });
console.log(`β
Created uploads directory: ${uploadsDir}`);
} else {
console.log(`β
Uploads directory exists: ${uploadsDir}`);
}
// Test write permissions
const testFile = path.join(uploadsDir, 'test-permissions.txt');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
console.log(`β
Upload directory is writable: ${uploadsDir}`);
} catch (error) {
console.warn(`β Failed to create or write to uploads directory at ${uploadsDir}:`, error);
console.log('File uploads may not work properly');
}
// 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);
console.log(`Creating upload destination: ${fullPath} for file: ${file.originalname}`);
if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath, { recursive: true });
console.log(`Created upload subdirectory: ${fullPath}`);
}
cb(null, fullPath);
} catch (error) {
console.error('Failed to create upload directory:', error);
console.log(`Falling back to base directory: ${uploadsDir}`);
// 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; |