From bffe457e13ae6bacafa9b2efc5eb45547ec44c7a Mon Sep 17 00:00:00 2001 From: eclipse Date: Sat, 11 Nov 2023 17:22:10 +0100 Subject: [PATCH] * added upload, download, and delete functionality * added error handling * added CSS stylesheet * bumped version to 0.3 --- css/updown.css | 13 ++++++ package.json | 3 +- updown.js | 102 ++++++++++++++++++++++++++++++++++++++++++----- views/updown.pug | 39 +++++++++--------- 4 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 css/updown.css diff --git a/css/updown.css b/css/updown.css new file mode 100644 index 0000000..80333d1 --- /dev/null +++ b/css/updown.css @@ -0,0 +1,13 @@ +.table { + display: table; + margin: 0 auto; +} + +.tr { + display: table-row; +} + +.td { + display: table-cell; +} + diff --git a/package.json b/package.json index 72fd7ce..4b6dc49 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "updown", - "version": "0.2.0", + "version": "0.3.0", "description": "a simple file uploader/downloader", "main": "updown.js", "scripts": { @@ -21,6 +21,7 @@ "buffer": "^6.0.3", "crypto-browserify": "^3.12.0", "events": "^3.3.0", + "express-fileupload": "^1.4.1", "parcel": "^2.9.3", "path-browserify": "^1.0.1", "process": "^0.11.10", diff --git a/updown.js b/updown.js index 5f73523..7aa8d14 100644 --- a/updown.js +++ b/updown.js @@ -1,35 +1,115 @@ const express = require('express'); +const fileupload = require('express-fileupload'); const app = express(); const port = 3000; +const path = require('path'); const fs = require('fs'); const staticPath = "public/"; app.set("view engine", "pug"); +app.use(fileupload()); +// serve static css files +app.use(express.static(path.join(__dirname, 'css'))); -function getFilesAndSizes(path) { +// Return all files and their sizes +function getFilesAndSizes(dir) { + // check if dir exists + try { + fs.accessSync(dir, fs.constants.F_OK); + } catch (error) { + console.log("exist-check: " + error); + return []; + } + // check if dir is readable + try { + fs.accessSync(dir, fs.constants.R_OK); + } catch (error) { + console.log("read-check: " + error); + return []; + } + // get files and their sizes from path let files = []; - fs.readdirSync(path).forEach(filename => { - files.push({ name: filename, size: fs.statSync(path + filename, (e, s) => s).size }); + fs.readdirSync(dir).forEach(filename => { + files.push({ name: filename, size: fs.statSync(dir + filename, (e, s) => s).size }); }) -console.log(files); + console.log(files);//DEBUG return files; } +// Handle main page request app.get('/', (req, res) => { - console.log(req.query); - res.render("updown", { - message: "method: get", + //TODO: handle sorting + //TODO: keep sort parameter in variable or something + return res.render("updown", { + message: req.query.msg, files: getFilesAndSizes(staticPath) }) }) -app.post("/", (req, res) => { - res.render("updown", { - message: "method: post", - files: getFilesAndSizes(staticPath) +// Handle download request +app.get('/download/:file', (req, res) => { + console.log('route /download/: params are ' + req.params);//DEBUG + res.download(staticPath + req.params['file'], error => { + if (!error) + return; + if (error.status === 404) + return res.redirect('/?msg=Download failed: file not found'); + else + return res.redirect('/?msg=Download failed: Internal server error'); }); }) +// Handle delete request +app.get('/delete/:file', (req, res) => { + console.log('route /delete/: params are ' + req.params);//DEBUG + // check if path is writable + fs.access(staticPath, fs.constants.w_OK, (error) => { + if (error) { + console.log("Deletion error: " + error.message); + return res.redirect("/?msg=Deletion failed: Path is not writable"); + } + }) + // delete file + try { + fs.rmSync(staticPath + req.params['file']); + return res.redirect("/?msg=File deleted"); + } catch (error) { + if (error.message.startsWith("ENOENT")) { + return res.redirect("/?msg=Deletion failed: File doesn't exist"); + } else { + console.log("Deletion error: " + error.message); + return res.redirect("/?msg=Deletion failed: Internal server error"); + } + } +}) + +// Handle upload request +app.post("/upload/", (req, res) => { + // check if request contains a file + if (!req.files || Object.keys(req.files).length === 0) { + console.log("Upload error: " + error.message); + return res.redirect("/?msg=Upload failed: no file selected"); + } + // check if path is writable + fs.access(staticPath, fs.constants.w_OK, (error) => { + if (error) { + console.log("Upload error: " + error.message); + return res.redirect("/?msg=Upload failed: Path is not writable"); + } + }) + // upload file + let upFile = req.files.up; + upFile.mv(staticPath + upFile.name, (error) => { + if (!error) + return res.redirect("/?msg=File uploaded"); + else { + console.log("Upload error: " + error.message); + return res.redirect("/?msg=Upload failed: Internal server error"); + } + }) +}) + + app.listen(port, () => { console.log(`updown listening on port ${port}`) }) diff --git a/views/updown.pug b/views/updown.pug index 6e69d4a..c81c2bc 100644 --- a/views/updown.pug +++ b/views/updown.pug @@ -2,23 +2,26 @@ doctype html html head(lang="en") title Updown + link(rel='stylesheet', href='/updown.css', type='text/css') body - div(align='center') - p= message - table(align='center') - form(method='get', action='') - tr - th - a(href='?sort=name') Uploaded File(s) - th - a(href='?sort=size') Size - th - th + div#message.table + div= message + div#f-upload.table + form#upload-form(method='post', enctype='multipart/form-data', action='/upload/') + label(for='up') Upload File + input#up(type='file', name='up') + button(type='submit') Submit + div#flist.table + div.tr#flist-heading + div.td + a(href='?sort=name') Uploaded File(s) + div.td + a(href='?sort=size') Size each file in files - tr - td= file.name - td= file.size - td - a(href='?download='+file.name alt="download") 🠳 - td - a(href='?delete='+file.name alt="delete") 🞬 + div.tr + div.td= file.name + div.td= file.size + div.td + a(href='/download/'+file.name alt="download file") 🠳 + div.td + a(href='/delete/'+file.name alt="delete file") 🞬