From 314c0980ae5d9387f43c1b32827b118d7277ddf4 Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Tue, 21 Oct 2025 21:05:25 +0200 Subject: [PATCH 1/6] ignored claude.md docs file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c2cb120..fd9c6aa 100644 --- a/.gitignore +++ b/.gitignore @@ -135,3 +135,4 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +CLAUDE.md From 4f90746fa80abe56f101c021f8ce44233f38ed51 Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Tue, 21 Oct 2025 21:19:10 +0200 Subject: [PATCH 2/6] added seperate web upload page --- index.js | 18 ++++- package.json | 1 + public/js/upload.js | 170 ++++++++++++++++++++++++++++++++++++++++++++ views/index.ejs | 2 +- views/upload.ejs | 101 ++++++++++++++++++++++++++ 5 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 public/js/upload.js create mode 100644 views/upload.ejs diff --git a/index.js b/index.js index 726c0ac..b005ab7 100644 --- a/index.js +++ b/index.js @@ -10,8 +10,20 @@ const port = process.env.PORT; const hosterEmail = process.env.HOSTER_EMAIL; app.set("view engine", "ejs"); +app.use(express.static("public")); +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'"], + fontSrc: ["'self'", "https://cdn.jsdelivr.net"], + }, + }, +})); app.use(fileRoutes); -app.use(helmet()); const s3 = require("./engines/s3.engine"); const local = require("./engines/local.engine"); @@ -57,6 +69,10 @@ app.get("/", async (req, res) => { }); }); +app.get("/upload", (req, res) => { + res.render("upload"); +}); + app.listen(port, () => { console.log(`Server is running on port ${port}`); }); diff --git a/package.json b/package.json index 03c5664..add60b5 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "scripts": { + "dev": "node index.js", "test": "jest" }, "jest": { diff --git a/public/js/upload.js b/public/js/upload.js new file mode 100644 index 0000000..0dad281 --- /dev/null +++ b/public/js/upload.js @@ -0,0 +1,170 @@ +document.addEventListener('DOMContentLoaded', function() { + console.log('Upload script loaded'); + + const dragArea = document.getElementById('dragArea'); + const fileInput = document.getElementById('fileInput'); + const uploadForm = document.getElementById('uploadForm'); + const uploadBtn = document.getElementById('uploadBtn'); + const fileInfo = document.getElementById('fileInfo'); + const fileName = document.getElementById('fileName'); + const fileSize = document.getElementById('fileSize'); + const uploadProgress = document.getElementById('uploadProgress'); + const progressBar = document.getElementById('progressBar'); + const progressText = document.getElementById('progressText'); + const uploadResult = document.getElementById('uploadResult'); + const fileUrl = document.getElementById('fileUrl'); + const browseBtn = document.getElementById('browseBtn'); + + console.log('Upload button:', uploadBtn); + console.log('Upload form:', uploadForm); + console.log('Browse button:', browseBtn); + console.log('File input:', fileInput); + + // Drag and drop functionality + dragArea.addEventListener('dragover', (e) => { + e.preventDefault(); + dragArea.classList.add('drag-over'); + }); + + dragArea.addEventListener('dragleave', () => { + dragArea.classList.remove('drag-over'); + }); + + dragArea.addEventListener('drop', (e) => { + e.preventDefault(); + dragArea.classList.remove('drag-over'); + const files = e.dataTransfer.files; + if (files.length > 0) { + fileInput.files = files; + handleFileSelect(); + } + }); + + // Browse button functionality + browseBtn.addEventListener('click', () => { + console.log('Browse button clicked'); + fileInput.click(); + }); + + // Also make the drag area clickable + dragArea.addEventListener('click', (e) => { + // Only trigger if not clicking the browse button itself + if (e.target !== browseBtn) { + console.log('Drag area clicked'); + fileInput.click(); + } + }); + + fileInput.addEventListener('change', handleFileSelect); + + function handleFileSelect() { + const file = fileInput.files[0]; + if (file) { + fileName.textContent = file.name; + fileSize.textContent = formatFileSize(file.size); + fileInfo.style.display = 'block'; + uploadBtn.disabled = false; + } + } + + function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + + uploadForm.addEventListener('submit', async (e) => { + console.log('Form submitted'); + e.preventDefault(); + + if (!fileInput.files[0]) { + alert('Please select a file first'); + return; + } + + const formData = new FormData(); + formData.append('file', fileInput.files[0]); + + const apiKey = document.getElementById('apiKey').value; + console.log('Starting upload with API key:', apiKey ? 'provided' : 'none'); + + uploadProgress.style.display = 'block'; + uploadResult.style.display = 'none'; + uploadBtn.disabled = true; + + try { + const xhr = new XMLHttpRequest(); + + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) { + const percentComplete = (e.loaded / e.total) * 100; + progressBar.style.width = percentComplete + '%'; + progressText.textContent = `Uploading... ${Math.round(percentComplete)}%`; + } + }); + + xhr.addEventListener('load', () => { + uploadProgress.style.display = 'none'; + uploadBtn.disabled = false; + + if (xhr.status === 200) { + const response = JSON.parse(xhr.responseText); + fileUrl.href = response.url; + fileUrl.textContent = response.url; + uploadResult.style.display = 'block'; + } else { + const error = JSON.parse(xhr.responseText); + alert('Upload failed: ' + (error.error || 'Unknown error')); + } + }); + + xhr.addEventListener('error', () => { + uploadProgress.style.display = 'none'; + uploadBtn.disabled = false; + alert('Upload failed: Network error'); + }); + + xhr.open('POST', '/upload'); + if (apiKey) { + xhr.setRequestHeader('x-api-key', apiKey); + } + xhr.send(formData); + + } catch (error) { + uploadProgress.style.display = 'none'; + uploadBtn.disabled = false; + alert('Upload failed: ' + error.message); + } + }); + + // Backup button click handler + uploadBtn.addEventListener('click', function(e) { + console.log('Upload button clicked'); + if (e.target.form) { + console.log('Triggering form submit'); + // Let the form submission handler take over + return; + } + // If for some reason the button isn't in a form, trigger manually + uploadForm.dispatchEvent(new Event('submit')); + }); + + // Copy button functionality + const copyBtn = document.getElementById('copyBtn'); + copyBtn.addEventListener('click', function() { + navigator.clipboard.writeText(fileUrl.href).then(() => { + alert('URL copied to clipboard!'); + }).catch(() => { + // Fallback for older browsers + const textArea = document.createElement('textarea'); + textArea.value = fileUrl.href; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + alert('URL copied to clipboard!'); + }); + }); +}); \ No newline at end of file diff --git a/views/index.ejs b/views/index.ejs index 7c3be19..dd1d7f1 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -42,7 +42,7 @@
diff --git a/views/upload.ejs b/views/upload.ejs new file mode 100644 index 0000000..01a5fa6 --- /dev/null +++ b/views/upload.ejs @@ -0,0 +1,101 @@ + + + + + + Upload File - filehost by file.coffee + + + + +
+
+

