├── .env.example ├── .gitignore ├── FIREBASE.md ├── LICENSE ├── README.MD ├── config └── firebase.js ├── controllers └── fileController.js ├── firebase.json.example ├── middlewares ├── checkFileSize.js └── fileUpload.js ├── package.json ├── public ├── 404.html ├── 500.html ├── favicon.ico ├── favicon.png ├── index.html ├── index.js └── style.css ├── routes └── fileRoutes.js ├── server.js ├── uploads └── pezraq.jpg ├── utils └── helpers.js └── vercel.json /.env.example: -------------------------------------------------------------------------------- 1 | # System environment variables 2 | PORT=3000 3 | PUBLIC_URL=http://localhost:3000 4 | STORAGE=local # Options: 'local', 'firebase' 5 | 6 | # Firebase environment variables (if using Firebase) 7 | FIREBASE_STORAGE_BUCKET=gs://your-firebase-bucket 8 | PUBLIC_URL_FIREBASE=https://firebasestorage.googleapis.com/v0/b/your-firebase-bucket/o -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | .env 4 | firebase.json 5 | -------------------------------------------------------------------------------- /FIREBASE.md: -------------------------------------------------------------------------------- 1 | # Firebase Storage Setup for CDN File Uploader 2 | 3 | This guide explains how to configure Firebase as a storage solution for the **CDN File Uploader**. By following these steps, you'll be able to store your uploaded files on Firebase Storage. 4 | 5 | ## Prerequisites 6 | 7 | 1. **Firebase Account**: You need a Firebase account. If you don't have one, you can sign up at [https://firebase.google.com/](https://firebase.google.com/). 8 | 2. **Firebase Project**: A Firebase project should be set up in the Firebase Console. 9 | 10 | ## Steps to Set Up Firebase Storage 11 | 12 | ### 1. Create a Firebase Project 13 | 14 | 1. Go to the [Firebase Console](https://console.firebase.google.com/). 15 | 2. Click on **Add Project** and follow the steps to create a new Firebase project. 16 | 3. Once the project is created, you will be directed to the Firebase project dashboard. 17 | 18 | ### 2. Set Up Firebase Storage 19 | 20 | 1. In the Firebase Console, go to the **Storage** section. 21 | 2. Click on **Get Started** to enable Firebase Storage for your project. 22 | 3. Set your storage rules according to your requirements. For example, to allow unauthenticated uploads, you can set the rules to: 23 | 24 | ```json 25 | service firebase.storage { 26 | match /b/{bucket}/o { 27 | match /{allPaths=**} { 28 | allow read, write: if true; 29 | } 30 | } 31 | } 32 | ``` 33 | 34 | **Note**: For production, it is recommended to secure your storage rules properly. You can update them to restrict access based on authentication or other criteria. 35 | 36 | ### 3. Generate a Firebase Admin SDK Key 37 | 38 | 1. In the Firebase Console, go to **Project Settings**. 39 | 2. Select the **Service Accounts** tab. 40 | 3. Click **Generate New Private Key**. This will download a JSON file containing your Firebase credentials. 41 | 4. Rename this file to `firebase.json` and place it in the root of your project. 42 | 43 | ### 4. Update `.env` with Firebase Environment Variables 44 | 45 | Update your `.env` file with the necessary Firebase environment variables. Make sure you add the following: 46 | 47 | ```bash 48 | STORAGE=firebase 49 | FIREBASE_STORAGE_BUCKET=gs://your-firebase-bucket 50 | PUBLIC_URL_FIREBASE=https://firebasestorage.googleapis.com/v0/b/your-firebase-bucket/o 51 | ``` 52 | 53 | - Replace `gs://your-firebase-bucket` with the **bucket name** from the Firebase Storage console. It should look something like `gs://your-project-id.appspot.com`. 54 | - `PUBLIC_URL_FIREBASE` is the base URL for accessing your files via Firebase Storage. This URL will be used to build the public URL for accessing uploaded files. 55 | 56 | ### 5. Install Firebase Admin SDK 57 | 58 | You need to install the Firebase Admin SDK to enable Firebase functionality in your Node.js app. 59 | 60 | Run the following command: 61 | 62 | ```bash 63 | npm install firebase-admin 64 | ``` 65 | 66 | ### 6. Initialize Firebase in `config/firebase.js` 67 | 68 | To initialize Firebase in your project, create a file named `config/firebase.js` where you will set up Firebase using the credentials and bucket you configured. 69 | 70 | 1. Create a new file in `config/` named `firebase.js`: 71 | 72 | ```javascript 73 | // config/firebase.js 74 | const admin = require("firebase-admin"); 75 | const fireCreds = require("../firebase.json"); 76 | 77 | let bucket = null; 78 | 79 | // Cek apakah aplikasi Firebase sudah diinisialisasi 80 | if (!admin.apps.length) { 81 | admin.initializeApp({ 82 | credential: admin.credential.cert(fireCreds), 83 | storageBucket: process.env.FIREBASE_STORAGE_BUCKET, 84 | }); 85 | } 86 | 87 | bucket = admin.storage().bucket(); 88 | 89 | module.exports = bucket; 90 | ``` 91 | 92 | 2. This file will initialize Firebase if the `STORAGE=firebase` variable is set in the `.env` file and export the `bucket` object for further use in file upload logic. 93 | 94 | 3. Import the Firebase bucket in your route or controller where you handle file uploads: 95 | 96 | ```javascript 97 | // Example: controllers/fileController.js 98 | 99 | const bucket = require("../config/firebase.js"); 100 | 101 | if (process.env.STORAGE === 'firebase') { 102 | // Your logic for uploading files to Firebase 103 | } 104 | ``` 105 | 106 | ### 7. Upload Files to Firebase Storage 107 | 108 | Once Firebase is set up, the application will automatically store files in Firebase Storage if `STORAGE=firebase` is set in the `.env` file. When a file is uploaded, it will be uploaded to Firebase Storage and will be accessible via a URL generated by Firebase. 109 | 110 | For example, you can access the uploaded file at: 111 | 112 | ``` 113 | https://firebasestorage.googleapis.com/v0/b/your-firebase-bucket/o/your-file-name?alt=media 114 | ``` 115 | 116 | This URL can also be built dynamically in the app using the `PUBLIC_URL_FIREBASE` variable. 117 | 118 | ### 8. Deploy to Vercel (Optional) 119 | 120 | If you want to deploy your project to Vercel, make sure to add the necessary environment variables in the **Vercel dashboard** under the **Environment Variables** section. 121 | 122 | The required environment variables are: 123 | 124 | - `STORAGE=firebase` 125 | - `FIREBASE_STORAGE_BUCKET` 126 | - `PUBLIC_URL_FIREBASE` 127 | - You can also upload the `firebase.json` key file to Vercel securely. 128 | 129 | ## Summary 130 | 131 | After following the steps outlined above, your application will be configured to store uploaded files in Firebase Storage. Make sure to test the functionality locally before deploying the project to production. 132 | 133 | For further assistance, you can refer to the official [Firebase Storage documentation](https://firebase.google.com/docs/storage). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Romi Muharom 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # CDN File Uploader 2 | 3 | A file uploader built with Node.js and Express that supports multiple file types, including images, documents, videos, presentations, and spreadsheets. The project includes MIME type validation to ensure only specific file types are uploaded. It also supports multiple storage options, including local storage, and Firebase Storage. 4 | 5 | ## Features 6 | - Supports file upload for images, videos, gifs, documents (PDF, DOC, DOCX), presentations (PPT, PPTX), and spreadsheets (XLS, XLSX). 7 | - MIME type and extension validation for uploaded files. 8 | - Supports three different storage options: **Local**, and **Firebase**. 9 | - Configurable via environment variables. 10 | - **Vercel-ready**: This project can be easily deployed to Vercel for hosting. 11 | 12 | ## Tech Stack 13 | - **Node.js** 14 | - **Express.js** 15 | - **Multer**: For handling file uploads. 16 | - **fs (File System)**: For file handling in the local filesystem. 17 | - **Firebase Admin SDK**: For Firebase storage (if using Firebase). 18 | - **dotenv**: For environment variable management. 19 | 20 | ## Installation 21 | 22 | 1. Clone this repository: 23 | ```bash 24 | git clone https://github.com/Leuthra/cdn-uploader.git 25 | cd cdn-uploader 26 | ``` 27 | 28 | 2. Install dependencies: 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | 3. Create a `.env` file to configure environment variables: 34 | ```bash 35 | touch .env 36 | ``` 37 | 38 | **Example `.env` file:** 39 | ```bash 40 | # System environment variables 41 | PORT=3000 42 | PUBLIC_URL=http://localhost:3000 43 | STORAGE=local # Options: 'local', 'firebase' 44 | 45 | # Firebase environment variables (if using Firebase) 46 | FIREBASE_STORAGE_BUCKET=gs://your-firebase-bucket 47 | PUBLIC_URL_FIREBASE=https://firebasestorage.googleapis.com/v0/b/your-firebase-bucket/o 48 | ``` 49 | 50 | 4. Start the server: 51 | ```bash 52 | npm start 53 | ``` 54 | 55 | ## Environment Variables 56 | 57 | | Variable Name | Description | Default Value | 58 | | ---------------------------- | -------------------------------------------------------- | ---------------- | 59 | | `PORT` | Port number for the server | `3000` | 60 | | `PUBLIC_URL` | Public URL for accessing the app | `http://localhost:3000` | 61 | | `STORAGE` | Storage option (`local`, `firebase`, or `drive`) | `local` | 62 | | `FIREBASE_STORAGE_BUCKET` | Firebase storage bucket name (only if using Firebase) | N/A | 63 | | `PUBLIC_URL_FIREBASE` | Public URL template for Firebase storage | N/A | 64 | 65 | ## Storage Options 66 | 67 | This project supports three different storage options. You can choose the storage method by setting the `STORAGE` variable in your `.env` file to either `local`, `firebase`. 68 | 69 | ### 1. Local Storage 70 | Files are stored on the server's filesystem in the `uploads/` directory, which acts as local storage. Files will be served via the `/file` route, even if stored locally. 71 | 72 | ### 2. Firebase Storage 73 | To use Firebase Storage, set the `STORAGE` variable to `firebase` and configure the Firebase-specific environment variables such as `FIREBASE_STORAGE_BUCKET`. Uploaded files will still be accessed using the `/file` route. 74 | 75 | You can find more detailed setup instructions in the [**FIREBASE.md**](./FIREBASE.md) file. 76 | 77 | ## Usage 78 | 79 | 1. **File Upload**: 80 | Send a `POST` request to `/upload` with a file using the form field `fileInput`. The file will be saved in the specified storage method (local, Firebase, or Google Drive). 81 | 82 | Example with **cURL**: 83 | ```bash 84 | curl -X POST http://localhost:3000/upload -F "fileInput=@/path/to/your/file.jpg" 85 | ``` 86 | 87 | 2. **Allowed File Types**: 88 | The following file types are allowed for upload: 89 | - Images: `.jpeg`, `.jpg`, `.png`, `.gif` 90 | - Documents: `.pdf`, `.doc`, `.docx` 91 | - Presentations: `.ppt`, `.pptx` 92 | - Spreadsheets: `.xls`, `.xlsx` 93 | - Videos: `.mp4`, `.avi`, `.mov`, `.mkv` 94 | 95 | 3. **Access Uploaded Files**: 96 | All uploaded files, regardless of the storage method (local, Firebase, or Google Drive), will be accessible through the `/file` route. For example: 97 | 98 | - **Local Storage**: `http://localhost:3000/file/your-file-name` 99 | - **Firebase Storage**: `http://localhost:3000/file/your-file-name` 100 | - **Google Drive**: `http://localhost:3000/file/your-file-name` 101 | 102 | ## Hosting on Vercel 103 | 104 | This project is fully compatible with **Vercel**. You can easily deploy it by pushing the repository to a GitHub repository and connecting it with Vercel. The configuration file `vercel.json` is included to handle deployment. 105 | 106 | Once deployed, all files will be accessible via the `/file` route on your Vercel domain. The **uploads/** folder is used for local storage when the `STORAGE` is set to `local`. 107 | 108 | ### Steps to Deploy on Vercel: 109 | 110 | 1. Push your project to GitHub or GitLab. 111 | 2. Go to [Vercel](https://vercel.com/), sign in, and create a new project by importing your repository. 112 | 3. Set the required environment variables in the Vercel project settings. 113 | 4. Deploy the project. 114 | 115 | ## Project Structure 116 | 117 | ``` 118 | . 119 | ├── config/ # Configuration files (e.g., Firebase) 120 | ├── controllers/ # Route controllers for handling requests 121 | ├── middlewares/ # Custom middleware (e.g., IP handling, validation) 122 | ├── public/ # Public assets (e.g., HTML, CSS, JS files) 123 | ├── routes/ # Express routes 124 | ├── uploads/ # Directory where uploaded files are stored (local storage only) 125 | ├── utils/ # Utility functions (e.g., helpers for file handling) 126 | ├── .env # Environment variables configuration (to be created) 127 | ├── .gitignore # Git ignore file 128 | ├── firebase.json # Firebase configuration (if applicable) 129 | ├── package.json # Node.js dependencies and scripts 130 | ├── README.md # Project documentation 131 | ├── server.js # Main server file 132 | ├── vercel.json # Vercel deployment configuration 133 | ├── FIREBASE.md # Firebase setup instructions 134 | ``` 135 | 136 | ## Dependencies 137 | 138 | - **express**: Web framework for Node.js. 139 | - **multer**: Middleware for handling `multipart/form-data`, used for uploading files. 140 | - **fs**: Node.js built-in module for interacting with the file system. 141 | - **firebase-admin**: For Firebase integration (if using Firebase storage). 142 | - **googleapis**: For Google Drive integration (if applicable). 143 | - **dotenv**: For environment variable management. 144 | 145 | ## License 146 | 147 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /config/firebase.js: -------------------------------------------------------------------------------- 1 | const admin = require("firebase-admin"); 2 | const fireCreds = require("../firebase.json"); 3 | 4 | let bucket = null; 5 | 6 | if (!admin.apps.length) { 7 | admin.initializeApp({ 8 | credential: admin.credential.cert(fireCreds), 9 | storageBucket: process.env.FIREBASE_STORAGE_BUCKET, 10 | }); 11 | } 12 | 13 | bucket = admin.storage().bucket(); 14 | 15 | module.exports = bucket; 16 | -------------------------------------------------------------------------------- /controllers/fileController.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | const bucket = require("../config/firebase.js"); 4 | const { formatDate } = require("../utils/helpers.js"); 5 | const PublicUrl = process.env.PUBLIC_URL || "http://localhost:3000"; 6 | const uploadDirectory = path.join(__dirname, "..", "uploads"); 7 | const author = "Romi Muharom"; 8 | const status = "success"; 9 | 10 | if (!bucket) { 11 | return res.status(500).send({ error: "Firebase storage not initialized" }); 12 | } 13 | 14 | async function uploadFile(req, res) { 15 | if (!req.file) { 16 | return res.status(400).send({ error: "No file uploaded." }); 17 | } 18 | 19 | if (process.env.STORAGE === "firebase") { 20 | const blob = bucket.file(req.file.originalname); 21 | const blobStream = blob.createWriteStream({ 22 | metadata: { 23 | contentType: req.file.mimetype, 24 | }, 25 | }); 26 | 27 | blobStream.on("error", (err) => { 28 | console.error("Error uploading to Firebase:", err); 29 | res.status(500).send({ error: "Error uploading file" }); 30 | }); 31 | 32 | blobStream.on("finish", () => { 33 | const publicUrl = `${PublicUrl}/file/${blob.name}`; 34 | res.send({ 35 | Developer: author, 36 | file_name: req.file.originalname, 37 | file_size: (req.file.size / 1024).toFixed(2) + " KB", 38 | url_response: publicUrl, 39 | }); 40 | }); 41 | 42 | blobStream.end(req.file.buffer); 43 | } else { 44 | const fileSize = (req.file.size / 1024).toFixed(2) + " KB"; 45 | const fileUrl = `${PublicUrl}/file/${req.file.filename}`; 46 | 47 | res.send({ 48 | Developer: author, 49 | file_name: req.file.filename, 50 | file_size: fileSize, 51 | url_response: fileUrl, 52 | }); 53 | } 54 | } 55 | 56 | // Get all files handler 57 | async function getAllFiles(req, res) { 58 | if (process.env.STORAGE === "local") { 59 | try { 60 | const files = fs.readdirSync(uploadDirectory).map((file) => { 61 | const filePath = path.join(uploadDirectory, file); 62 | const stats = fs.statSync(filePath); 63 | return { 64 | title: file, 65 | size: (stats.size / (1024 * 1024)).toFixed(2) + " MB", 66 | link: `${PublicUrl}/file/${file}`, 67 | createdTime: formatDate(stats.mtime), 68 | }; 69 | }); 70 | res.json({ author, status, files }); 71 | } catch (error) { 72 | res 73 | .status(500) 74 | .send({ error: "Error fetching files from local storage" }); 75 | } 76 | } else if (process.env.STORAGE === "firebase") { 77 | try { 78 | const [files] = await bucket.getFiles(); 79 | if (!files.length) { 80 | return res.status(200).json({ files: [] }); 81 | } 82 | 83 | const firebaseFiles = files.map((file) => ({ 84 | title: file.name, 85 | size: file.metadata.size 86 | ? (file.metadata.size / (1024 * 1024)).toFixed(2) + " MB" 87 | : "Unknown size", 88 | link: `${PublicUrl}/file/${file.name}`, 89 | createdTime: formatDate(file.metadata.timeCreated), 90 | })); 91 | 92 | res.json({ author, status, files: firebaseFiles }); 93 | } catch (error) { 94 | res.status(500).send({ error: "Error fetching files from Firebase" }); 95 | } 96 | } else { 97 | res.status(400).send({ error: "Invalid storage option" }); 98 | } 99 | } 100 | 101 | async function getFileHistory(req, res) { 102 | try { 103 | let latestFile; 104 | let fileSize = 0; 105 | let publicUrl; 106 | 107 | if (process.env.STORAGE === "firebase") { 108 | const [files] = await bucket.getFiles(); 109 | if (files.length === 0) { 110 | return res.status(200).json({ message: "No files available" }); 111 | } 112 | const sortedFiles = files.sort((a, b) => { 113 | return ( 114 | new Date(b.metadata.timeCreated) - new Date(a.metadata.timeCreated) 115 | ); 116 | }); 117 | 118 | latestFile = sortedFiles[0]; 119 | fileSize = latestFile.metadata.size 120 | ? (latestFile.metadata.size / (1024 * 1024)).toFixed(2) + " MB" 121 | : "Unknown size"; 122 | 123 | publicUrl = `${PublicUrl}/file/${latestFile.name}`; 124 | } else if (process.env.STORAGE === "local") { 125 | const files = fs.readdirSync(uploadDirectory); 126 | if (files.length === 0) { 127 | return res.status(200).json({ message: "No files available" }); 128 | } 129 | const sortedFiles = files.sort((a, b) => { 130 | const aTime = fs.statSync(path.join(uploadDirectory, a)).mtime; 131 | const bTime = fs.statSync(path.join(uploadDirectory, b)).mtime; 132 | return bTime - aTime; 133 | }); 134 | 135 | latestFile = sortedFiles[0]; 136 | const filePath = path.join(uploadDirectory, latestFile); 137 | const stats = fs.statSync(filePath); 138 | fileSize = (stats.size / (1024 * 1024)).toFixed(2) + " MB"; 139 | 140 | publicUrl = `${PublicUrl}/file/${latestFile}`; 141 | } 142 | 143 | const jsonResponse = { 144 | author, 145 | status, 146 | fileName: latestFile.name || latestFile, 147 | url: publicUrl, 148 | size: fileSize, 149 | }; 150 | 151 | res.setHeader("Content-Type", "application/json"); 152 | res.send(JSON.stringify(jsonResponse, null, 2)); 153 | } catch (err) { 154 | res.status(500).json({ error: "Failed to fetch file history" }); 155 | } 156 | } 157 | 158 | async function getFileByProxy(req, res) { 159 | const filename = req.params.filename; 160 | console.log("Requested file:", filename); 161 | 162 | try { 163 | if (process.env.STORAGE === "firebase") { 164 | const file = bucket.file(filename); 165 | const [exists] = await file.exists(); 166 | console.log("File exists in Firebase:", exists); 167 | 168 | if (!exists) { 169 | return res.status(404).send("File not found in Firebase"); 170 | } 171 | 172 | const [metadata] = await file.getMetadata(); 173 | const contentType = metadata.contentType || "application/octet-stream"; 174 | res.setHeader("Content-Type", contentType); 175 | 176 | const readStream = file.createReadStream(); 177 | readStream.pipe(res); 178 | 179 | readStream.on("error", (err) => { 180 | console.error("Error fetching file from Firebase:", err); 181 | res.status(500).send("Error fetching file from Firebase"); 182 | }); 183 | } 184 | 185 | else if (process.env.STORAGE === "local") { 186 | const filePath = path.join(uploadDirectory, filename); 187 | console.log("File path in local storage:", filePath); 188 | 189 | if (fs.existsSync(filePath)) { 190 | console.log("Serving file from local storage..."); 191 | res.sendFile(filePath); 192 | } else { 193 | console.log("File not found in local storage"); 194 | res.status(404).send("File not found in local storage"); 195 | } 196 | } else { 197 | res.status(400).send({ error: "Invalid storage option" }); 198 | } 199 | } catch (err) { 200 | console.error("Error fetching file:", err); 201 | res.status(500).send("Error fetching file from storage"); 202 | } 203 | } 204 | 205 | async function getAllFilesWithTotalSize(req, res) { 206 | try { 207 | let allFiles = []; 208 | let totalSize = 0; 209 | 210 | if (process.env.STORAGE === "firebase") { 211 | const [files] = await bucket.getFiles(); 212 | files.forEach((file) => { 213 | const fileSize = file.metadata.size 214 | ? parseInt(file.metadata.size, 10) 215 | : 0; 216 | allFiles.push({ 217 | fileName: file.name, 218 | size: fileSize, 219 | link: `${PublicUrl}/file/${file.name}`, 220 | }); 221 | totalSize += fileSize; 222 | }); 223 | } 224 | 225 | else if (process.env.STORAGE === "local") { 226 | const files = fs.readdirSync(uploadDirectory); 227 | files.forEach((file) => { 228 | const filePath = path.join(uploadDirectory, file); 229 | const stats = fs.statSync(filePath); 230 | allFiles.push({ 231 | fileName: file, 232 | size: stats.size, 233 | link: `${PublicUrl}/file/${file}`, 234 | }); 235 | totalSize += stats.size; 236 | }); 237 | } else { 238 | return res.status(400).send({ error: "Invalid storage option" }); 239 | } 240 | 241 | const jsonResponse = { 242 | author, 243 | status, 244 | totalFiles: allFiles.length, 245 | totalSize: (totalSize / (1024 * 1024)).toFixed(2) + " MB", 246 | files: allFiles, 247 | }; 248 | 249 | res.setHeader("Content-Type", "application/json"); 250 | res.send(JSON.stringify(jsonResponse, null, 2)); 251 | } catch (err) { 252 | res.status(500).send({ error: "Error fetching files" }); 253 | } 254 | } 255 | 256 | module.exports = { getAllFilesWithTotalSize, uploadFile, getAllFiles, getFileHistory, getFileByProxy }; 257 | -------------------------------------------------------------------------------- /firebase.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "type": "service_account", 3 | "project_id": "cloud-xxxx", 4 | "private_key_id": "d040xxxx", 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nxxxx\n-----END PRIVATE KEY-----\n", 6 | "client_email": "firebase-adminsdk-xxxx@cloud-xxxxx.gserviceaccount.com", 7 | "client_id": "xxxx", 8 | "auth_uri": "https://accounts.google.com/o/oauth2/auth", 9 | "token_uri": "https://oauth2.googleapis.com/token", 10 | "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", 11 | "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-xxxx%40cloud-xxxxxx.gserviceaccount.com", 12 | "universe_domain": "googleapis.com" 13 | } 14 | -------------------------------------------------------------------------------- /middlewares/checkFileSize.js: -------------------------------------------------------------------------------- 1 | function checkFileSize(req, res, next) { 2 | if (req.file && req.file.size > 50 * 1024 * 1024) { 3 | res.setHeader("Content-Type", "application/json"); 4 | return res 5 | .status(400) 6 | .send(JSON.stringify({ error: "Error: File max upload 50MB" }, null, 2)); 7 | } 8 | next(); 9 | } 10 | 11 | module.exports = checkFileSize; 12 | -------------------------------------------------------------------------------- /middlewares/fileUpload.js: -------------------------------------------------------------------------------- 1 | const multer = require("multer"); 2 | const path = require("path"); 3 | const { generateRandomString, checkFileType } = require("../utils/helpers.js"); 4 | 5 | const uploadDirectory = path.join(__dirname, "..", "uploads"); 6 | 7 | const storage = multer.diskStorage({ 8 | destination: function (req, file, cb) { 9 | cb(null, uploadDirectory); 10 | }, 11 | filename: function (req, file, cb) { 12 | const uniqueName = generateRandomString() + path.extname(file.originalname); 13 | cb(null, uniqueName); 14 | }, 15 | }); 16 | 17 | const upload = multer({ 18 | storage: 19 | process.env.STORAGE === "firebase" ? multer.memoryStorage() : storage, 20 | limits: { fileSize: 50 * 1024 * 1024 }, 21 | fileFilter: function (req, file, cb) { 22 | checkFileType(file, cb); 23 | }, 24 | }); 25 | 26 | module.exports = upload; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cdn-uploader", 3 | "version": "1.0.0", 4 | "description": "cdn uploader for everyone use", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "Romi Muharom", 12 | "license": "MIT", 13 | "dependencies": { 14 | "date-fns": "^4.1.0", 15 | "dotenv": "^16.4.5", 16 | "express": "^4.19.2", 17 | "firebase-admin": "^12.5.0", 18 | "fs": "^0.0.1-security", 19 | "mime-types": "^2.1.35", 20 | "multer": "^1.4.5-lts.1", 21 | "path": "^0.12.7" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 404 Not Found 7 | 11 | 13 | 93 | 94 | 95 |
96 |
97 |

