188 lines
5.2 KiB
JavaScript
188 lines
5.2 KiB
JavaScript
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}`);
|
|
}); |