impelement http api

This commit is contained in:
Schneider Roland 2025-02-22 13:14:06 +01:00
parent bc8e58e472
commit 65d4779083
4 changed files with 132 additions and 50 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
node_modules node_modules
data data
*.code-workspace
.vscode

147
index.js
View File

@ -1,25 +1,25 @@
const express = require('express') const express = require('express');
const fileUpload = require('express-fileupload') const fileUpload = require('express-fileupload');
const app = express()
const fs = require('fs'); const fs = require('fs');
const port = 3000 const mime = require( 'mime' );
const app = express();
const port = 3000;
const uploadPath = process.env.UPLOAD_PATH || '/tmp'; const uploadPath = process.env.UPLOAD_PATH || '/tmp';
const config = process.env.config || '{ "uploadGroups": [{ "matcher": "^test.*\.txt$", "groupSize": 3}] } '; const config = process.env.config || '{ "uploadGroups": [{ "matcher": "^test.*\.txt$", "groupSize": 3}] }';
app.use(fileUpload()) app.use(fileUpload());
app.use((req, res, next) =>{ app.use((req, res, next) => {
const authenticated = req.get('Authorization') === 'Bearer ' + process.env.API_KEY ; const authenticated = req.get('Authorization') === 'Bearer ' + process.env.API_KEY;
if(!authenticated){ if (!authenticated) {
return res.status(401).send('Unauthorized'); return res.status(401).send('Unauthorized');
} }
next(); next();
}); });
const processGroups = () => { const processGroups = () => {
const files = [] = fs.readdirSync(uploadPath); const files = fs.readdirSync(uploadPath);
// Sort files by modification time (mtime) // Sort files by modification time (mtime)
files.sort((a, b) => { files.sort((a, b) => {
@ -32,29 +32,65 @@ const processGroups = () => {
// collection files for defined groups // collection files for defined groups
JSON.parse(config).uploadGroups.forEach(group => { JSON.parse(config).uploadGroups.forEach(group => {
//const filesStartingWithPrefix = files.filter(file => new RegExp(group.mathcer).test(file));
const filesStartingWithPrefix = files.filter(file => new RegExp(group.matcher).test(file)); const filesStartingWithPrefix = files.filter(file => new RegExp(group.matcher).test(file));
groups.push({ ...group, files: filesStartingWithPrefix }) groups.push({ ...group, files: filesStartingWithPrefix });
}); });
return groups; return groups;
} };
/**
*
* clean up files based on the group size
*/
const cleanUp = () => { const cleanUp = () => {
const groups = processGroups(); const groups = processGroups();
groups.forEach(group => { groups.forEach(group => {
if (group.files.length > group.groupSize) { if (group.files.length > group.groupSize) {
const filesToDelete = group.files.slice(0, group.files.length - group.groupSize); const filesToDelete = group.files.slice(0, group.files.length - group.groupSize);
filesToDelete.forEach(file => { filesToDelete.forEach(file => {
console.log(new Date().toISOString() +" Deleting file: ", file); console.log(new Date().toISOString() + " Deleting file: ", file);
fs.unlinkSync(uploadPath + "/" + 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) => { app.get('/state', (req, res) => {
const groups = processGroups(); const groups = processGroups();
@ -77,32 +113,59 @@ app.get('/', (req, res) => {
res.send(files); res.send(files);
}); });
app.post('/*', function (req, res) { /**
let sampleFile; *
* upload a file
const uploadedFileKeys = Object.keys(req.files); * throws 409 if file already exists
if (!req.files || uploadedFileKeys.length === 0) { */
return res.status(400).send('No files were uploaded.'); app.post('/', function (req, res) {
} uploadFile(req, res, false);
let messages = "";
const uploadedFileObj = req.files[uploadedFileKeys[0]];
let uploadFilePath = uploadPath + "/" + uploadedFileObj.name;
uploadedFileObj.mv(uploadFilePath, function (err) {
if (err) {
return res.status(500).send(err);
}
console.log( new Date().toISOString() +" File uploaded to: ", uploadFilePath);
cleanUp();
res.send('File(s) uploaded!');
});
}); });
/**
*
* 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, () => { app.listen(port, () => {
console.log(`Example app listening on port ${port}`) console.log(`Example app listening on port ${port}`);
}) });

28
package-lock.json generated
View File

@ -10,7 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"express": "^4.21.2", "express": "^4.21.2",
"express-fileupload": "^1.5.1" "express-fileupload": "^1.5.1",
"mime": "^4.0.6"
} }
}, },
"node_modules/accepts": { "node_modules/accepts": {
@ -498,15 +499,18 @@
} }
}, },
"node_modules/mime": { "node_modules/mime": {
"version": "1.6.0", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==",
"funding": [
"https://github.com/sponsors/broofa"
],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"mime": "cli.js" "mime": "bin/cli.js"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=16"
} }
}, },
"node_modules/mime-db": { "node_modules/mime-db": {
@ -695,6 +699,18 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/send/node_modules/ms": { "node_modules/send/node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@ -11,6 +11,7 @@
"description": "", "description": "",
"dependencies": { "dependencies": {
"express": "^4.21.2", "express": "^4.21.2",
"express-fileupload": "^1.5.1" "express-fileupload": "^1.5.1",
"mime": "^4.0.6"
} }
} }