From 65d4779083484070ab945bb412a72882f56b9606 Mon Sep 17 00:00:00 2001 From: Schneider Roland Date: Sat, 22 Feb 2025 13:14:06 +0100 Subject: [PATCH] impelement http api --- .gitignore | 4 +- index.js | 147 +++++++++++++++++++++++++++++++++------------- package-lock.json | 28 +++++++-- package.json | 3 +- 4 files changed, 132 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index aa15018..8a579dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ node_modules -data \ No newline at end of file +data +*.code-workspace +.vscode \ No newline at end of file diff --git a/index.js b/index.js index 3f684ba..e54ce09 100644 --- a/index.js +++ b/index.js @@ -1,25 +1,25 @@ -const express = require('express') -const fileUpload = require('express-fileupload') -const app = express() +const express = require('express'); +const fileUpload = require('express-fileupload'); 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 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) =>{ - const authenticated = req.get('Authorization') === 'Bearer ' + process.env.API_KEY ; - if(!authenticated){ - return res.status(401).send('Unauthorized'); +app.use((req, res, next) => { + 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); + const files = fs.readdirSync(uploadPath); // Sort files by modification time (mtime) files.sort((a, b) => { @@ -32,29 +32,65 @@ const processGroups = () => { // collection files for defined groups 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)); - groups.push({ ...group, files: filesStartingWithPrefix }) + groups.push({ ...group, files: filesStartingWithPrefix }); }); return groups; -} +}; +/** + * + * clean up files based on the group size + */ const cleanUp = () => { - const groups = processGroups(); 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); + 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(); @@ -77,32 +113,59 @@ app.get('/', (req, res) => { res.send(files); }); -app.post('/*', function (req, res) { - 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; - - 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 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}`) -}) \ No newline at end of file + console.log(`Example app listening on port ${port}`); +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 4a98c02..f9981d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "ISC", "dependencies": { "express": "^4.21.2", - "express-fileupload": "^1.5.1" + "express-fileupload": "^1.5.1", + "mime": "^4.0.6" } }, "node_modules/accepts": { @@ -498,15 +499,18 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz", + "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==", + "funding": [ + "https://github.com/sponsors/broofa" + ], "license": "MIT", "bin": { - "mime": "cli.js" + "mime": "bin/cli.js" }, "engines": { - "node": ">=4" + "node": ">=16" } }, "node_modules/mime-db": { @@ -695,6 +699,18 @@ "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": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index fd0ce1a..667f512 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "description": "", "dependencies": { "express": "^4.21.2", - "express-fileupload": "^1.5.1" + "express-fileupload": "^1.5.1", + "mime": "^4.0.6" } }