require('dotenv').config() const express = require('express'); const fileUpload = require('express-fileupload'); const fs = require('fs'); const mime = require('mime'); const app = express(); const port = 3000; const uploadPath = process.env.UPLOAD_PATH || '/tmp'; const config = process.env.CONFIG || "{ \"uploadGroups\": [] }"; //const config = process.env.CONFIG || "{ \"uploadGroups\": [{ \"matcher\": \"^mmkb-android-.*-test\\.apk$\", \"groupSize\": 3},{ \"matcher\": \"^mmkb-android-.*-int\\.apk$\", \"groupSize\": 3},{ \"matcher\": \"^mmkb-android-.*-prod\\.apk$\", \"groupSize\": 3}] }"; console.info("Upload path: ", uploadPath); console.info("Config: ", config); app.use(fileUpload()); app.use((req, res, next) => { if (process.env.API_KEY) { const authenticated = req.get('Authorization') === 'Bearer ' + process.env.API_KEY; if (!authenticated) { return res.status(401).send('Unauthorized'); } } next(); }); const processGroups = () => { const files = fs.readdirSync(uploadPath); // Sort files by modification time (mtime) files.sort((a, b) => { const statA = fs.statSync(uploadPath + "/" + a); const statB = fs.statSync(uploadPath + "/" + b); return statA.mtime - statB.mtime; }); const groups = []; // collection files for defined groups JSON.parse(config).uploadGroups.forEach(group => { const filesStartingWithPrefix = files.filter(file => new RegExp(group.matcher).test(file)); groups.push({ ...group, files: filesStartingWithPrefix }); }); return groups; }; /** * * clean up files based on the group size */ const cleanUp = () => { console.info("Cleaning up files..."); const groups = processGroups(); console.info("Groups: ", groups); groups.forEach(group => { if (group.files.length > group.groupSize) { const filesToDelete = group.files.slice(0, group.files.length - group.groupSize); filesToDelete.forEach(file => { console.log(new Date().toISOString() + " Deleting file: ", file); //fs.unlinkSync(uploadPath + "/" + file); }); } }); }; /** * * upload a file * throws 400 if no file was uploaded * throws 409 if file already exists and overwrite is false * throws 500 if file upload failed */ const uploadFile = (req, res, overwrite) => { let sampleFile; const uploadedFileKeys = Object.keys(req.files); if (!req.files || uploadedFileKeys.length === 0) { return res.status(400).send('No files were uploaded.'); } let messages = ""; const uploadedFileObj = req.files[uploadedFileKeys[0]]; let uploadFilePath = uploadPath + "/" + uploadedFileObj.name; if (fs.existsSync(uploadFilePath) && !overwrite) { console.log(new Date().toISOString() + " File already exists: ", uploadFilePath); return res.status(409).send('File already exists.'); } uploadedFileObj.mv(uploadFilePath, function (err) { if (err) { console.log(new Date().toISOString() + " File upalod failed: ", err); return res.status(500).send(err); } console.log(new Date().toISOString() + " File uploaded to: ", uploadFilePath); cleanUp(); res.send('File(s) uploaded!'); }); }; app.get('/state', (req, res) => { const groups = processGroups(); res.setHeader('Content-Type', 'application/json'); res.send(groups); }); app.get('/clean', (req, res) => { cleanUp(); res.setHeader('Content-Type', 'application/json'); res.send('Clean up done!'); }); app.get('/', (req, res) => { const files = []; fs.readdirSync(uploadPath).forEach(file => { const stat = fs.statSync(uploadPath + "/" + file); files.push({ name: file, "mtime": stat.mtime }); }); res.setHeader('Content-Type', 'application/json'); res.send(files); }); /** * * upload a file * throws 409 if file already exists */ app.post('/', function (req, res) { uploadFile(req, res, false); }); /** * * upload or replace a file */ app.put('/', function (req, res) { uploadFile(req, res, true); }); /** * * delete a file with the given filename * throws 404 if file not found */ app.delete('/:filename', (req, res) => { const filename = req.params.filename; const filePath = uploadPath + "/" + filename; if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); res.send(`File ${filename} deleted!`); } else { res.status(404).send('File not found.'); } }); /** * * get a file with the given filename * returns the file content with the appropriate Content-Type */ app.get('/:filename', (req, res) => { const filename = req.params.filename; const filePath = uploadPath + "/" + filename; if (fs.existsSync(filePath)) { // todo: mime.getType is not a function //const mimeType = mime.getType(filePath); //res.setHeader('Content-Type', mimeType); fs.createReadStream(filePath).pipe(res); } else { res.status(404).send('File not found.'); } }); app.listen(port, () => { console.log(`Example app listening on port ${port}`); });