404 Not Found

98 |

Sorry, the page you are looking for does not exist.

99 |
100 | Go Home 101 |
102 | 105 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 500 Server Error 7 | 11 | 13 | 93 | 94 | 95 |
96 |
97 |

500 Server Error

98 |

Oops, something went wrong, Try to refresh this page or feel free to contact us if the problem persists

99 |
100 | Go Home 101 |
102 | 105 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leuthra/file-uploader/a141a954e0454abef1a9d3c3acc5b7a69854f16f/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leuthra/file-uploader/a141a954e0454abef1a9d3c3acc5b7a69854f16f/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cloud CDN File Uploader 7 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 38 | 39 | 40 | 41 | 42 | 46 | 47 | 48 |
49 |
50 |

Cloud CDN File Uploader

51 |

Made With ❤️ by Romi ~

52 |
53 |
54 | 57 | 58 | 59 |
60 |
61 |
62 |

History:

63 |
64 |
65 |
66 |
67 |
68 |

Total Files

69 |

70 |
71 |
72 |

Total Size

73 |

74 |
75 |
76 |
77 | 78 |
79 |

About You

80 |

IP Address:

81 |

Country:

82 |

Region:

83 |

Zip Code:

84 |

Languages:

85 |

Time Zone:

86 |
87 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /public/index.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", async () => { 2 | try { 3 | const response = await fetch("https://freeipapi.com/api/json"); 4 | const data = await response.json(); 5 | document.getElementById("ipAddress").textContent = data.ipAddress; 6 | document.getElementById("country").textContent = data.countryName; 7 | document.getElementById("region").textContent = data.regionName; 8 | document.getElementById("zipCode").textContent = data.zipCode; 9 | document.getElementById("languages").textContent = data.language; 10 | document.getElementById("timeZone").textContent = data.timeZone; 11 | } catch (error) { 12 | console.error("Error fetching user data:", error); 13 | } 14 | loadHistory(); 15 | document.getElementById("currentYear").textContent = new Date().getFullYear(); 16 | fetchFileInfo(); 17 | if (!localStorage.getItem("welcomePopupShown")) { 18 | showWelcomePopup(); 19 | } 20 | }); 21 | 22 | function showWelcomePopup() { 23 | const popupContainer = document.createElement("div"); 24 | popupContainer.className = "welcome-popup-container"; 25 | 26 | const popupContent = document.createElement("div"); 27 | popupContent.className = "welcome-popup-content"; 28 | 29 | const popupTitle = document.createElement("h2"); 30 | popupTitle.textContent = "Welcome to Cloud CDN"; 31 | 32 | const popupMessage = document.createElement("p"); 33 | popupMessage.innerHTML = 34 | "With Cloud CDN, we deliver the best speed, security, and performance for your website. Enjoy faster, safer, and more reliable content wherever your visitors are.