Upload to filehost

+

Select or drag and drop your file to upload

+
+ +
+
+
+ + +
+ +
+
+ + + +

Drop your file here or click below to browse

+ +

Maximum file size: Check server configuration

+
+ +
+ + + + +
+ + + + +
+ + +
+ + + + \ No newline at end of file From 8daddb3d179146b7f403a3ad6013d444dc740179 Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Wed, 22 Oct 2025 10:09:54 +0200 Subject: [PATCH 3/6] Add server configuration endpoint and enhance upload validation - Implemented a new API endpoint `/api/config` to provide file upload limits and filename length. - Enhanced client-side validation in the upload script to check file size, name length, and invalid characters. - Added error and success message styles in the upload view for better user feedback. --- index.js | 8 ++ public/js/upload.js | 278 ++++++++++++++++++++++++++++++++++++-------- views/upload.ejs | 21 ++++ 3 files changed, 259 insertions(+), 48 deletions(-) diff --git a/index.js b/index.js index b005ab7..6d2b6d9 100644 --- a/index.js +++ b/index.js @@ -73,6 +73,14 @@ app.get("/upload", (req, res) => { res.render("upload"); }); +app.get("/api/config", (req, res) => { + res.json({ + maxFileSize: parseInt(process.env.FILE_MAX_SIZE_MB, 10) * 1024 * 1024, + maxFileSizeMB: parseInt(process.env.FILE_MAX_SIZE_MB, 10), + fileNameLength: parseInt(process.env.FILE_NAME_LENGTH, 10) || 10, + }); +}); + app.listen(port, () => { console.log(`Server is running on port ${port}`); }); diff --git a/public/js/upload.js b/public/js/upload.js index 0dad281..3634940 100644 --- a/public/js/upload.js +++ b/public/js/upload.js @@ -1,6 +1,6 @@ document.addEventListener('DOMContentLoaded', function() { console.log('Upload script loaded'); - + const dragArea = document.getElementById('dragArea'); const fileInput = document.getElementById('fileInput'); const uploadForm = document.getElementById('uploadForm'); @@ -14,11 +14,95 @@ document.addEventListener('DOMContentLoaded', function() { const uploadResult = document.getElementById('uploadResult'); const fileUrl = document.getElementById('fileUrl'); const browseBtn = document.getElementById('browseBtn'); - - console.log('Upload button:', uploadBtn); - console.log('Upload form:', uploadForm); - console.log('Browse button:', browseBtn); - console.log('File input:', fileInput); + const errorMessage = document.getElementById('errorMessage'); + const maxSizeDisplay = document.getElementById('maxSize'); + + // Server configuration + let serverConfig = { + maxFileSize: 0, + maxFileSizeMB: 0, + fileNameLength: 10 + }; + + // Fetch server configuration on load + fetchServerConfig(); + + async function fetchServerConfig() { + try { + const response = await fetch('/api/config'); + if (response.ok) { + serverConfig = await response.json(); + maxSizeDisplay.textContent = `${serverConfig.maxFileSizeMB} MB`; + console.log('Server config loaded:', serverConfig); + } else { + throw new Error('Failed to fetch server configuration'); + } + } catch (error) { + console.error('Error fetching server config:', error); + showError('Warning: Could not load server configuration. File size validation may be inaccurate.'); + maxSizeDisplay.textContent = 'Unknown'; + } + } + + function showError(message) { + errorMessage.textContent = message; + errorMessage.style.display = 'block'; + // Scroll to error message + errorMessage.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + + function hideError() { + errorMessage.style.display = 'none'; + errorMessage.textContent = ''; + } + + function validateFile(file) { + hideError(); + + if (!file) { + showError('No file selected.'); + return false; + } + + // Check if server config is loaded + if (serverConfig.maxFileSize === 0) { + showError('Server configuration not loaded. Please refresh the page and try again.'); + return false; + } + + // Validate file size + if (file.size === 0) { + showError('The selected file is empty (0 bytes). Please select a valid file.'); + return false; + } + + if (file.size > serverConfig.maxFileSize) { + const fileSizeMB = (file.size / 1024 / 1024).toFixed(2); + showError(`File size (${fileSizeMB} MB) exceeds the maximum allowed size of ${serverConfig.maxFileSizeMB} MB.`); + return false; + } + + // Validate filename length + if (!file.name || file.name.length === 0) { + showError('The file has no name. Please select a valid file.'); + return false; + } + + // Check for extremely long filenames (common filesystem limit is 255 characters) + if (file.name.length > 255) { + showError(`Filename is too long (${file.name.length} characters). Maximum allowed is 255 characters.`); + return false; + } + + // Check for invalid filename characters (basic check) + const invalidChars = /[<>:"|?*\x00-\x1F]/; + if (invalidChars.test(file.name)) { + showError('Filename contains invalid characters. Please rename the file and try again.'); + return false; + } + + return true; + } // Drag and drop functionality dragArea.addEventListener('dragover', (e) => { @@ -45,7 +129,7 @@ document.addEventListener('DOMContentLoaded', function() { console.log('Browse button clicked'); fileInput.click(); }); - + // Also make the drag area clickable dragArea.addEventListener('click', (e) => { // Only trigger if not clicking the browse button itself @@ -59,12 +143,25 @@ document.addEventListener('DOMContentLoaded', function() { function handleFileSelect() { const file = fileInput.files[0]; - if (file) { - fileName.textContent = file.name; - fileSize.textContent = formatFileSize(file.size); - fileInfo.style.display = 'block'; - uploadBtn.disabled = false; + + if (!file) { + return; + } + + // Validate file immediately + if (!validateFile(file)) { + fileInput.value = ''; // Clear the file input + fileInfo.style.display = 'none'; + uploadBtn.disabled = true; + return; } + + // File is valid, show info and enable upload + fileName.textContent = file.name; + fileSize.textContent = formatFileSize(file.size); + fileInfo.style.display = 'block'; + uploadBtn.disabled = false; + hideError(); } function formatFileSize(bytes) { @@ -78,25 +175,28 @@ document.addEventListener('DOMContentLoaded', function() { uploadForm.addEventListener('submit', async (e) => { console.log('Form submitted'); e.preventDefault(); - - if (!fileInput.files[0]) { - alert('Please select a file first'); + + const file = fileInput.files[0]; + + // Re-validate file before upload + if (!validateFile(file)) { return; } - + const formData = new FormData(); - formData.append('file', fileInput.files[0]); - + formData.append('file', file); + const apiKey = document.getElementById('apiKey').value; console.log('Starting upload with API key:', apiKey ? 'provided' : 'none'); - + uploadProgress.style.display = 'block'; uploadResult.style.display = 'none'; uploadBtn.disabled = true; - + hideError(); + try { const xhr = new XMLHttpRequest(); - + xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percentComplete = (e.loaded / e.total) * 100; @@ -104,67 +204,149 @@ document.addEventListener('DOMContentLoaded', function() { progressText.textContent = `Uploading... ${Math.round(percentComplete)}%`; } }); - + xhr.addEventListener('load', () => { uploadProgress.style.display = 'none'; uploadBtn.disabled = false; - + if (xhr.status === 200) { - const response = JSON.parse(xhr.responseText); - fileUrl.href = response.url; - fileUrl.textContent = response.url; - uploadResult.style.display = 'block'; + try { + const response = JSON.parse(xhr.responseText); + fileUrl.href = response.url; + fileUrl.textContent = response.url; + uploadResult.style.display = 'block'; + + // Clear form for next upload + fileInput.value = ''; + fileInfo.style.display = 'none'; + uploadBtn.disabled = true; + } catch (parseError) { + console.error('Error parsing success response:', parseError); + showError('Upload may have succeeded, but the response format was unexpected. Please check your uploads.'); + } } else { - const error = JSON.parse(xhr.responseText); - alert('Upload failed: ' + (error.error || 'Unknown error')); + handleUploadError(xhr); } }); - + xhr.addEventListener('error', () => { uploadProgress.style.display = 'none'; uploadBtn.disabled = false; - alert('Upload failed: Network error'); + showError('Upload failed: Network error. Please check your internet connection and try again.'); + }); + + xhr.addEventListener('timeout', () => { + uploadProgress.style.display = 'none'; + uploadBtn.disabled = false; + showError('Upload failed: Request timed out. The file may be too large or your connection too slow.'); + }); + + xhr.addEventListener('abort', () => { + uploadProgress.style.display = 'none'; + uploadBtn.disabled = false; + showError('Upload was cancelled.'); }); - + xhr.open('POST', '/upload'); if (apiKey) { xhr.setRequestHeader('x-api-key', apiKey); } + + // Set timeout to 5 minutes for large files + xhr.timeout = 300000; + xhr.send(formData); - + } catch (error) { uploadProgress.style.display = 'none'; uploadBtn.disabled = false; - alert('Upload failed: ' + error.message); + console.error('Upload error:', error); + showError(`Upload failed: ${error.message || 'An unexpected error occurred. Please try again.'}`); } }); - // Backup button click handler - uploadBtn.addEventListener('click', function(e) { - console.log('Upload button clicked'); - if (e.target.form) { - console.log('Triggering form submit'); - // Let the form submission handler take over - return; + function handleUploadError(xhr) { + let errorMsg = 'Upload failed: '; + + try { + // Try to parse error response + const errorResponse = JSON.parse(xhr.responseText); + if (errorResponse.error) { + errorMsg += errorResponse.error; + } else if (errorResponse.message) { + errorMsg += errorResponse.message; + } else { + errorMsg += `Server returned status ${xhr.status}`; + } + } catch (parseError) { + // If we can't parse the response, provide helpful error based on status code + switch (xhr.status) { + case 400: + errorMsg += 'Invalid request. Please ensure you selected a valid file.'; + break; + case 401: + errorMsg += 'Authentication failed. Please check your API key.'; + break; + case 403: + errorMsg += 'Access denied. Public uploads may be disabled or your API key is invalid.'; + break; + case 413: + errorMsg += `File too large. Maximum size is ${serverConfig.maxFileSizeMB} MB.`; + break; + case 429: + errorMsg += 'Too many requests. Please wait a moment and try again.'; + break; + case 500: + errorMsg += 'Server error. Please try again later or contact the administrator.'; + break; + case 503: + errorMsg += 'Service temporarily unavailable. Please try again later.'; + break; + default: + errorMsg += `Unexpected error (${xhr.status}). `; + if (xhr.responseText) { + errorMsg += xhr.responseText.substring(0, 100); + } else { + errorMsg += 'Please try again or contact the administrator.'; + } + } } - // If for some reason the button isn't in a form, trigger manually - uploadForm.dispatchEvent(new Event('submit')); - }); + + console.error('Upload failed:', { + status: xhr.status, + statusText: xhr.statusText, + response: xhr.responseText + }); + + showError(errorMsg); + } // Copy button functionality const copyBtn = document.getElementById('copyBtn'); copyBtn.addEventListener('click', function() { navigator.clipboard.writeText(fileUrl.href).then(() => { - alert('URL copied to clipboard!'); + const originalText = copyBtn.textContent; + copyBtn.textContent = 'Copied!'; + setTimeout(() => { + copyBtn.textContent = originalText; + }, 2000); }).catch(() => { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = fileUrl.href; document.body.appendChild(textArea); textArea.select(); - document.execCommand('copy'); + try { + document.execCommand('copy'); + const originalText = copyBtn.textContent; + copyBtn.textContent = 'Copied!'; + setTimeout(() => { + copyBtn.textContent = originalText; + }, 2000); + } catch (err) { + showError('Failed to copy URL to clipboard. Please copy it manually.'); + } document.body.removeChild(textArea); - alert('URL copied to clipboard!'); }); }); -}); \ No newline at end of file +}); diff --git a/views/upload.ejs b/views/upload.ejs index 01a5fa6..ccd60eb 100644 --- a/views/upload.ejs +++ b/views/upload.ejs @@ -33,6 +33,26 @@ .file-input { display: none; } + + .error-message { + background-color: #991B1B; + border: 1px solid #DC2626; + color: #FEE2E2; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + display: none; + } + + .success-message { + background-color: #065F46; + border: 1px solid #10B981; + color: #D1FAE5; + padding: 1rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + display: none; + } @@ -43,6 +63,7 @@
+
From e6fc80d7a87a0d8aec7c1f7bda51425501cae8a7 Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Wed, 22 Oct 2025 10:12:54 +0200 Subject: [PATCH 4/6] check for file size length --- public/js/upload.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/js/upload.js b/public/js/upload.js index 3634940..19fff86 100644 --- a/public/js/upload.js +++ b/public/js/upload.js @@ -89,8 +89,8 @@ document.addEventListener('DOMContentLoaded', function() { } // Check for extremely long filenames (common filesystem limit is 255 characters) - if (file.name.length > 255) { - showError(`Filename is too long (${file.name.length} characters). Maximum allowed is 255 characters.`); + if (file.name.length > serverConfig.fileNameLength) { + showError(`Filename is too long (${file.name.length} characters). Maximum allowed is ${serverConfig.fileNameLength} characters.`); return false; } From 47bcb7077702fbcb5060747b00d439b24ba1f382 Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Wed, 22 Oct 2025 10:24:28 +0200 Subject: [PATCH 5/6] Remove console logs and improve error handling in upload script --- public/js/upload.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/public/js/upload.js b/public/js/upload.js index 19fff86..d0f8b5a 100644 --- a/public/js/upload.js +++ b/public/js/upload.js @@ -1,6 +1,4 @@ document.addEventListener('DOMContentLoaded', function() { - console.log('Upload script loaded'); - const dragArea = document.getElementById('dragArea'); const fileInput = document.getElementById('fileInput'); const uploadForm = document.getElementById('uploadForm'); @@ -33,12 +31,10 @@ document.addEventListener('DOMContentLoaded', function() { if (response.ok) { serverConfig = await response.json(); maxSizeDisplay.textContent = `${serverConfig.maxFileSizeMB} MB`; - console.log('Server config loaded:', serverConfig); } else { throw new Error('Failed to fetch server configuration'); } } catch (error) { - console.error('Error fetching server config:', error); showError('Warning: Could not load server configuration. File size validation may be inaccurate.'); maxSizeDisplay.textContent = 'Unknown'; } @@ -126,7 +122,6 @@ document.addEventListener('DOMContentLoaded', function() { // Browse button functionality browseBtn.addEventListener('click', () => { - console.log('Browse button clicked'); fileInput.click(); }); @@ -134,7 +129,6 @@ document.addEventListener('DOMContentLoaded', function() { dragArea.addEventListener('click', (e) => { // Only trigger if not clicking the browse button itself if (e.target !== browseBtn) { - console.log('Drag area clicked'); fileInput.click(); } }); @@ -173,7 +167,6 @@ document.addEventListener('DOMContentLoaded', function() { } uploadForm.addEventListener('submit', async (e) => { - console.log('Form submitted'); e.preventDefault(); const file = fileInput.files[0]; @@ -187,7 +180,6 @@ document.addEventListener('DOMContentLoaded', function() { formData.append('file', file); const apiKey = document.getElementById('apiKey').value; - console.log('Starting upload with API key:', apiKey ? 'provided' : 'none'); uploadProgress.style.display = 'block'; uploadResult.style.display = 'none'; @@ -221,7 +213,6 @@ document.addEventListener('DOMContentLoaded', function() { fileInfo.style.display = 'none'; uploadBtn.disabled = true; } catch (parseError) { - console.error('Error parsing success response:', parseError); showError('Upload may have succeeded, but the response format was unexpected. Please check your uploads.'); } } else { @@ -260,7 +251,6 @@ document.addEventListener('DOMContentLoaded', function() { } catch (error) { uploadProgress.style.display = 'none'; uploadBtn.disabled = false; - console.error('Upload error:', error); showError(`Upload failed: ${error.message || 'An unexpected error occurred. Please try again.'}`); } }); @@ -312,12 +302,6 @@ document.addEventListener('DOMContentLoaded', function() { } } - console.error('Upload failed:', { - status: xhr.status, - statusText: xhr.statusText, - response: xhr.responseText - }); - showError(errorMsg); } From bf8efdc06d82063dd6d6710ef5f2bf187acb094f Mon Sep 17 00:00:00 2001 From: lukasvdberk Date: Sun, 15 Feb 2026 14:28:38 +0100 Subject: [PATCH 6/6] Add configuration for enabling web upload page and update README --- .env.example | 3 +++ README.md | 6 ++++++ index.js | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/.env.example b/.env.example index 9eca3f5..1976968 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,9 @@ PORT=3000 # Comma-separated list of API keys API_KEYS=key1,key2,key3 +# Enable the web upload page (default: false) +ENABLE_UPLOAD_PAGE=false + # This is the maximum file size that can be uploaded and the max file name length. '-1' is unlimited file size, not recommended. FILE_NAME_LENGTH=10 FILE_MAX_SIZE_MB=30 diff --git a/README.md b/README.md index 65fc2a0..545f371 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,12 @@ We also recommend forking the project and deploying your forked version to avoid - [ ] Advertising support - [ ] NSFW detection and filtering +### Web Upload Page +The built-in web upload page at `/upload` is disabled by default. To enable it, set the following in your `.env`: +``` +ENABLE_UPLOAD_PAGE=true +``` + ### S3 Compatible Storage For the s3 compatbile storage engine, we recommend using Contabo Object Storage. It's a cheap (2,50/mth for 250GB with unlimited bandwidth at 80mbps) and really easy to set up. Just make an account, get the object storage, make a bucket and fill in the details in the `.env` and it _just works_. diff --git a/index.js b/index.js index 6d2b6d9..d8b7439 100644 --- a/index.js +++ b/index.js @@ -69,7 +69,12 @@ app.get("/", async (req, res) => { }); }); +const enableUploadPage = process.env.ENABLE_UPLOAD_PAGE === "true"; + app.get("/upload", (req, res) => { + if (!enableUploadPage) { + return res.status(404).send("Upload page is disabled."); + } res.render("upload"); });