mirror of
https://github.com/DumbWareio/DumbDrop.git
synced 2025-10-23 07:41:58 +00:00
@@ -14,3 +14,5 @@ DUMBDROP_TITLE=DumbDrop # Site title displayed in header
|
||||
APPRISE_URL= # Apprise URL for notifications (e.g., tgram://bottoken/ChatID)
|
||||
APPRISE_MESSAGE=New file uploaded - {filename} ({size}), Storage used {storage}
|
||||
APPRISE_SIZE_UNIT=auto # Size unit for notifications (auto, B, KB, MB, GB, TB)
|
||||
|
||||
DEMO_MODE=false
|
||||
|
@@ -13,6 +13,7 @@ No auth (unless you want it now!), no storage, no nothing. Just a simple file up
|
||||
- [Security](#security)
|
||||
- [Development](#development)
|
||||
- [Technical Details](#technical-details)
|
||||
- [Demo Mode](demo.md)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
|
28
demo.md
Normal file
28
demo.md
Normal file
@@ -0,0 +1,28 @@
|
||||
|
||||
## Demo Mode
|
||||
|
||||
### Overview
|
||||
DumbDrop includes a demo mode that allows testing the application without actually storing files. Perfect for trying out the interface or development testing.
|
||||
|
||||
### Enabling Demo Mode
|
||||
Set in your environment or docker-compose.yml:
|
||||
```env
|
||||
DEMO_MODE=true
|
||||
```
|
||||
|
||||
### Demo Features
|
||||
- 🚫 No actual file storage - files are processed in memory
|
||||
- 🎯 Full UI experience with upload/download simulation
|
||||
- 🔄 Maintains all functionality including:
|
||||
- Drag and drop
|
||||
- Progress tracking
|
||||
- Multiple file uploads
|
||||
- Directory structure
|
||||
- File listings
|
||||
- 🚨 Clear visual indicator (red banner) showing demo status
|
||||
- 🧹 Auto-cleans upload directory on startup
|
||||
- Files are processed but not written to disk
|
||||
- Upload progress is simulated
|
||||
- File metadata stored in memory
|
||||
- Maintains same API responses as production
|
||||
- Cleared on server restart
|
5588
package-lock.json
generated
5588
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
src/app.js
22
src/app.js
@@ -16,6 +16,7 @@ const { ensureDirectoryExists } = require('./utils/fileUtils');
|
||||
const { securityHeaders, requirePin } = require('./middleware/security');
|
||||
const { safeCompare } = require('./utils/security');
|
||||
const { initUploadLimiter, pinVerifyLimiter, downloadLimiter } = require('./middleware/rateLimiter');
|
||||
const { injectDemoBanner, demoMiddleware } = require('./utils/demoMode');
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
@@ -34,6 +35,9 @@ const { router: uploadRouter } = require('./routes/upload');
|
||||
const fileRoutes = require('./routes/files');
|
||||
const authRoutes = require('./routes/auth');
|
||||
|
||||
// Add demo middleware before your routes
|
||||
app.use(demoMiddleware);
|
||||
|
||||
// Use routes with appropriate middleware
|
||||
app.use('/api/auth', pinVerifyLimiter, authRoutes);
|
||||
app.use('/api/upload', requirePin(config.pin), initUploadLimiter, uploadRouter);
|
||||
@@ -49,6 +53,7 @@ app.get('/', (req, res) => {
|
||||
let html = fs.readFileSync(path.join(__dirname, '../public', 'index.html'), 'utf8');
|
||||
html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
|
||||
html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
|
||||
html = injectDemoBanner(html);
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
@@ -61,6 +66,7 @@ app.get('/login.html', (req, res) => {
|
||||
|
||||
let html = fs.readFileSync(path.join(__dirname, '../public', 'login.html'), 'utf8');
|
||||
html = html.replace(/{{SITE_TITLE}}/g, config.siteTitle);
|
||||
html = injectDemoBanner(html);
|
||||
res.send(html);
|
||||
});
|
||||
|
||||
@@ -77,6 +83,7 @@ app.use((req, res, next) => {
|
||||
if (req.path === 'index.html') {
|
||||
html = html.replace('{{AUTO_UPLOAD}}', config.autoUpload.toString());
|
||||
}
|
||||
html = injectDemoBanner(html);
|
||||
res.send(html);
|
||||
} catch (err) {
|
||||
next();
|
||||
@@ -117,6 +124,21 @@ async function initialize() {
|
||||
logger.info('Apprise notifications enabled');
|
||||
}
|
||||
|
||||
// After initializing demo middleware
|
||||
if (process.env.DEMO_MODE === 'true') {
|
||||
logger.info('[DEMO] Running in demo mode - uploads will not be saved');
|
||||
// Clear any existing files in upload directory
|
||||
try {
|
||||
const files = fs.readdirSync(config.uploadDir);
|
||||
for (const file of files) {
|
||||
fs.unlinkSync(path.join(config.uploadDir, file));
|
||||
}
|
||||
logger.info('[DEMO] Cleared upload directory');
|
||||
} catch (err) {
|
||||
logger.error(`[DEMO] Failed to clear upload directory: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
} catch (err) {
|
||||
logger.error(`Initialization failed: ${err.message}`);
|
||||
|
@@ -14,6 +14,7 @@ const { getUniqueFilePath, getUniqueFolderPath } = require('../utils/fileUtils')
|
||||
const { sendNotification } = require('../services/notifications');
|
||||
const fs = require('fs');
|
||||
const { cleanupIncompleteUploads } = require('../utils/cleanup');
|
||||
const { isDemoMode, createMockUploadResponse } = require('../utils/demoMode');
|
||||
|
||||
// Store ongoing uploads
|
||||
const uploads = new Map();
|
||||
|
184
src/utils/demoMode.js
Normal file
184
src/utils/demoMode.js
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Demo mode utilities
|
||||
* Provides demo banner and demo-related functionality
|
||||
* Used to clearly indicate when application is running in demo mode
|
||||
*/
|
||||
|
||||
const multer = require('multer');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const logger = require('./logger');
|
||||
const { config } = require('../config');
|
||||
|
||||
const isDemoMode = () => process.env.DEMO_MODE === 'true';
|
||||
|
||||
const getDemoBannerHTML = () => `
|
||||
<div id="demo-banner" style="
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||
">
|
||||
🚀 DEMO MODE - This is a demonstration only. Files will not be saved. 🚀
|
||||
</div>
|
||||
`;
|
||||
|
||||
const injectDemoBanner = (html) => {
|
||||
if (!isDemoMode()) return html;
|
||||
return html.replace(
|
||||
'<body>',
|
||||
'<body>' + getDemoBannerHTML()
|
||||
);
|
||||
};
|
||||
|
||||
// Mock storage for demo files and uploads
|
||||
const demoFiles = new Map();
|
||||
const demoUploads = new Map();
|
||||
|
||||
// Configure demo upload handling
|
||||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage });
|
||||
|
||||
// Create demo routes with exact path matching
|
||||
const demoRouter = express.Router();
|
||||
|
||||
// Mock upload init - match exact path
|
||||
demoRouter.post('/api/upload/init', (req, res) => {
|
||||
const { filename, fileSize } = req.body;
|
||||
const uploadId = 'demo-' + Math.random().toString(36).substr(2, 9);
|
||||
|
||||
demoUploads.set(uploadId, {
|
||||
filename,
|
||||
fileSize,
|
||||
bytesReceived: 0
|
||||
});
|
||||
|
||||
logger.info(`[DEMO] Initialized upload for ${filename} (${fileSize} bytes)`);
|
||||
|
||||
return res.json({ uploadId });
|
||||
});
|
||||
|
||||
// Mock chunk upload - match exact path and handle large files
|
||||
demoRouter.post('/api/upload/chunk/:uploadId',
|
||||
express.raw({
|
||||
type: 'application/octet-stream',
|
||||
limit: config.maxFileSize
|
||||
}),
|
||||
(req, res) => {
|
||||
const { uploadId } = req.params;
|
||||
const upload = demoUploads.get(uploadId);
|
||||
|
||||
if (!upload) {
|
||||
return res.status(404).json({ error: 'Upload not found' });
|
||||
}
|
||||
|
||||
const chunkSize = req.body.length;
|
||||
upload.bytesReceived += chunkSize;
|
||||
|
||||
// Calculate progress
|
||||
const progress = Math.min(
|
||||
Math.round((upload.bytesReceived / upload.fileSize) * 100),
|
||||
100
|
||||
);
|
||||
|
||||
logger.debug(`[DEMO] Chunk received for ${upload.filename}, progress: ${progress}%`);
|
||||
|
||||
// If upload is complete
|
||||
if (upload.bytesReceived >= upload.fileSize) {
|
||||
const fileId = 'demo-' + Math.random().toString(36).substr(2, 9);
|
||||
const mockFile = {
|
||||
id: fileId,
|
||||
name: upload.filename,
|
||||
size: upload.fileSize,
|
||||
url: `/api/files/${fileId}`,
|
||||
createdAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
demoFiles.set(fileId, mockFile);
|
||||
demoUploads.delete(uploadId);
|
||||
|
||||
logger.success(`[DEMO] Upload completed: ${upload.filename} (${upload.fileSize} bytes)`);
|
||||
|
||||
// Return completion response
|
||||
return res.json({
|
||||
bytesReceived: upload.bytesReceived,
|
||||
progress,
|
||||
complete: true,
|
||||
file: mockFile
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
bytesReceived: upload.bytesReceived,
|
||||
progress
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Mock upload cancel - match exact path
|
||||
demoRouter.post('/api/upload/cancel/:uploadId', (req, res) => {
|
||||
const { uploadId } = req.params;
|
||||
demoUploads.delete(uploadId);
|
||||
logger.info(`[DEMO] Upload cancelled: ${uploadId}`);
|
||||
return res.json({ message: 'Upload cancelled' });
|
||||
});
|
||||
|
||||
// Mock file download - match exact path
|
||||
demoRouter.get('/api/files/:id', (req, res) => {
|
||||
const file = demoFiles.get(req.params.id);
|
||||
if (!file) {
|
||||
return res.status(404).json({
|
||||
message: 'Demo Mode: File not found'
|
||||
});
|
||||
}
|
||||
return res.json({
|
||||
message: 'Demo Mode: This would download the file in production',
|
||||
file
|
||||
});
|
||||
});
|
||||
|
||||
// Mock file list - match exact path
|
||||
demoRouter.get('/api/files', (req, res) => {
|
||||
return res.json({
|
||||
files: Array.from(demoFiles.values()),
|
||||
message: 'Demo Mode: Showing mock file list'
|
||||
});
|
||||
});
|
||||
|
||||
// Update middleware to handle errors
|
||||
const demoMiddleware = (req, res, next) => {
|
||||
if (!isDemoMode()) return next();
|
||||
|
||||
logger.debug(`[DEMO] Incoming request: ${req.method} ${req.path}`);
|
||||
|
||||
// Handle payload too large errors
|
||||
demoRouter(req, res, (err) => {
|
||||
if (err) {
|
||||
logger.error(`[DEMO] Error handling request: ${err.message}`);
|
||||
if (err.type === 'entity.too.large') {
|
||||
return res.status(413).json({
|
||||
error: 'Payload too large',
|
||||
message: `File size exceeds limit of ${config.maxFileSize} bytes`
|
||||
});
|
||||
}
|
||||
return res.status(500).json({
|
||||
error: 'Internal server error',
|
||||
message: err.message
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
isDemoMode,
|
||||
injectDemoBanner,
|
||||
demoMiddleware
|
||||
};
|
Reference in New Issue
Block a user