Explore our features and if you have any questions, our support team is ready to help you anytime.

Thank you for choosing Cloud CDN!"; 35 | 36 | const checkboxContainer = document.createElement("div"); 37 | checkboxContainer.className = "checkbox-container"; 38 | 39 | const checkbox = document.createElement("input"); 40 | checkbox.type = "checkbox"; 41 | checkbox.id = "dontShowAgain"; 42 | 43 | const checkboxLabel = document.createElement("label"); 44 | checkboxLabel.htmlFor = "dontShowAgain"; 45 | checkboxLabel.textContent = "Don't show again"; 46 | 47 | checkboxContainer.appendChild(checkbox); 48 | checkboxContainer.appendChild(checkboxLabel); 49 | 50 | const closeButton = document.createElement("button"); 51 | closeButton.textContent = "Close"; 52 | closeButton.addEventListener("click", () => { 53 | if (checkbox.checked) { 54 | localStorage.setItem("welcomePopupShown", "true"); 55 | } 56 | document.body.removeChild(popupContainer); 57 | }); 58 | 59 | popupContent.appendChild(popupTitle); 60 | popupContent.appendChild(popupMessage); 61 | popupContent.appendChild(checkboxContainer); 62 | popupContent.appendChild(closeButton); 63 | popupContainer.appendChild(popupContent); 64 | document.body.appendChild(popupContainer); 65 | } 66 | 67 | function fetchFileInfo() { 68 | fetch("/files") 69 | .then((response) => response.json()) 70 | .then((data) => { 71 | document.getElementById("totalFiles").textContent = data.totalFiles; 72 | document.getElementById("totalSize").textContent = data.totalSize; 73 | }) 74 | .catch((error) => { 75 | console.error("Error fetching file information:", error); 76 | document.getElementById("totalFiles").textContent = "Error"; 77 | document.getElementById("totalSize").textContent = "Error"; 78 | }); 79 | } 80 | 81 | function formatSize(size) { 82 | const units = ["B", "KB", "MB", "GB", "TB"]; 83 | let unitIndex = 0; 84 | while (size >= 1024 && unitIndex < units.length - 1) { 85 | size /= 1024; 86 | unitIndex++; 87 | } 88 | return `${size.toFixed(2)} ${units[unitIndex]}`; 89 | } 90 | 91 | document.getElementById("fileInput").addEventListener("change", function (e) { 92 | const file = e.target.files[0]; 93 | displayPreview(file); 94 | }); 95 | 96 | document.getElementById("fileInput").addEventListener("dragover", function (e) { 97 | e.preventDefault(); 98 | }); 99 | 100 | document.getElementById("fileInput").addEventListener("drop", function (e) { 101 | e.preventDefault(); 102 | const file = e.dataTransfer.files[0]; 103 | displayPreview(file); 104 | }); 105 | 106 | let uploadCompleted = false; 107 | 108 | document.getElementById("uploadForm").addEventListener("submit", function (e) { 109 | e.preventDefault(); 110 | 111 | const uploadButton = document.querySelector(".upload-button"); 112 | const fileInput = document.getElementById("fileInput"); 113 | const file = fileInput.files[0]; 114 | 115 | if (!file) { 116 | showPopup("No files selected", "error"); 117 | return; 118 | } 119 | 120 | if (file.size > 50 * 1024 * 1024) { 121 | showPopup("File Size Exceeds 50MB", "error"); 122 | return; 123 | } 124 | 125 | if (uploadCompleted) { 126 | location.reload(); 127 | return; 128 | } 129 | 130 | uploadButton.innerHTML = ''; 131 | uploadButton.disabled = true; 132 | 133 | const formData = new FormData(); 134 | formData.append("fileInput", file); 135 | 136 | fetch("/upload", { 137 | method: "POST", 138 | body: formData, 139 | }) 140 | .then((response) => response.json()) 141 | .then((data) => { 142 | uploadButton.innerHTML = "Refresh"; 143 | uploadButton.disabled = false; 144 | showPopup("File Uploaded", "success"); 145 | updateHistory(data.url_response); 146 | uploadCompleted = true; 147 | }) 148 | .catch((error) => { 149 | uploadButton.innerHTML = "Upload"; 150 | uploadButton.disabled = false; 151 | showPopup("Oops Something Went Wrong", "error"); 152 | }); 153 | }); 154 | 155 | function displayPreview(file) { 156 | const preview = document.getElementById("preview"); 157 | preview.innerHTML = ""; 158 | const fileType = file.type.split("/")[0]; 159 | let previewElement; 160 | 161 | if (fileType === "image") { 162 | previewElement = document.createElement("img"); 163 | previewElement.src = URL.createObjectURL(file); 164 | } else if (fileType === "video") { 165 | previewElement = document.createElement("video"); 166 | previewElement.controls = true; 167 | previewElement.src = URL.createObjectURL(file); 168 | } else { 169 | previewElement = document.createElement("div"); 170 | previewElement.className = "file"; 171 | previewElement.innerText = file.name; 172 | } 173 | 174 | preview.appendChild(previewElement); 175 | } 176 | 177 | function updateHistory(url) { 178 | const history = document.getElementById("history"); 179 | history.innerHTML = ""; 180 | 181 | const link = document.createElement("a"); 182 | link.href = "#"; 183 | link.textContent = url; 184 | link.addEventListener("click", function (e) { 185 | e.preventDefault(); 186 | copyToClipboard(url); 187 | }); 188 | 189 | history.appendChild(link); 190 | } 191 | 192 | function copyToClipboard(text) { 193 | navigator.clipboard 194 | .writeText(text) 195 | .then(() => { 196 | showPopup("Link Copied!", "success"); 197 | }) 198 | .catch((err) => { 199 | showPopup("Failed to Copy Link", "error"); 200 | }); 201 | } 202 | 203 | function saveToLocalStorage(url) { 204 | localStorage.setItem("uploadedFileUrl", url); 205 | } 206 | 207 | function loadHistory() { 208 | fetch("/history") 209 | .then((response) => response.json()) 210 | .then((data) => { 211 | const historyContainer = document.getElementById("history"); 212 | historyContainer.innerHTML = ""; 213 | if (data.fileName && data.url) { 214 | const link = document.createElement("a"); 215 | link.href = data.url; 216 | link.textContent = `${data.fileName} (${data.size})`; 217 | historyContainer.appendChild(link); 218 | } else if (data.message) { 219 | historyContainer.textContent = data.message; 220 | } else { 221 | historyContainer.textContent = "Unknown error"; 222 | } 223 | }) 224 | .catch((error) => { 225 | console.error("Error loading history:", error); 226 | const historyContainer = document.getElementById("history"); 227 | historyContainer.textContent = "Error fetching history"; 228 | }); 229 | } 230 | 231 | function showPopup(message, type = "error") { 232 | const popup = document.createElement("div"); 233 | popup.className = `popup ${type}`; 234 | popup.textContent = message; 235 | popup.style.animation = "slideDown 0.3s ease-out forwards"; 236 | document.body.appendChild(popup); 237 | 238 | setTimeout(() => { 239 | popup.style.animation = "fadeOut 0.5s ease-out forwards"; 240 | setTimeout(() => { 241 | document.body.removeChild(popup); 242 | }, 500); 243 | }, 3000); 244 | } 245 | 246 | function fetchStats() { 247 | fetch("/files") 248 | .then((response) => response.json()) 249 | .then((data) => { 250 | document.getElementById("totalFiles").textContent = data.totalFiles; 251 | document.getElementById("totalSize").textContent = data.totalSize; 252 | }) 253 | .catch((error) => console.error("Error fetching stats:", error)); 254 | } 255 | 256 | setInterval(fetchStats, 5000); 257 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Comfortaa:wght@300..700&display=swap"); 2 | 3 | :root { 4 | --primary-color: #faae2b; 5 | --secondary-color: #8bd3dd; 6 | --background-color: #f2f7f5; 7 | --text-color: #000; 8 | --error-color: #ff0000; 9 | --success-color: #8bd3dd; 10 | --font-family: "Comfortaa", sans-serif; 11 | } 12 | 13 | body { 14 | background-color: var(--background-color); 15 | font-family: var(--font-family); 16 | height: 100vh; 17 | margin: 0; 18 | padding-right: 20px; 19 | padding-left: 20px; 20 | } 21 | 22 | .container { 23 | background-color: #f2f7f5; 24 | margin: 20px auto; 25 | padding: 20px; 26 | border: 1px solid #000; 27 | border-radius: 5px; 28 | box-shadow: 10px 10px 0 #000000bf; 29 | text-align: center; 30 | width: 80%; 31 | max-width: 500px; 32 | margin-bottom: 20px; 33 | } 34 | 35 | .header h1 { 36 | font-size: 1.5em; 37 | margin-bottom: 5px; 38 | } 39 | 40 | .header p { 41 | margin-top: 0; 42 | margin-bottom: 20px; 43 | } 44 | 45 | .header .heart { 46 | color: red; 47 | display: inline-block; 48 | animation: beat 1s infinite; 49 | } 50 | 51 | form { 52 | display: flex; 53 | flex-direction: column; 54 | align-items: center; 55 | margin-bottom: 20px; 56 | } 57 | 58 | .file-label { 59 | background-color: #8bd3dd; 60 | border: 1px solid var(--text-color); 61 | border-radius: 10px; 62 | padding: 10px; 63 | width: 100%; 64 | text-align: center; 65 | cursor: pointer; 66 | margin-bottom: 10px; 67 | box-shadow: 5px 5px 0 var(--text-color); 68 | } 69 | 70 | input[type="file"] { 71 | display: none; 72 | } 73 | 74 | .upload-button { 75 | background-color: #faae2b; 76 | color: #000; 77 | font-family: "Comfortaa", sans-serif; 78 | border: none; 79 | padding: 10px 20px; 80 | cursor: pointer; 81 | box-shadow: 5px 5px 0 #000000bf; 82 | transition: transform 0.2s; 83 | width: 150px; 84 | height: 50px; 85 | line-height: 50px; 86 | text-align: center; 87 | border-radius: 5px; 88 | display: flex; 89 | justify-content: center; 90 | align-items: center; 91 | } 92 | 93 | .upload-button:hover { 94 | transform: scale(1.05); 95 | } 96 | 97 | .preview img, 98 | .preview video, 99 | .preview .file { 100 | max-width: 100%; 101 | max-height: 200px; 102 | border: 1px solid #000000bf; 103 | border-radius: 10px; 104 | margin-top: 10px; 105 | box-shadow: 5px 5px 0 #000000bf; 106 | } 107 | 108 | .history { 109 | margin-top: 20px; 110 | text-align: left; 111 | background-color: var(--background-color); 112 | padding: 20px; 113 | border: 1px solid var(--text-color); 114 | border-radius: 5px; 115 | box-shadow: 10px 10px 0 var(--text-color); 116 | } 117 | 118 | .history h2 { 119 | font-size: 1.2em; 120 | margin-bottom: 10px; 121 | } 122 | 123 | .history a { 124 | margin-bottom: 5px; 125 | color: var(--text-color); 126 | text-decoration: none; 127 | padding: 5px; 128 | display: block; 129 | max-width: 100%; 130 | white-space: nowrap; 131 | overflow: hidden; 132 | text-overflow: ellipsis; 133 | transition: transform 0.2s; 134 | } 135 | 136 | .history a:hover { 137 | transform: scale(1.05); 138 | } 139 | 140 | .history .copy-icon { 141 | cursor: pointer; 142 | margin-left: 10px; 143 | color: var(--text-color); 144 | } 145 | 146 | .history .copy-icon:hover { 147 | color: var(--error-color); 148 | } 149 | 150 | .stats { 151 | display: grid; 152 | grid-template-columns: repeat(2, 1fr); 153 | gap: 4%; 154 | margin-top: 20px; 155 | } 156 | 157 | .stats-box { 158 | background-color: #e0e0e0; 159 | padding: 10px; 160 | box-shadow: 5px 5px 0 #000000bf; 161 | text-align: center; 162 | border-radius: 10px; 163 | } 164 | 165 | .stats-box:nth-child(1) { 166 | background-color: #faae2b; 167 | } 168 | 169 | .stats-box:nth-child(2) { 170 | background-color: #8bd3dd; 171 | } 172 | 173 | .fa-regular, 174 | .far { 175 | font-family: "Font Awesome 6 Free"; 176 | font-weight: 400; 177 | } 178 | 179 | @keyframes spin { 180 | 0% { 181 | transform: rotate(0deg); 182 | } 183 | 100% { 184 | transform: rotate(360deg); 185 | } 186 | } 187 | .fas.fa-spinner { 188 | animation: spin 1s linear infinite; 189 | } 190 | 191 | .container-you { 192 | background-color: #faae2b; 193 | padding: 20px; 194 | border: 1px solid #000; 195 | border-radius: 5px; 196 | box-shadow: 10px 10px 0 #000000bf; 197 | text-align: center; 198 | width: 80%; 199 | max-width: 500px; 200 | margin: 21px auto; 201 | } 202 | 203 | .popup { 204 | position: fixed; 205 | top: 10px; 206 | left: 50%; 207 | transform: translateX(-50%); 208 | z-index: 1000; 209 | background-color: #ff0000; 210 | color: #000; 211 | padding: 10px 20px; 212 | border: 1px solid #000; 213 | box-shadow: 5px 5px 0 #000000bf; 214 | font-family: "Comfortaa", sans-serif; 215 | animation: slideDown 0.3s ease-out forwards; 216 | } 217 | 218 | .popup.success { 219 | background-color: #8bd3dd; 220 | } 221 | 222 | @keyframes slideDown { 223 | from { 224 | opacity: 0; 225 | transform: translateX(-50%) translateY(-20px); 226 | } 227 | to { 228 | opacity: 1; 229 | transform: translateX(-50%) translateY(0); 230 | } 231 | } 232 | 233 | @keyframes fadeOut { 234 | from { 235 | opacity: 1; 236 | } 237 | to { 238 | opacity: 0; 239 | } 240 | } 241 | 242 | .welcome-popup-container { 243 | position: fixed; 244 | top: 0; 245 | left: 0; 246 | width: 100%; 247 | height: 100%; 248 | background-color: rgba(0, 0, 0, 0.5); 249 | display: flex; 250 | justify-content: center; 251 | align-items: center; 252 | z-index: 1000; 253 | } 254 | 255 | .welcome-popup-content { 256 | background-color: #fff; 257 | padding: 20px; 258 | border: 1px solid #000; 259 | border-radius: 5px; 260 | box-shadow: 10px 10px 0 #000000bf; 261 | text-align: center; 262 | max-width: 500px; 263 | width: 80%; 264 | } 265 | 266 | .welcome-popup-content h2 { 267 | font-size: 1.5em; 268 | margin-bottom: 10px; 269 | } 270 | 271 | .welcome-popup-content p { 272 | margin-bottom: 20px; 273 | } 274 | 275 | .welcome-popup-content button { 276 | background-color: var(--primary-color); 277 | color: #000; 278 | font-family: var(--font-family); 279 | border: none; 280 | padding: 10px 20px; 281 | cursor: pointer; 282 | box-shadow: 5px 5px 0 #000000bf; 283 | transition: transform 0.2s; 284 | } 285 | 286 | .welcome-popup-content button:hover { 287 | transform: scale(1.05); 288 | } 289 | 290 | .checkbox-container { 291 | display: flex; 292 | align-items: center; 293 | margin-bottom: 20px; 294 | } 295 | 296 | .checkbox-container input[type="checkbox"] { 297 | margin-right: 10px; 298 | } 299 | 300 | .footer { 301 | color: #000; 302 | text-align: center; 303 | padding: 20px; 304 | font-family: var(--font-family); 305 | font-weight: bold; 306 | } 307 | -------------------------------------------------------------------------------- /routes/fileRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const { uploadFile, getAllFiles, getFileHistory, getFileByProxy, getAllFilesWithTotalSize } = require("../controllers/fileController.js"); 4 | const upload = require("../middlewares/fileUpload.js"); 5 | const checkFileSize = require("../middlewares/checkFileSize.js"); 6 | 7 | router.get("/", (req, res) => { 8 | res.sendFile(path.join(__dirname, "../public/index.html")); 9 | }); 10 | router.post("/upload", upload.single("fileInput"), checkFileSize, uploadFile); 11 | router.get("/all", getAllFiles); 12 | router.get("/history", getFileHistory); 13 | router.get("/file/:filename", getFileByProxy); 14 | router.get("/files", getAllFilesWithTotalSize); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | require("dotenv").config(); 3 | const fileRoutes = require("./routes/fileRoutes.js"); 4 | const path = require("path"); 5 | 6 | const app = express(); 7 | const port = process.env.PORT || 3000; 8 | 9 | app.use(express.static(path.join(__dirname, "public"))); 10 | 11 | // Routes 12 | app.use("/", fileRoutes); 13 | 14 | // Error handling routes 15 | app.use((req, res, next) => { 16 | res.status(404).sendFile(path.join(__dirname, "public", "404.html")); 17 | }); 18 | 19 | app.use((err, req, res, next) => { 20 | res.status(500).sendFile(path.join(__dirname, "public", "500.html")); 21 | }); 22 | 23 | // security 24 | app.use((req, res, next) => { 25 | res.setHeader( 26 | "Content-Security-Policy", 27 | "default-src 'self'; script-src 'none'; object-src 'none';" 28 | ); 29 | next(); 30 | }); 31 | 32 | app.listen(port, () => { 33 | console.log(`Server running at ${process.env.PUBLIC_URL}`); 34 | }); 35 | -------------------------------------------------------------------------------- /uploads/pezraq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Leuthra/file-uploader/a141a954e0454abef1a9d3c3acc5b7a69854f16f/uploads/pezraq.jpg -------------------------------------------------------------------------------- /utils/helpers.js: -------------------------------------------------------------------------------- 1 | const { Readable } = require("stream"); 2 | const { format } = require("date-fns"); 3 | const path = require("path"); 4 | 5 | function generateRandomString() { 6 | return Math.random().toString(36).substring(2, 8); 7 | } 8 | 9 | function bufferToStream(buffer) { 10 | const stream = new Readable(); 11 | stream.push(buffer); 12 | stream.push(null); 13 | return stream; 14 | } 15 | 16 | function formatDate(dateString) { 17 | const date = new Date(dateString); 18 | return format(date, "hh:mm aa, dd MMMM yyyy"); 19 | } 20 | 21 | function checkFileType(file, cb) { 22 | const filetypes = 23 | /jpeg|jpg|png|gif|pdf|doc|docx|mp4|avi|mov|mkv|ppt|pptx|xls|xlsx/; 24 | const extname = filetypes.test(path.extname(file.originalname).toLowerCase()); 25 | const mimetypes = [ 26 | "image/jpeg", 27 | "image/jpg", 28 | "image/png", 29 | "image/gif", 30 | "application/pdf", 31 | "application/msword", 32 | "application/vnd.openxmlformats-officedocument.wordprocessingml.document", 33 | "video/mp4", 34 | "video/x-msvideo", 35 | "video/quicktime", 36 | "video/x-matroska", 37 | "application/vnd.ms-powerpoint", 38 | "application/vnd.openxmlformats-officedocument.presentationml.presentation", 39 | "application/vnd.ms-excel", 40 | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 41 | ]; 42 | 43 | const mimetype = mimetypes.includes(file.mimetype); 44 | 45 | if (mimetype && extname) { 46 | return cb(null, true); 47 | } else { 48 | cb( 49 | "Error: Only images, videos, gifs, docs, PDFs, presentations, and spreadsheets are allowed!" 50 | ); 51 | } 52 | } 53 | 54 | module.exports = { 55 | generateRandomString, 56 | bufferToStream, 57 | formatDate, 58 | checkFileType, 59 | }; 60 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "server.js", 6 | "use": "@vercel/node" 7 | }, 8 | { 9 | "src": "**/*", 10 | "use": "@vercel/static" 11 | } 12 | ], 13 | "routes": [ 14 | { 15 | "src": "/(.*)", 16 | "dest": "server.js" 17 | } 18 | ] 19 | } 20 | --------------------------------------------------------------------------------