diff --git a/README.md b/README.md index 8273b37..f45841d 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ WantedBy=multi-user.target server { listen 80; server_name forensic-pathways.yourdomain.com; + client_max_body_size 50M; # Important for uploads location / { proxy_pass http://localhost:4321; diff --git a/src/pages/contribute/knowledgebase.astro b/src/pages/contribute/knowledgebase.astro index 31af7d0..43d6807 100644 --- a/src/pages/contribute/knowledgebase.astro +++ b/src/pages/contribute/knowledgebase.astro @@ -310,6 +310,13 @@ class KnowledgebaseForm { private handleFiles(files: File[]) { files.forEach(file => { + // Client-side validation before upload + const validation = this.validateFileBeforeUpload(file); + if (!validation.valid) { + this.showMessage('error', `Cannot upload ${file.name}: ${validation.error}`); + return; + } + const fileId = Date.now() + '-' + Math.random().toString(36).substr(2, 9); const newFile: UploadedFile = { id: fileId, @@ -323,6 +330,38 @@ class KnowledgebaseForm { this.renderFileList(); } + private validateFileBeforeUpload(file: File): { valid: boolean; error?: string } { + const maxSizeBytes = 50 * 1024 * 1024; // 50MB + if (file.size > maxSizeBytes) { + const sizeMB = (file.size / 1024 / 1024).toFixed(1); + const maxMB = (maxSizeBytes / 1024 / 1024).toFixed(0); + return { + valid: false, + error: `File too large (${sizeMB}MB). Maximum size: ${maxMB}MB` + }; + } + + // Check file type + const allowedExtensions = [ + '.pdf', '.doc', '.docx', '.txt', '.md', '.markdown', '.csv', '.json', + '.xml', '.html', '.rtf', '.yaml', '.yml', '.zip', '.tar', '.gz', + '.rar', '.7z', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', + '.mp4', '.webm', '.mov', '.avi' + ]; + + const fileName = file.name.toLowerCase(); + const hasValidExtension = allowedExtensions.some(ext => fileName.endsWith(ext)); + + if (!hasValidExtension) { + return { + valid: false, + error: `File type not allowed. Allowed: ${allowedExtensions.join(', ')}` + }; + } + + return { valid: true }; + } + private async uploadFile(fileId: string) { const fileItem = this.uploadedFiles.find(f => f.id === fileId); if (!fileItem) { @@ -345,22 +384,17 @@ class KnowledgebaseForm { }); console.log('[UPLOAD] Response status:', response.status); - console.log('[UPLOAD] Response headers:', Object.fromEntries(response.headers.entries())); - // FIXED: Read the response body only once - let responseData: any; let responseText: string; + let responseData: any; try { - // Try to read as text first (works for both JSON and plain text) responseText = await response.text(); console.log('[UPLOAD] Raw response:', responseText.substring(0, 200)); - // Then try to parse as JSON try { responseData = JSON.parse(responseText); } catch (parseError) { - // If JSON parsing fails, treat as plain text responseData = { error: responseText }; } } catch (readError) { @@ -377,22 +411,11 @@ class KnowledgebaseForm { this.showMessage('success', `Successfully uploaded ${fileItem.name}`); } else { - // Enhanced error handling with single response read - let errorMessage = `Upload failed with status ${response.status}`; - if (responseData && responseData.error) { - errorMessage = responseData.error; - } else if (responseText) { - errorMessage += ` (${responseText.substring(0, 100)})`; - } - - // Log additional details if available if (responseData && responseData.details) { console.error('[UPLOAD] Error details:', responseData.details); - errorMessage += ` (Details: ${responseData.details.join(', ')})`; } - throw new Error(errorMessage); } } catch (error) { console.error('[UPLOAD] Upload error for', fileItem.name, ':', error);