diff --git a/.env b/.env
index cc0be73..3eb07f8 100644
--- a/.env
+++ b/.env
@@ -1 +1,2 @@
+PORT=3001
ATLAS_URI="mongodb://localhost:27017/file-upload"
\ No newline at end of file
diff --git a/config.js b/config.js
index acc2611..ee43118 100644
--- a/config.js
+++ b/config.js
@@ -1,12 +1,24 @@
const mongoose = require("mongoose");
+// Configure mongoose for serverless
+mongoose.set('strictQuery', false);
+
const connectDB = async () => {
+ // If already connected, return
+ if (mongoose.connection.readyState >= 1) {
+ console.log("MongoDB already connected");
+ return;
+ }
+
try {
- await mongoose.connect(process.env.ATLAS_URI);
+ await mongoose.connect(process.env.ATLAS_URI, {
+ serverSelectionTimeoutMS: 5000, // Timeout after 5s instead of 30s
+ socketTimeoutMS: 45000,
+ });
console.log("MongoDB connected");
} catch (err) {
- console.error(err.message);
- process.exit(1);
+ console.error("MongoDB connection error:", err.message);
+ throw err; // Throw error to be caught by middleware
}
};
diff --git a/controllers/fileController.js b/controllers/fileController.js
new file mode 100644
index 0000000..5a0e3aa
--- /dev/null
+++ b/controllers/fileController.js
@@ -0,0 +1,181 @@
+const mongoose = require("mongoose");
+const archiver = require("archiver");
+const { Transform } = require("stream");
+const FileModel = require("../models/File");
+
+class FileController {
+ // Upload single file
+ async uploadFile(req, res) {
+ try {
+ res.status(201).json({
+ text: "File uploaded successfully !",
+ file: req.file
+ });
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: "Unable to upload the file", error },
+ });
+ }
+ }
+
+ // Upload multiple files
+ async uploadFiles(req, res) {
+ try {
+ res.status(201).json({
+ text: "Files uploaded successfully !",
+ files: req.files
+ });
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to upload files`, error },
+ });
+ }
+ }
+
+ // Get all files
+ async getAllFiles(req, res) {
+ try {
+ const files = await FileModel.getAllFiles();
+ res.status(200).json(files);
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to retrieve files`, error },
+ });
+ }
+ }
+
+ // Download a single file by id
+ async downloadFile(req, res) {
+ try {
+ const { fileId } = req.params;
+
+ // Check if file exists
+ const file = await FileModel.getFileById(fileId);
+ if (file.length === 0) {
+ return res.status(404).json({ error: { text: "File not found" } });
+ }
+
+ // Set the headers
+ res.set("Content-Type", file[0].contentType);
+ res.set("Content-Disposition", `attachment; filename=${file[0].filename}`);
+
+ // Create a stream to read from the bucket
+ const downloadStream = FileModel.openDownloadStream(fileId);
+
+ // Pipe the stream to the response
+ downloadStream.pipe(res);
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({ error: { text: `Unable to download file`, error } });
+ }
+ }
+
+ // Download multiple files in a zip file
+ async downloadFilesZip(req, res) {
+ try {
+ const files = await FileModel.getAllFiles();
+ if (files.length === 0) {
+ return res.status(404).json({ error: { text: "No files found" } });
+ }
+
+ res.set("Content-Type", "application/zip");
+ res.set("Content-Disposition", `attachment; filename=files.zip`);
+ res.set("Access-Control-Allow-Origin", "*");
+
+ const archive = archiver("zip", {
+ zlib: { level: 9 },
+ });
+
+ archive.pipe(res);
+
+ files.forEach((file) => {
+ const downloadStream = FileModel.openDownloadStream(file._id);
+ archive.append(downloadStream, { name: file.filename });
+ });
+
+ archive.finalize();
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to download files`, error },
+ });
+ }
+ }
+
+ // Download multiple files in base64 format
+ async downloadFilesBase64(req, res) {
+ try {
+ const files = await FileModel.getAllFiles();
+
+ const filesData = await Promise.all(
+ files.map((file) => {
+ return new Promise((resolve, _reject) => {
+ FileModel.openDownloadStream(file._id).pipe(
+ (() => {
+ const chunks = [];
+ return new Transform({
+ transform(chunk, encoding, done) {
+ chunks.push(chunk);
+ done();
+ },
+ flush(done) {
+ const fbuf = Buffer.concat(chunks);
+ const fileBase64String = fbuf.toString("base64");
+ resolve({
+ filename: file.filename,
+ contentType: file.contentType,
+ data: fileBase64String,
+ size: file.length,
+ uploadDate: file.uploadDate
+ });
+ done();
+ },
+ });
+ })()
+ );
+ });
+ })
+ );
+ res.status(200).json(filesData);
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to retrieve files`, error },
+ });
+ }
+ }
+
+ // Rename a file
+ async renameFile(req, res) {
+ try {
+ const { fileId } = req.params;
+ const { filename } = req.body;
+ await FileModel.renameFile(fileId, filename);
+ res.status(200).json({ text: "File renamed successfully !" });
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to rename file`, error },
+ });
+ }
+ }
+
+ // Delete a file
+ async deleteFile(req, res) {
+ try {
+ const { fileId } = req.params;
+ await FileModel.deleteFile(fileId);
+ res.status(200).json({ text: "File deleted successfully !" });
+ } catch (error) {
+ console.log(error);
+ res.status(400).json({
+ error: { text: `Unable to delete file`, error },
+ });
+ }
+ }
+}
+
+module.exports = new FileController();
diff --git a/index.js b/index.js
index 7583514..11a9000 100644
--- a/index.js
+++ b/index.js
@@ -2,190 +2,60 @@ const express = require("express");
const bodyParser = require("body-parser");
const logger = require("morgan");
const dotenv = require("dotenv");
+const path = require("path");
const connectDB = require("./config");
-const mongoose = require("mongoose");
-const { upload } = require("./utils/upload");
-const archiver = require("archiver");
-const { Transform } = require("stream");
+const fileRoutes = require("./routes/fileRoutes");
+const fs = require("fs");
dotenv.config();
const app = express();
-// Connect to database
-connectDB();
-
-// Connect to MongoDB GridFS bucket using mongoose
-let bucket;
-(() => {
- mongoose.connection.on("connected", () => {
- bucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
- bucketName: "uploads",
- });
- });
-})();
+// Lazy database connection - only connect when needed
+let isConnected = false;
+const ensureDbConnection = async () => {
+ if (!isConnected) {
+ await connectDB();
+ isConnected = true;
+ }
+};
// Middleware for parsing request body and logging requests
app.use(bodyParser.json());
app.use(logger("dev"));
-/* Routes for API endpoints */
-// Upload a single file
-app.post("/upload/file", upload().single("file"), async (req, res) => {
- try {
- res.status(201).json({ text: "File uploaded successfully !" });
- } catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: "Unable to upload the file", error },
- });
- }
-});
-
-// Upload multiple files
-app.post("/upload/files", upload().array("files"), async (req, res) => {
- try {
- res.status(201).json({ text: "Files uploaded successfully !" });
- } catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: `Unable to upload files`, error },
- });
- }
-});
-
-// Download a file by id
-app.get("/download/files/:fileId", async (req, res) => {
- try {
- const { fileId } = req.params;
-
- // Check if file exists
- const file = await bucket
- .find({ _id: new mongoose.Types.ObjectId(fileId) })
- .toArray();
- if (file.length === 0) {
- return res.status(404).json({ error: { text: "File not found" } });
- }
-
- // set the headers
- res.set("Content-Type", file[0].contentType);
- res.set("Content-Disposition", `attachment; filename=${file[0].filename}`);
-
- // create a stream to read from the bucket
- const downloadStream = bucket.openDownloadStream(
- new mongoose.Types.ObjectId(fileId)
- );
-
- // pipe the stream to the response
- downloadStream.pipe(res);
- } catch (error) {
- console.log(error);
- res.status(400).json({ error: { text: `Unable to download file`, error } });
- }
-});
-
-// Download multiple files in a zip file
-app.get("/download/files-zip", async (req, res) => {
+// Middleware to ensure DB connection for API routes
+app.use(async (req, res, next) => {
try {
- const files = await bucket.find().toArray();
- if (files.length === 0) {
- return res.status(404).json({ error: { text: "No files found" } });
- }
- res.set("Content-Type", "application/zip");
- res.set("Content-Disposition", `attachment; filename=files.zip`);
- res.set("Access-Control-Allow-Origin", "*");
- const archive = archiver("zip", {
- zlib: { level: 9 },
- });
-
- archive.pipe(res);
-
- files.forEach((file) => {
- const downloadStream = bucket.openDownloadStream(
- new mongoose.Types.ObjectId(file._id)
- );
- archive.append(downloadStream, { name: file.filename });
- });
-
- archive.finalize();
+ await ensureDbConnection();
+ next();
} catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: `Unable to download files`, error },
- });
+ console.error("Database connection error:", error);
+ res.status(500).json({ error: "Database connection failed" });
}
});
-// Download multiple files in base64 format
-app.get("/download/files-base64", async (_req, res) => {
- try {
- const cursor = bucket.find();
- const files = await cursor.toArray();
-
- const filesData = await Promise.all(
- files.map((file) => {
- return new Promise((resolve, _reject) => {
- bucket.openDownloadStream(file._id).pipe(
- (() => {
- const chunks = [];
- return new Transform({
- // transform method will
- transform(chunk, encoding, done) {
- chunks.push(chunk);
- done();
- },
- flush(done) {
- const fbuf = Buffer.concat(chunks);
- const fileBase64String = fbuf.toString("base64");
- resolve(fileBase64String);
- done();
- },
- });
- })()
- );
- });
- })
- );
- res.status(200).json(filesData);
- } catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: `Unable to retrieve files`, error },
- });
+// Serve the main HTML file for the root route
+app.get("/", (req, res) => {
+ const indexPath = path.join(__dirname, "public", "index.html");
+ if (fs.existsSync(indexPath)) {
+ res.sendFile(indexPath);
+ } else {
+ res.json({ message: "File Upload API with MongoDB - Server is running" });
}
});
-// Rename a file
-app.put("/rename/file/:fileId", async (req, res) => {
- try {
- const { fileId } = req.params;
- const { filename } = req.body;
- await bucket.rename(new mongoose.Types.ObjectId(fileId), filename);
- res.status(200).json({ text: "File renamed successfully !" });
- } catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: `Unable to rename file`, error },
- });
- }
-});
+// API Routes
+app.use("/", fileRoutes);
-// Delete a file
-app.delete("/delete/file/:fileId", async (req, res) => {
- try {
- const { fileId } = req.params;
- await bucket.delete(new mongoose.Types.ObjectId(fileId));
- res.status(200).json({ text: "File deleted successfully !" });
- } catch (error) {
- console.log(error);
- res.status(400).json({
- error: { text: `Unable to delete file`, error },
- });
- }
-});
+// Export the Express app for Vercel
+module.exports = app;
-// Server listening on port 3000 for incoming requests
-const port = process.env.PORT || 3000;
-app.listen(port, () => {
- console.log(`Server listening on port ${port}`);
-});
+// Server listening on port 3001 for incoming requests (for local development)
+if (require.main === module) {
+ const port = process.env.PORT || 3001;
+ app.listen(port, () => {
+ console.log(`Server listening on port ${port}`);
+ console.log(`Visit http://localhost:${port} to access the UI`);
+ });
+}
diff --git a/models/File.js b/models/File.js
new file mode 100644
index 0000000..acb2472
--- /dev/null
+++ b/models/File.js
@@ -0,0 +1,49 @@
+const mongoose = require("mongoose");
+
+class FileModel {
+ constructor() {
+ this.bucket = null;
+ this.initBucket();
+ }
+
+ initBucket() {
+ mongoose.connection.on("connected", () => {
+ this.bucket = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
+ bucketName: "uploads",
+ });
+ });
+ }
+
+ getBucket() {
+ return this.bucket;
+ }
+
+ async getAllFiles() {
+ return await this.bucket.find().toArray();
+ }
+
+ async getFileById(fileId) {
+ return await this.bucket
+ .find({ _id: new mongoose.Types.ObjectId(fileId) })
+ .toArray();
+ }
+
+ async renameFile(fileId, newFilename) {
+ await this.bucket.rename(
+ new mongoose.Types.ObjectId(fileId),
+ newFilename
+ );
+ }
+
+ async deleteFile(fileId) {
+ await this.bucket.delete(new mongoose.Types.ObjectId(fileId));
+ }
+
+ openDownloadStream(fileId) {
+ return this.bucket.openDownloadStream(
+ new mongoose.Types.ObjectId(fileId)
+ );
+ }
+}
+
+module.exports = new FileModel();
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..0bc6d57
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,100 @@
+
+
+
+
+
+ File Upload with MongoDB
+
+
+
+
+
+
+
+
+
+
Upload Files
+
+
+
Single File Upload
+
+
+
+
Multiple Files Upload
+
+
+
+
+
+
+
+
+
+
Bulk Download Options
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Filename |
+ Size |
+ Type |
+ Upload Date |
+ Actions |
+
+
+
+
+ | No files uploaded yet |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Rename File
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/script.js b/public/script.js
new file mode 100644
index 0000000..7c83773
--- /dev/null
+++ b/public/script.js
@@ -0,0 +1,319 @@
+// API Base URL
+const API_BASE = '';
+
+// Global variables
+let currentFileIdForRename = null;
+
+// DOM Elements
+const singleUploadForm = document.getElementById('singleUploadForm');
+const multipleUploadForm = document.getElementById('multipleUploadForm');
+const filesTableBody = document.getElementById('filesTableBody');
+const refreshBtn = document.getElementById('refreshBtn');
+const downloadZipBtn = document.getElementById('downloadZipBtn');
+const downloadBase64Btn = document.getElementById('downloadBase64Btn');
+const toast = document.getElementById('toast');
+const renameModal = document.getElementById('renameModal');
+const newFilenameInput = document.getElementById('newFilename');
+const confirmRenameBtn = document.getElementById('confirmRename');
+const cancelRenameBtn = document.getElementById('cancelRename');
+
+// Toast Notification
+function showToast(message, type = 'info') {
+ toast.textContent = message;
+ toast.className = `toast ${type} show`;
+ setTimeout(() => {
+ toast.classList.remove('show');
+ }, 3000);
+}
+
+// Format file size
+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 Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
+}
+
+// Format date
+function formatDate(dateString) {
+ const date = new Date(dateString);
+ return date.toLocaleString();
+}
+
+// Load all files
+async function loadFiles() {
+ try {
+ const response = await fetch(`${API_BASE}/files`);
+ const files = await response.json();
+
+ if (files.length === 0) {
+ filesTableBody.innerHTML = '| No files uploaded yet |
';
+ return;
+ }
+
+ filesTableBody.innerHTML = files.map(file => `
+
+ | ${file.filename} |
+ ${formatFileSize(file.length)} |
+ ${file.contentType || 'N/A'} |
+ ${formatDate(file.uploadDate)} |
+
+
+
+
+
+
+ |
+
+ `).join('');
+ } catch (error) {
+ console.error('Error loading files:', error);
+ showToast('Failed to load files', 'error');
+ }
+}
+
+// Upload single file
+singleUploadForm.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const formData = new FormData();
+ const fileInput = document.getElementById('singleFile');
+ const submitBtn = singleUploadForm.querySelector('button[type="submit"]');
+
+ formData.append('file', fileInput.files[0]);
+
+ // Disable button during upload
+ submitBtn.disabled = true;
+ submitBtn.innerHTML = ' Uploading...';
+
+ try {
+ const response = await fetch(`${API_BASE}/upload/file`, {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ showToast(result.text, 'success');
+ fileInput.value = '';
+ loadFiles();
+ } else {
+ showToast(result.error.text, 'error');
+ }
+ } catch (error) {
+ console.error('Error uploading file:', error);
+ showToast('Failed to upload file', 'error');
+ } finally {
+ // Re-enable button
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Upload File';
+ }
+});
+
+// Upload multiple files
+multipleUploadForm.addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const formData = new FormData();
+ const filesInput = document.getElementById('multipleFiles');
+ const submitBtn = multipleUploadForm.querySelector('button[type="submit"]');
+
+ for (let file of filesInput.files) {
+ formData.append('files', file);
+ }
+
+ // Disable button during upload
+ submitBtn.disabled = true;
+ submitBtn.innerHTML = ' Uploading...';
+
+ try {
+ const response = await fetch(`${API_BASE}/upload/files`, {
+ method: 'POST',
+ body: formData
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ showToast(result.text, 'success');
+ filesInput.value = '';
+ loadFiles();
+ } else {
+ showToast(result.error.text, 'error');
+ }
+ } catch (error) {
+ console.error('Error uploading files:', error);
+ showToast('Failed to upload files', 'error');
+ } finally {
+ // Re-enable button
+ submitBtn.disabled = false;
+ submitBtn.textContent = 'Upload Files';
+ }
+});
+
+// Download single file
+async function downloadFile(fileId, filename) {
+ try {
+ const response = await fetch(`${API_BASE}/download/files/${fileId}`);
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast('File downloaded successfully', 'success');
+ } catch (error) {
+ console.error('Error downloading file:', error);
+ showToast('Failed to download file', 'error');
+ }
+}
+
+// Download all files as ZIP
+downloadZipBtn.addEventListener('click', async () => {
+ try {
+ const response = await fetch(`${API_BASE}/download/files-zip`);
+ if (!response.ok) {
+ const error = await response.json();
+ showToast(error.error.text, 'error');
+ return;
+ }
+ const blob = await response.blob();
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'files.zip';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast('All files downloaded as ZIP', 'success');
+ } catch (error) {
+ console.error('Error downloading ZIP:', error);
+ showToast('Failed to download ZIP', 'error');
+ }
+});
+
+// Download all files as Base64
+downloadBase64Btn.addEventListener('click', async () => {
+ try {
+ const response = await fetch(`${API_BASE}/download/files-base64`);
+ if (!response.ok) {
+ const error = await response.json();
+ showToast(error.error.text, 'error');
+ return;
+ }
+ const filesData = await response.json();
+
+ // Create a JSON file with all base64 data
+ const dataStr = JSON.stringify(filesData, null, 2);
+ const blob = new Blob([dataStr], { type: 'application/json' });
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'files-base64.json';
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ showToast('Base64 files downloaded successfully', 'success');
+ } catch (error) {
+ console.error('Error downloading base64:', error);
+ showToast('Failed to download base64 files', 'error');
+ }
+});
+
+// Open rename modal
+function openRenameModal(fileId, currentFilename) {
+ currentFileIdForRename = fileId;
+ newFilenameInput.value = currentFilename;
+ renameModal.classList.add('show');
+ newFilenameInput.focus();
+}
+
+// Close rename modal
+function closeRenameModal() {
+ renameModal.classList.remove('show');
+ currentFileIdForRename = null;
+ newFilenameInput.value = '';
+}
+
+// Confirm rename
+confirmRenameBtn.addEventListener('click', async () => {
+ const newFilename = newFilenameInput.value.trim();
+ if (!newFilename) {
+ showToast('Please enter a filename', 'error');
+ return;
+ }
+
+ try {
+ const response = await fetch(`${API_BASE}/rename/file/${currentFileIdForRename}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ filename: newFilename })
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ showToast(result.text, 'success');
+ closeRenameModal();
+ loadFiles();
+ } else {
+ showToast(result.error.text, 'error');
+ }
+ } catch (error) {
+ console.error('Error renaming file:', error);
+ showToast('Failed to rename file', 'error');
+ }
+});
+
+// Cancel rename
+cancelRenameBtn.addEventListener('click', closeRenameModal);
+
+// Close modal on outside click
+renameModal.addEventListener('click', (e) => {
+ if (e.target === renameModal) {
+ closeRenameModal();
+ }
+});
+
+// Delete file
+async function deleteFile(fileId) {
+ if (!confirm('Are you sure you want to delete this file?')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(`${API_BASE}/delete/file/${fileId}`, {
+ method: 'DELETE'
+ });
+
+ const result = await response.json();
+ if (response.ok) {
+ showToast(result.text, 'success');
+ loadFiles();
+ } else {
+ showToast(result.error.text, 'error');
+ }
+ } catch (error) {
+ console.error('Error deleting file:', error);
+ showToast('Failed to delete file', 'error');
+ }
+}
+
+// Refresh files
+refreshBtn.addEventListener('click', () => {
+ loadFiles();
+ showToast('Files refreshed', 'info');
+});
+
+// Load files on page load
+document.addEventListener('DOMContentLoaded', loadFiles);
diff --git a/public/styles.css b/public/styles.css
new file mode 100644
index 0000000..2f6a0e7
--- /dev/null
+++ b/public/styles.css
@@ -0,0 +1,330 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ min-height: 100vh;
+ padding: 20px;
+}
+
+.container {
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+header {
+ text-align: center;
+ color: white;
+ margin-bottom: 30px;
+ padding: 20px;
+}
+
+header h1 {
+ font-size: 2.5rem;
+ margin-bottom: 10px;
+}
+
+header p {
+ font-size: 1.1rem;
+ opacity: 0.9;
+}
+
+.card {
+ background: white;
+ border-radius: 12px;
+ padding: 25px;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+ margin-bottom: 25px;
+}
+
+/* Upload Section */
+.upload-section .upload-options {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ margin-top: 20px;
+}
+
+.upload-option {
+ padding: 20px;
+ border: 2px dashed #667eea;
+ border-radius: 8px;
+ background: #f8f9ff;
+}
+
+.upload-option h3 {
+ color: #667eea;
+ margin-bottom: 15px;
+ font-size: 1.1rem;
+}
+
+.upload-option input[type="file"] {
+ display: block;
+ width: 100%;
+ margin-bottom: 15px;
+ padding: 10px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ background: white;
+}
+
+/* Buttons */
+.btn {
+ padding: 12px 24px;
+ border: none;
+ border-radius: 6px;
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+.btn-primary:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+}
+
+.btn-secondary {
+ background: #6c757d;
+ color: white;
+}
+
+.btn-secondary:hover {
+ background: #5a6268;
+ transform: translateY(-2px);
+}
+
+.btn-success {
+ background: #28a745;
+ color: white;
+ padding: 8px 16px;
+ font-size: 0.9rem;
+}
+
+.btn-danger {
+ background: #dc3545;
+ color: white;
+ padding: 8px 16px;
+ font-size: 0.9rem;
+}
+
+.btn-warning {
+ background: #ffc107;
+ color: #000;
+ padding: 8px 16px;
+ font-size: 0.9rem;
+}
+
+.btn-info {
+ background: #17a2b8;
+ color: white;
+ padding: 8px 16px;
+ font-size: 0.9rem;
+}
+
+.btn-small {
+ padding: 8px 16px;
+ font-size: 0.9rem;
+}
+
+/* Download Section */
+.download-section .download-options {
+ display: flex;
+ gap: 15px;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+
+/* Files Section */
+.files-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.files-header h2 {
+ color: #333;
+}
+
+.table-container {
+ overflow-x: auto;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ color: white;
+}
+
+th {
+ padding: 15px;
+ text-align: left;
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 0.9rem;
+ letter-spacing: 0.5px;
+}
+
+td {
+ padding: 15px;
+ border-bottom: 1px solid #eee;
+}
+
+tbody tr:hover {
+ background: #f8f9ff;
+}
+
+.no-files {
+ text-align: center;
+ color: #999;
+ font-style: italic;
+ padding: 40px !important;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 8px;
+ flex-wrap: wrap;
+}
+
+/* Toast Notification */
+.toast {
+ position: fixed;
+ bottom: 30px;
+ right: 30px;
+ padding: 15px 25px;
+ background: #333;
+ color: white;
+ border-radius: 8px;
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
+ transform: translateX(400px);
+ transition: transform 0.3s ease;
+ z-index: 1000;
+ max-width: 350px;
+}
+
+.toast.show {
+ transform: translateX(0);
+}
+
+.toast.success {
+ background: #28a745;
+}
+
+.toast.error {
+ background: #dc3545;
+}
+
+.toast.info {
+ background: #17a2b8;
+}
+
+/* Modal */
+.modal {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ align-items: center;
+ justify-content: center;
+}
+
+.modal.show {
+ display: flex;
+}
+
+.modal-content {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ max-width: 400px;
+ width: 90%;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
+}
+
+.modal-content h3 {
+ margin-bottom: 20px;
+ color: #333;
+}
+
+.modal-content input {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ font-size: 1rem;
+ margin-bottom: 20px;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ header h1 {
+ font-size: 1.8rem;
+ }
+
+ .upload-options {
+ grid-template-columns: 1fr !important;
+ }
+
+ .download-options {
+ flex-direction: column;
+ }
+
+ .action-buttons {
+ flex-direction: column;
+ }
+
+ table {
+ font-size: 0.85rem;
+ }
+
+ th, td {
+ padding: 10px;
+ }
+}
+
+/* Loading Spinner */
+.loading {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 3px solid rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ border-top-color: white;
+ animation: spin 1s ease-in-out infinite;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+.btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
diff --git a/routes/fileRoutes.js b/routes/fileRoutes.js
new file mode 100644
index 0000000..31250fa
--- /dev/null
+++ b/routes/fileRoutes.js
@@ -0,0 +1,35 @@
+const express = require("express");
+const { upload } = require("../utils/upload");
+const fileController = require("../controllers/fileController");
+
+const router = express.Router();
+
+// Upload a single file
+router.post("/upload/file", (req, res, next) => {
+ upload().single("file")(req, res, next);
+}, fileController.uploadFile);
+
+// Upload multiple files
+router.post("/upload/files", (req, res, next) => {
+ upload().array("files")(req, res, next);
+}, fileController.uploadFiles);
+
+// Get all files
+router.get("/files", fileController.getAllFiles);
+
+// Download a file by id
+router.get("/download/files/:fileId", fileController.downloadFile);
+
+// Download multiple files in a zip file
+router.get("/download/files-zip", fileController.downloadFilesZip);
+
+// Download multiple files in base64 format
+router.get("/download/files-base64", fileController.downloadFilesBase64);
+
+// Rename a file
+router.put("/rename/file/:fileId", fileController.renameFile);
+
+// Delete a file
+router.delete("/delete/file/:fileId", fileController.deleteFile);
+
+module.exports = router;
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..9b6a863
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,9 @@
+{
+ "version": 2,
+ "rewrites": [
+ {
+ "source": "/(.*)",
+ "destination": "/index.js"
+ }
+ ]
+}