From 37ffb3144c3957cca0988e2fca1244635c6a3eaa Mon Sep 17 00:00:00 2001 From: Robbie Antenesse Date: Fri, 4 Jan 2019 13:46:41 -0700 Subject: [PATCH] Add a simple backup/restore page --- .gitignore | 1 + package.json | 1 + server.js | 104 ++++++++++++++++++++++- settings.json | 3 +- templates/pages/backup.html | 72 ++++++++++++++++ yarn.lock | 160 +++++++++++++++++++++++++++++++++++- 6 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 templates/pages/backup.html diff --git a/.gitignore b/.gitignore index 4728c4b..ca1dfa9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ public/files/*.epub public/files/*.mobi public/files/*.pdf public/files/*.json +public/files/*.zip public/history/*.json diff --git a/package.json b/package.json index 3c42c64..211a9f5 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "filenamify": "^2.1.0", "helmet": "^3.15.0", "jquery": "^3.3.1", + "onezip": "^3.1.1", "snarkdown": "^1.2.2", "socket.io": "^2.2.0", "socket.io-client": "^2.2.0", diff --git a/server.js b/server.js index 66cb691..ae26b73 100644 --- a/server.js +++ b/server.js @@ -32,11 +32,12 @@ function Server () { this.server.use(bodyParser.json()); // support json encoded bodies this.server.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies - this.server.use(fileUpload({ // support file uploads + this.server.use('/give', fileUpload({ // support file uploads limits: { fileSize: (settings.maxFileSize > 0 ? settings.maxFileSize * 1024 * 1024 : Infinity), // filesize in bytes (settings accepts MB) }, })); + this.server.use('/backup', fileUpload()); // Allow file upload on backup with no limits. this.server.use('/files', express.static(path.join(__dirname, './public/files/'))); this.server.use('/css', express.static(path.resolve('./node_modules/bulma/css/'))); @@ -136,6 +137,107 @@ function Server () { } }); + this.server.get('/backup', (req, res) => { + if (req.query.pass === settings.backupPassword) { + const templateValues = {}; + let html = this.fillTemplate('./templates/pages/backup.html', templateValues); + + if (req.query.dl && ['files', 'history'].includes(req.query.dl)) { + const onezip = require('onezip'); + const { dl } = req.query; + const saveLocation = path.resolve(this.fileLocation, dl + 'Backup.zip'); + const backupLocation = dl === 'history' ? this.historyLocation : this.fileLocation; + const files = fs.readdirSync(backupLocation).filter(fileName => !fileName.includes('.zip')); + onezip.pack(backupLocation, saveLocation, files) + .on('start', () => { + console.info('Starting a backup zip of ' + dl) + }) + .on('error', (error) => { + console.error(error); + templateValues[dl + 'Download'] = 'Something went wrong: ' + JSON.stringify(error); + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + }) + .on('end', () => { + console.log('Backup complete. Saved to ' + saveLocation); + let backupLocation = saveLocation.replace(/\\/g, '/'); + backupLocation = backupLocation.substr(backupLocation.lastIndexOf('/')); + templateValues[dl + 'Download'] = 'Download (This will be removed from the server in 1 hour)'; + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + console.log('Will delete ' + saveLocation + ' in 1 hour'); + setTimeout(() => { + fs.unlink(saveLocation, (err) => { + if (err) { + console.error(err); + } else { + console.log('Deleted backup file ' + saveLocation); + } + }) + }, 60 * 60 * 1000); + }); + } else { + res.send(html); + } + } else { + res.status(400).send(); + } + }); + this.server.post('/backup', (req, res) => { + if (req.query.pass === settings.backupPassword) { + const templateValues = {}; + let html = this.fillTemplate('./templates/pages/backup.html', templateValues); + + const { files } = req; + if (Object.keys(files).length > 0) { + const backupType = Object.keys(files)[0]; + if (['files', 'history'].includes(backupType)) { + const onezip = require('onezip'); + const uploadPath = path.resolve('./', backupType + 'UploadedBackup.zip'); + files[backupType].mv(uploadPath, (err) => { + if (err) { + console.error(error); + templateValues[backupType + 'UploadSuccess'] = 'Could not upload the file.'; + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + } else { + onezip.extract(uploadPath, path.resolve('./public', backupType)) + .on('start', () => { + console.info('Extracting file ' + uploadPath) + }) + .on('error', (error) => { + console.error(error); + templateValues[backupType + 'UploadSuccess'] = 'Something went wrong: ' + JSON.stringify(error); + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + }) + .on('end', () => { + templateValues[backupType + 'UploadSuccess'] = 'Uploaded Successfully!'; + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + fs.unlink(uploadPath, (err) => { + if (err) { + console.error(err); + } else { + console.log('Deleted backup file ' + uploadPath); + } + }) + }); + } + }); + } else { + templateValues['generalError'] = '

' + backupType + ' is not a valid backup type.

'; + html = this.fillTemplate('./templates/pages/backup.html', templateValues); + res.send(html); + } + } else { + res.send(html); + } + } else { + res.status(400).send(); + } + }); + this.io.on('connection', socket => { this.connections++; this.io.emit('update visitors', this.connections); diff --git a/settings.json b/settings.json index 27e9095..913434a 100644 --- a/settings.json +++ b/settings.json @@ -7,5 +7,6 @@ "maxLibrarySize": 0, "maxFileSize": 0, "maxHistory": 0, - "allowedFormats": [".epub", ".mobi", ".pdf"] + "allowedFormats": [".epub", ".mobi", ".pdf"], + "backupPassword": "password" } \ No newline at end of file diff --git a/templates/pages/backup.html b/templates/pages/backup.html new file mode 100644 index 0000000..3ba496d --- /dev/null +++ b/templates/pages/backup.html @@ -0,0 +1,72 @@ + + + + Backup + + +

Backup/Restore

+

+ This page allows you to download a .zip file of your Files folder and your History + folder or re-import one of the zipped files you received from a backup. +

+ +

Export

+

+ + + {{filesDownload}} +

+

+ + + {{historyDownload}} +

+ +

Import

+{{generalError}} +
+

+
+ + {{filesUploadSuccess}} +

+

+ +

+
+
+

+
+ + {{historyUploadSuccess}} +

+

+ +

+
+ + + + \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 9877712..d25c84e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,11 @@ backo2@1.0.2: resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -73,6 +78,19 @@ body-parser@1.18.3, body-parser@^1.18.3: raw-body "2.3.3" type-is "~1.6.16" +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= + bulma@^0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/bulma/-/bulma-0.7.2.tgz#8e944377b74c7926558830d38d8e19eaf49f5fb6" @@ -96,6 +114,11 @@ callsite@1.0.0: resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" integrity sha1-KAOY5dZkvXQDi28JBRU+borxvCA= +camelcase@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" + integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== + camelize@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" @@ -121,6 +144,11 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + content-disposition@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" @@ -182,6 +210,11 @@ debug@~4.1.0: dependencies: ms "^2.1.1" +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -325,6 +358,13 @@ express@^4.16.4: utils-merge "1.0.1" vary "~1.1.2" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + feature-policy@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.2.0.tgz#22096de49ab240176878ffe2bde2f6ff04d48c43" @@ -362,6 +402,11 @@ finalhandler@1.1.1: statuses "~1.4.0" unpipe "~1.0.0" +findit2@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6" + integrity sha1-WKRmaX34piBc39vzlVNri9d3pfY= + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -377,6 +422,23 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +glob@^7.0.0: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + has-binary2@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.3.tgz#7776ac627f3ea77250cfc332dab7ddf5e4f5d11d" @@ -466,7 +528,15 @@ indexof@0.0.1: resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= -inherits@2.0.3, inherits@~2.0.1: +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -537,6 +607,25 @@ mime@1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + modify-filename@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1" @@ -574,6 +663,27 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onezip@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/onezip/-/onezip-3.1.1.tgz#160f5369c1c943976d6576f8534855d6a1082981" + integrity sha512-a0n77aH4Posl6XDU3QAJgd5KPoL0RIEGnmU0xrvpgDfJBVKL7btNQ+dTTVO3KYdA6lnsrmjiKd+W2PRToJbJJQ== + dependencies: + findit2 "^2.2.3" + glob "^7.0.0" + mkdirp "^0.5.1" + pipe-io "^3.0.0" + try-to-catch "^1.0.2" + yargs-parser "^11.0.0" + yauzl "^2.6.0" + yazl "^2.4.1" + parseqs@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" @@ -598,11 +708,26 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +pipe-io@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/pipe-io/-/pipe-io-3.0.11.tgz#61cf893c7887908397927675a1fb5188fffbc9c5" + integrity sha512-Mj9M+vdptBRBqHOf52kIDVeKVb2gLHuuqQyTemBfaK8J+5tdQhIL4RFP3bQ15G7+2Ib/RCHBinnRW7TJz4VuaA== + platform@1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.5.tgz#fb6958c696e07e2918d2eeda0f0bc9448d733444" @@ -795,6 +920,11 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +try-to-catch@^1.0.2: + version "1.1.1" + resolved "https://registry.yarnpkg.com/try-to-catch/-/try-to-catch-1.1.1.tgz#770162dd13b9a0e55da04db5b7f888956072038a" + integrity sha512-ikUlS+/BcImLhNYyIgZcEmq4byc31QpC+46/6Jm5ECWkVFhf8SM2Fp/0pMVXPX6vk45SMCwrP4Taxucne8I0VA== + type-is@~1.6.16: version "1.6.16" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" @@ -826,6 +956,11 @@ vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + ws@~6.1.0: version "6.1.2" resolved "https://registry.yarnpkg.com/ws/-/ws-6.1.2.tgz#3cc7462e98792f0ac679424148903ded3b9c3ad8" @@ -843,6 +978,29 @@ xmlhttprequest-ssl@~1.5.4: resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e" integrity sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4= +yargs-parser@^11.0.0: + version "11.1.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" + integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== + dependencies: + camelcase "^5.0.0" + decamelize "^1.2.0" + +yauzl@^2.6.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + +yazl@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" + integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + dependencies: + buffer-crc32 "~0.2.3" + yeast@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"