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;