diff --git a/package-lock.json b/package-lock.json index 3d81a61b1..8f1e65e49 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3869,6 +3869,11 @@ } } }, + "express-async-handler": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.1.4.tgz", + "integrity": "sha512-HdmbVF4V4w1q/iz++RV7bUxIeepTukWewiJGkoCKQMtvPF11MLTa7It9PRc/reysXXZSEyD4Pthchju+IUbMiQ==" + }, "express-static-gzip": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.1.tgz", diff --git a/package.json b/package.json index bc2cf0aeb..fa4beba36 100644 --- a/package.json +++ b/package.json @@ -49,17 +49,18 @@ "cookie-parser": "^1.4.5", "create-react-class": "^15.7.0", "express": "^4.17.1", + "express-async-handler": "^1.1.4", "express-static-gzip": "2.1.1", "fs-extra": "9.1.0", "googleapis": "67.1.1", "jwt-simple": "^0.5.6", "less": "^3.13.1", "lodash": "^4.17.21", + "marked": "2.0.1", + "markedLegacy": "npm:marked@^0.3.19", "moment": "^2.29.1", "mongoose": "^5.12.0", "nanoid": "3.1.21", - "markedLegacy": "npm:marked@^0.3.19", - "marked": "2.0.1", "nconf": "^0.11.2", "prop-types": "15.7.2", "query-string": "6.14.1", diff --git a/server.js b/server.js index 2b460ccf2..c5861238e 100644 --- a/server.js +++ b/server.js @@ -8,6 +8,23 @@ 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'); +const asyncHandler = require('express-async-handler'); + +//Get the brew object from the HB database or Google Drive +const getBrewFromId = asyncHandler(async (id, accessType)=>{ + if(accessType !== 'edit' && accessType !== 'share') + throw ('Invalid Access Type when getting brew'); + let brew; + if(id.length > 12) { + const googleId = id.slice(0, -12); + id = id.slice(-12); + brew = await GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, id, accessType); + } else { + brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id }); + brew.sanatize(true); + } + return brew; +}); app.use('/', serveCompressedStaticAssets(`${__dirname}/build`)); @@ -65,84 +82,33 @@ 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) { - 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)=>{ - 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(400).send('Can\'t get brew from Google'); - }); - } else { - HomebrewModel.get({ shareId: req.params.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'); - }); +app.get('/source/:id', asyncHandler(async (req, res)=>{ + const brew = await getBrewFromId(req.params.id, 'share'); + + const replaceStrings = { '&': '&', '<': '<', '>': '>' }; + let text = brew.text; + for (const replaceStr in replaceStrings) { + text = text.replaceAll(replaceStr, replaceStrings[replaceStr]); } -}); + text = `
${text}
`; + res.status(200).send(text); +})); //Download brew source page -app.get('/download/:id', (req, res)=>{ +app.get('/download/:id', asyncHandler(async (req, res)=>{ + const brew = await getBrewFromId(req.params.id, 'share'); 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'); - }); - } -}); + 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); +})); //User Page app.get('/user/:username', async (req, res, next)=>{ @@ -170,123 +136,45 @@ app.get('/user/:username', async (req, res, next)=>{ }); //Edit Page -app.get('/edit/:id', (req, res, next)=>{ +app.get('/edit/:id', asyncHandler(async (req, res, next)=>{ res.header('Cache-Control', 'no-cache, no-store'); //reload the latest saved brew when pressing back button, not the cached version before save. - if(req.params.id.length > 12) { - const googleId = req.params.id.slice(0, -12); - const editId = req.params.id.slice(-12); - GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, editId, 'edit') - .then((brew)=>{ - req.brew = brew; //TODO Need to sanitize later - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send('Can\'t get brew from Google'); - }); - } else { - HomebrewModel.get({ editId: req.params.id }) - .then((brew)=>{ - req.brew = brew.sanatize(); - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send(`Can't get that`); - }); - } -}); + const brew = await getBrewFromId(req.params.id, 'edit'); + req.brew = brew; + return next(); +})); //New Page -app.get('/new/:id', (req, res, next)=>{ - 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)=>{ - req.brew = brew; //TODO Need to sanitize later - return next(); - }) - .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)=>{ - req.brew = brew; - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send(`Can't get that`); - }); - } -}); +app.get('/new/:id', asyncHandler(async (req, res, next)=>{ + const brew = await getBrewFromId(req.params.id, 'share'); + req.brew = brew; + return next(); +})); //Share Page -app.get('/share/:id', (req, res, next)=>{ +app.get('/share/:id', asyncHandler(async (req, res, next)=>{ + const brew = await getBrewFromId(req.params.id, 'share'); + 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)=>{ - GoogleActions.increaseView(googleId, shareId, 'share', brew); - return brew; - }) - .then((brew)=>{ - req.brew = brew; //TODO Need to sanitize later - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send('Can\'t get brew from Google'); - }); + await GoogleActions.increaseView(googleId, shareId, 'share', brew) + .catch((err)=>{next(err);}); } else { - HomebrewModel.get({ shareId: req.params.id }) - .then((brew)=>{ - return brew.increaseView(); - }) - .then((brew)=>{ - req.brew = brew.sanatize(true); - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send(`Can't get that`); - }); + await brew.increaseView(); } -}); + + req.brew = brew; + return next(); +})); //Print Page -app.get('/print/:id', (req, res, next)=>{ - 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)=>{ - req.brew = brew; //TODO Need to sanitize later - return next(); - }) - .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)=>{ - req.brew = brew.sanatize(true); - return next(); - }) - .catch((err)=>{ - console.log(err); - return res.status(400).send(`Can't get that`); - }); - } -}); +app.get('/print/:id', asyncHandler(async (req, res, next)=>{ + const brew = await getBrewFromId(req.params.id, 'share'); + req.brew = brew; + return next(); +})); //Render the page -//const render = require('.build/render'); const templateFn = require('./client/template.js'); app.use((req, res)=>{ const props = { @@ -303,11 +191,35 @@ app.use((req, res)=>{ templateFn('homebrew', title = req.brew ? req.brew.title : '', props) .then((page)=>{ res.send(page); }) .catch((err)=>{ + console.log('TEMPLATE ERROR'); console.log(err); return res.sendStatus(500); }); }); +//v=====----- Error-Handling Middleware -----=====v// +//Format Errors so all fields will be sent +const replaceErrors = (key, value)=>{ + if(value instanceof Error) { + const error = {}; + Object.getOwnPropertyNames(value).forEach(function (key) { + error[key] = value[key]; + }); + return error; + } + return value; +}; + +const getPureError = (error)=>{ + return JSON.parse(JSON.stringify(error, replaceErrors)); +}; + +app.use((err, req, res, next)=>{ + const status = err.status || 500; + console.error(err); + res.status(status).send(getPureError(err)); +}); +//^=====--------------------------------------=====^// const PORT = process.env.PORT || config.get('web_port') || 8000; app.listen(PORT); diff --git a/server/googleActions.js b/server/googleActions.js index 33fc65791..184eac2f5 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -240,6 +240,7 @@ GoogleActions = { }, readFileMetadata : async (auth, id, accessId, accessType)=>{ + const drive = google.drive({ version: 'v3', auth: auth }); const obj = await drive.files.get({ @@ -248,7 +249,7 @@ GoogleActions = { }) .catch((err)=>{ console.log('Error loading from Google'); - console.error(err); + throw (err); return; }); @@ -345,7 +346,10 @@ GoogleActions = { increaseView : async (id, accessId, accessType, brew)=>{ //service account because this is modifying another user's file properties //so we need extended scope - const keys = JSON.parse(config.get('service_account')); + const keys = typeof(config.get('service_account')) == 'string' ? + JSON.parse(config.get('service_account')) : + config.get('service_account'); + const auth = google.auth.fromJSON(keys); auth.scopes = ['https://www.googleapis.com/auth/drive'];