diff --git a/client/homebrew/pages/sharePage/sharePage.jsx b/client/homebrew/pages/sharePage/sharePage.jsx
index a1490ed28..529c0d9ee 100644
--- a/client/homebrew/pages/sharePage/sharePage.jsx
+++ b/client/homebrew/pages/sharePage/sharePage.jsx
@@ -63,6 +63,9 @@ const SharePage = createClass({
source
+
+ download
+
diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.jsx b/client/homebrew/pages/userPage/brewItem/brewItem.jsx
index b7b13ff4c..b24bad363 100644
--- a/client/homebrew/pages/userPage/brewItem/brewItem.jsx
+++ b/client/homebrew/pages/userPage/brewItem/brewItem.jsx
@@ -78,6 +78,19 @@ const BrewItem = createClass({
;
},
+ renderDownloadLink : function(){
+ if(!this.props.brew.shareId) return;
+
+ let shareLink = this.props.brew.shareId;
+ if(this.props.brew.googleId) {
+ shareLink = this.props.brew.googleId + shareLink;
+ }
+
+ return
+
+ ;
+ },
+
renderGoogleDriveIcon : function(){
if(!this.props.brew.gDrive) return;
@@ -109,6 +122,7 @@ const BrewItem = createClass({
{this.renderShareLink()}
{this.renderEditLink()}
+ {this.renderDownloadLink()}
{this.renderDeleteBrewLink()}
;
diff --git a/client/homebrew/pages/userPage/brewItem/brewItem.less b/client/homebrew/pages/userPage/brewItem/brewItem.less
index 6ab0a893c..efae9d8d3 100644
--- a/client/homebrew/pages/userPage/brewItem/brewItem.less
+++ b/client/homebrew/pages/userPage/brewItem/brewItem.less
@@ -7,7 +7,7 @@
box-sizing : border-box;
overflow : hidden;
width : 48%;
- min-height : 80px;
+ min-height : 105px;
margin-right : 15px;
margin-bottom : 15px;
padding : 5px 15px 5px 8px;
diff --git a/package-lock.json b/package-lock.json
index 9788cc675..c4e43b468 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6592,6 +6592,14 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sanitize-filename": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
+ "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
+ "requires": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
"saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
@@ -7333,6 +7341,14 @@
"nopt": "~1.0.10"
}
},
+ "truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
+ "requires": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -7589,6 +7605,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
+ "utf8-byte-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
+ "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
+ },
"util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
diff --git a/package.json b/package.json
index 5690fe334..881578590 100644
--- a/package.json
+++ b/package.json
@@ -67,6 +67,7 @@
"react-dom": "^16.14.0",
"react-frame-component": "4.1.3",
"react-router-dom": "5.2.0",
+ "sanitize-filename": "1.6.3",
"superagent": "^6.1.0",
"vitreum": "github:calculuschild/vitreum#21a8e1c9421f1d3a3b474c12f480feb2fbd28c5b"
},
diff --git a/server.js b/server.js
index ca2f6f17f..2b460ccf2 100644
--- a/server.js
+++ b/server.js
@@ -1,4 +1,4 @@
-/*eslint max-lines: ["warn", {"max": 250, "skipBlankLines": true, "skipComments": true}]*/
+/*eslint max-lines: ["warn", {"max": 300, "skipBlankLines": true, "skipComments": true}]*/
const _ = require('lodash');
const jwt = require('jwt-simple');
const express = require('express');
@@ -7,6 +7,7 @@ const app = express();
const homebrewApi = require('./server/homebrew.api.js');
const GoogleActions = require('./server/googleActions.js');
const serveCompressedStaticAssets = require('./server/static-assets.mv.js');
+const sanitizeFilename = require('sanitize-filename');
app.use('/', serveCompressedStaticAssets(`${__dirname}/build`));
@@ -64,6 +65,7 @@ app.get('/robots.txt', (req, res)=>{
return res.sendFile(`${__dirname}/robots.txt`);
});
+
//Source page
app.get('/source/:id', (req, res)=>{
if(req.params.id.length > 12) {
@@ -71,8 +73,13 @@ app.get('/source/:id', (req, res)=>{
const shareId = req.params.id.slice(-12);
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
.then((brew)=>{
- const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
- return res.send(`${text}`);
+ const replaceStrings = { '&': '&', '<': '<', '>': '>' };
+ let text = brew.text;
+ for (const replaceStr in replaceStrings) {
+ text = text.replaceAll(replaceStr, replaceStrings[replaceStr]);
+ }
+ text = `${text}`;
+ res.status(200).send(text);
})
.catch((err)=>{
console.log(err);
@@ -80,14 +87,60 @@ app.get('/source/:id', (req, res)=>{
});
} else {
HomebrewModel.get({ shareId: req.params.id })
- .then((brew)=>{
- const text = brew.text.replaceAll('<', '<').replaceAll('>', '>');
- return res.send(`${text}`);
- })
- .catch((err)=>{
- console.log(err);
- return res.status(404).send('Could not find Homebrew with that id');
+ .then((brew)=>{
+ const replaceStrings = { '&': '&', '<': '<', '>': '>' };
+ let text = brew.text;
+ for (const replaceStr in replaceStrings) {
+ text = text.replaceAll(replaceStr, replaceStrings[replaceStr]);
+ }
+ text = `${text}`;
+ res.status(200).send(text);
+ })
+ .catch((err)=>{
+ console.log(err);
+ return res.status(404).send('Could not find Homebrew with that id');
+ });
+ }
+});
+
+//Download brew source page
+app.get('/download/:id', (req, res)=>{
+ const prefix = 'HB - ';
+
+ if(req.params.id.length > 12) {
+ const googleId = req.params.id.slice(0, -12);
+ const shareId = req.params.id.slice(-12);
+ GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
+ .then((brew)=>{
+ let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
+ if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
+ res.set({
+ 'Cache-Control' : 'no-cache',
+ 'Content-Type' : 'text/plain',
+ 'Content-Disposition' : `attachment; filename="${fileName}.txt"`
});
+ res.status(200).send(brew.text);
+ })
+ .catch((err)=>{
+ console.log(err);
+ return res.status(400).send('Can\'t get brew from Google');
+ });
+ } else {
+ HomebrewModel.get({ shareId: req.params.id })
+ .then((brew)=>{
+ let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', '');
+ if(!fileName || !fileName.length) { fileName = `${prefix}-Untitled-Brew`; };
+ res.set({
+ 'Cache-Control' : 'no-cache',
+ 'Content-Type' : 'text/plain',
+ 'Content-Disposition' : `attachment; filename="${fileName}.txt"`
+ });
+ res.status(200).send(brew.text);
+ })
+ .catch((err)=>{
+ console.log(err);
+ return res.status(404).send('Could not find Homebrew with that id');
+ });
}
});
diff --git a/server/googleActions.js b/server/googleActions.js
index 1a8d656ac..bd288ceea 100644
--- a/server/googleActions.js
+++ b/server/googleActions.js
@@ -259,8 +259,12 @@ GoogleActions = {
throw ('Share ID does not match');
}
- //Access actual file with service account. Just api key is causing "automated query" errors.
- const keys = JSON.parse(config.get('service_account'));
+ //Access file using service account. Using API key only causes "automated query" lockouts after a while.
+
+ const keys = typeof(config.get('service_account')) == 'string' ?
+ JSON.parse(config.get('service_account')) :
+ config.get('service_account');
+
const serviceAuth = google.auth.fromJSON(keys);
serviceAuth.scopes = ['https://www.googleapis.com/auth/drive'];