diff --git a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx index f76d14ebe..b1d30f6f3 100644 --- a/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx +++ b/client/homebrew/pages/basePages/listPage/brewItem/brewItem.jsx @@ -16,8 +16,8 @@ const BrewItem = createClass({ brew : { title : '', description : '', - - authors : [] + authors : [], + stubbed : true } }; }, @@ -50,7 +50,7 @@ const BrewItem = createClass({ if(!this.props.brew.editId) return; let editLink = this.props.brew.editId; - if(this.props.brew.googleId) { + if(this.props.brew.googleId && !this.props.brew.stubbed) { editLink = this.props.brew.googleId + editLink; } @@ -63,7 +63,7 @@ const BrewItem = createClass({ if(!this.props.brew.shareId) return; let shareLink = this.props.brew.shareId; - if(this.props.brew.googleId) { + if(this.props.brew.googleId && !this.props.brew.stubbed) { shareLink = this.props.brew.googleId + shareLink; } @@ -86,7 +86,7 @@ const BrewItem = createClass({ }, renderGoogleDriveIcon : function(){ - if(!this.props.brew.gDrive) return; + if(!this.props.brew.googleId) return; return googleDriveIcon @@ -104,8 +104,8 @@ const BrewItem = createClass({
- - {brew.authors.join(', ')} + + {brew.authors?.join(', ')}
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx index fa985b7bf..969b80f49 100644 --- a/client/homebrew/pages/editPage/editPage.jsx +++ b/client/homebrew/pages/editPage/editPage.jsx @@ -211,7 +211,7 @@ const EditPage = createClass({ this.savedBrew = res.body; if(transfer) { - history.replaceState(null, null, `/edit/${this.savedBrew.googleId ?? ''}${this.savedBrew.editId}`); + history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); } this.setState((prevState)=>({ diff --git a/client/homebrew/pages/newPage/newPage.jsx b/client/homebrew/pages/newPage/newPage.jsx index b57da013a..b99d6f85a 100644 --- a/client/homebrew/pages/newPage/newPage.jsx +++ b/client/homebrew/pages/newPage/newPage.jsx @@ -174,7 +174,7 @@ const NewPage = createClass({ localStorage.removeItem(BREWKEY); localStorage.removeItem(STYLEKEY); localStorage.removeItem(METAKEY); - window.location = `/edit/${brew.googleId ?? ''}${brew.editId}`; + window.location = `/edit/${brew.editId}`; }, renderSaveButton : function(){ diff --git a/server/app.js b/server/app.js index de51c8882..79cda797b 100644 --- a/server/app.js +++ b/server/app.js @@ -9,48 +9,14 @@ const yaml = require('js-yaml'); const app = express(); const config = require('./config.js'); -const homebrewApi = require('./homebrew.api.js'); +const { homebrewApi, getBrew } = require('./homebrew.api.js'); const GoogleActions = require('./googleActions.js'); const serveCompressedStaticAssets = require('./static-assets.mv.js'); const sanitizeFilename = require('sanitize-filename'); const asyncHandler = require('express-async-handler'); -const brewAccessTypes = ['edit', 'share', 'raw']; - -//Get the brew object from the HB database or Google Drive -const getBrewFromId = asyncHandler(async (id, accessType)=>{ - if(!brewAccessTypes.includes(accessType)) - 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.getGoogleBrew(googleId, id, accessType); - } else { - brew = await HomebrewModel.get(accessType == 'edit' ? { editId: id } : { shareId: id }); - brew = brew.toObject(); // Convert MongoDB object to standard Javascript Object - } - - brew = sanitizeBrew(brew, accessType === 'edit' ? false : true); - //Split brew.text into text and style - //unless the Access Type is RAW, in which case return immediately - if(accessType == 'raw') { - return brew; - } - splitTextStyleAndMetadata(brew); - return brew; -}); - -const sanitizeBrew = (brew, full=false)=>{ - delete brew._id; - delete brew.__v; - if(full){ - delete brew.editId; - } - return brew; -}; - -const splitTextStyleAndMetadata = (brew)=>{ +const splitTextStyleAndMetadata = (req, res, next)=>{ + const { brew } = req; brew.text = brew.text.replaceAll('\r\n', '\n'); if(brew.text.startsWith('```metadata')) { const index = brew.text.indexOf('```\n\n'); @@ -64,6 +30,24 @@ const splitTextStyleAndMetadata = (brew)=>{ brew.style = brew.text.slice(7, index - 1); brew.text = brew.text.slice(index + 5); } + req.brew = brew; + return next(); +}; + +const sanitizeBrew = (brew, full = false)=>{ + delete brew._id; + delete brew.__v; + if(full){ + delete brew.editId; + } + return brew; +}; + +const sanitizeMiddleware = (accessType)=>{ + return (req, res, next)=>{ + req.brew = sanitizeBrew(req.brew, accessType !== 'edit'); + return next(); + }; }; app.use('/', serveCompressedStaticAssets(`build`)); @@ -108,63 +92,54 @@ app.get('/robots.txt', (req, res)=>{ }); //Home page -app.get('/', async (req, res, next)=>{ - const brew = { +app.get('/', (req, res, next)=>{ + req.brew = { text : welcomeText }; - req.brew = brew; return next(); }); //Home page v3 -app.get('/v3_preview', async (req, res, next)=>{ - const brew = { +app.get('/v3_preview', (req, res, next)=>{ + req.brew = { text : welcomeTextV3, renderer : 'V3' }; - splitTextStyleAndMetadata(brew); - req.brew = brew; return next(); -}); +}, splitTextStyleAndMetadata); //Legacy/Other Document -> v3 Migration Guide -app.get('/migrate', async (req, res, next)=>{ - const brew = { +app.get('/migrate', (req, res, next)=>{ + req.brew = { text : migrateText, renderer : 'V3' }; - splitTextStyleAndMetadata(brew); - req.brew = brew; return next(); -}); +}, splitTextStyleAndMetadata); //Changelog page app.get('/changelog', async (req, res, next)=>{ - const brew = { + req.brew = { title : 'Changelog', text : changelogText, renderer : 'V3' }; - splitTextStyleAndMetadata(brew); - req.brew = brew; return next(); -}); +}, splitTextStyleAndMetadata); //FAQ page app.get('/faq', async (req, res, next)=>{ - const brew = { + req.brew = { title : 'FAQ', text : faqText, renderer : 'V3' }; - splitTextStyleAndMetadata(brew); - req.brew = brew; return next(); -}); +}, splitTextStyleAndMetadata); //Source page -app.get('/source/:id', asyncHandler(async (req, res)=>{ - const brew = await getBrewFromId(req.params.id, 'raw'); +app.get('/source/:id', asyncHandler(getBrew('share')), (req, res)=>{ + const { brew } = req; const replaceStrings = { '&': '&', '<': '<', '>': '>' }; let text = brew.text; @@ -173,11 +148,11 @@ app.get('/source/:id', asyncHandler(async (req, res)=>{ } text = `
${text}
`; res.status(200).send(text); -})); +}); //Download brew source page -app.get('/download/:id', asyncHandler(async (req, res)=>{ - const brew = await getBrewFromId(req.params.id, 'raw'); +app.get('/download/:id', asyncHandler(getBrew('share')), sanitizeMiddleware('share'), (req, res)=>{ + const { brew } = req; const prefix = 'HB - '; let fileName = sanitizeFilename(`${prefix}${brew.title}`).replaceAll(' ', ''); @@ -188,7 +163,7 @@ app.get('/download/:id', asyncHandler(async (req, res)=>{ 'Content-Disposition' : `attachment; filename="${fileName}.txt"` }); res.status(200).send(brew.text); -})); +}); //User Page app.get('/user/:username', async (req, res, next)=>{ @@ -220,44 +195,35 @@ app.get('/user/:username', async (req, res, next)=>{ }); //Edit Page -app.get('/edit/:id', asyncHandler(async (req, res, next)=>{ +app.get('/edit/:id', asyncHandler(getBrew('edit')), sanitizeMiddleware('edit'), splitTextStyleAndMetadata, (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. - const brew = await getBrewFromId(req.params.id, 'edit'); - req.brew = brew; return next(); -})); +}); //New Page -app.get('/new/:id', asyncHandler(async (req, res, next)=>{ - const brew = await getBrewFromId(req.params.id, 'share'); - brew.title = `CLONE - ${brew.title}`; - req.brew = brew; +app.get('/new/:id', asyncHandler(getBrew('share')), sanitizeMiddleware('share'), splitTextStyleAndMetadata, (req, res, next)=>{ + req.brew.title = `CLONE - ${brew.title}`; return next(); -})); +}); //Share Page -app.get('/share/:id', asyncHandler(async (req, res, next)=>{ - const brew = await getBrewFromId(req.params.id, 'share'); +app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ + const { brew } = req; - if(req.params.id.length > 12) { + if(req.params.id.length > 12 && !brew._id) { const googleId = req.params.id.slice(0, -12); const shareId = req.params.id.slice(-12); await GoogleActions.increaseView(googleId, shareId, 'share', brew) - .catch((err)=>{next(err);}); + .catch((err)=>{next(err);}); } else { await HomebrewModel.increaseView({ shareId: brew.shareId }); } - req.brew = brew; return next(); -})); +}), sanitizeMiddleware('share'), splitTextStyleAndMetadata); //Print Page -app.get('/print/:id', asyncHandler(async (req, res, next)=>{ - const brew = await getBrewFromId(req.params.id, 'share'); - req.brew = brew; - return next(); -})); +app.get('/print/:id', asyncHandler(getBrew('share')), sanitizeMiddleware('share'), splitTextStyleAndMetadata); //Render the page const templateFn = require('./../client/template.js'); diff --git a/server/googleActions.js b/server/googleActions.js index 3a202cbce..feb0db401 100644 --- a/server/googleActions.js +++ b/server/googleActions.js @@ -141,13 +141,7 @@ const GoogleActions = { name : `${brew.title}.txt`, description : `${brew.description}`, properties : { - title : brew.title, - published : brew.published, - version : brew.version, - renderer : brew.renderer, - tags : brew.tags, - pageCount : brew.pageCount, - systems : brew.systems.join() + title : brew.title } }, media : { @@ -159,10 +153,9 @@ const GoogleActions = { console.log('Error saving to google'); console.error(err); throw (err); - //return res.status(500).send('Error while saving'); }); - return (brew); + return true; }, newGoogleBrew : async (auth, brew)=>{ @@ -180,12 +173,9 @@ const GoogleActions = { 'description' : `${brew.description}`, 'parents' : [folderId], 'properties' : { //AppProperties is not accessible - 'shareId' : brew.shareId || nanoid(12), - 'editId' : brew.editId || nanoid(12), - 'title' : brew.title, - 'views' : '0', - 'pageCount' : brew.pageCount, - 'renderer' : brew.renderer || 'legacy' + 'shareId' : brew.shareId || nanoid(12), + 'editId' : brew.editId || nanoid(12), + 'title' : brew.title } }; @@ -212,26 +202,7 @@ const GoogleActions = { console.error(err); }); - const newHomebrew = { - text : brew.text, - shareId : fileMetadata.properties.shareId, - editId : fileMetadata.properties.editId, - createdAt : new Date(), - updatedAt : new Date(), - gDrive : true, - googleId : obj.data.id, - pageCount : fileMetadata.properties.pageCount, - - title : brew.title, - description : brew.description, - tags : '', - published : brew.published, - renderer : brew.renderer, - authors : [], - systems : [] - }; - - return newHomebrew; + return obj.data.id; }, getGoogleBrew : async (id, accessId, accessType)=>{ @@ -244,7 +215,6 @@ const GoogleActions = { .catch((err)=>{ console.log('Error loading from Google'); throw (err); - return; }); if(obj) { @@ -254,9 +224,7 @@ const GoogleActions = { throw ('Share ID does not match'); } - const serviceDrive = google.drive({ version: 'v3' }); - - const file = await serviceDrive.files.get({ + const file = await drive.files.get({ fileId : id, fields : 'description, properties', alt : 'media' @@ -273,7 +241,7 @@ const GoogleActions = { text : file.data, description : obj.data.description, - tags : obj.data.properties.tags ? obj.data.properties.tags : '', + tags : obj.data.properties.tags ? obj.data.properties.tags : '', systems : obj.data.properties.systems ? obj.data.properties.systems.split(',') : [], authors : [], published : obj.data.properties.published ? obj.data.properties.published == 'true' : false, @@ -287,7 +255,6 @@ const GoogleActions = { version : parseInt(obj.data.properties.version) || 0, renderer : obj.data.properties.renderer ? obj.data.properties.renderer : 'legacy', - gDrive : true, googleId : id }; @@ -295,14 +262,11 @@ const GoogleActions = { } }, - deleteGoogleBrew : async (auth, id)=>{ + deleteGoogleBrew : async (auth, id, accessId)=>{ const drive = google.drive({ version: 'v3', auth }); - const googleId = id.slice(0, -12); - const accessId = id.slice(-12); - const obj = await drive.files.get({ - fileId : googleId, + fileId : id, fields : 'properties' }) .catch((err)=>{ @@ -311,11 +275,11 @@ const GoogleActions = { }); if(obj && obj.data.properties.editId != accessId) { - throw ('Not authorized to delete this Google brew'); + throw { status: 403, message: 'Not authorized to delete this Google brew' }; } await drive.files.update({ - fileId : googleId, + fileId : id, resource : { trashed: true } }) .catch((err)=>{ diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 6faa69719..fbe9c7aaa 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -5,6 +5,8 @@ const zlib = require('zlib'); const GoogleActions = require('./googleActions.js'); const Markdown = require('../shared/naturalcrit/markdown.js'); const yaml = require('js-yaml'); +const asyncHandler = require('express-async-handler'); +const { nanoid } = require('nanoid'); // const getTopBrews = (cb) => { // HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) { @@ -12,6 +14,51 @@ const yaml = require('js-yaml'); // }); // }; +const findBrew = async (brewId, accessType, googleObjectId)=>{ + let id = brewId; + let googleId = googleObjectId; + if(id.length > 12) { + googleId = id.slice(0, -12); + id = id.slice(-12); + } + let brew = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id }) + .catch((err)=>{ + if(googleId) { + console.warn(`Unable to find document stub for ${accessType}Id ${id}`); + } else { + console.warn(err); + } + }); + + let googleBrew; + if(googleId || brew?.googleId) { + googleBrew = await GoogleActions.getGoogleBrew(googleId || brew?.googleId, id, accessType) + .catch((err)=>{ + console.warn(err); + }); + if(!brew && !googleBrew) throw 'Brew not found in database or Google Drive'; + brew = brew ? _.merge(brew, googleBrew) : googleBrew; + } else if(!brew) { + throw 'Brew not found in database'; + } + + return brew; +}; + +const getBrew = (accessType)=>{ + return async (req, res, next)=>{ + const { brew } = req; + + if(!brew) { + const id = req.params.id, googleId = req.body?.googleId; + const found = await findBrew(id, accessType, googleId); + req.brew = accessType !== 'edit' && found.toObject ? found.toObject() : found; + } + + !!next ? next() : undefined; + }; +}; + const mergeBrewText = (brew)=>{ let text = brew.text; if(brew.style !== undefined) { @@ -32,13 +79,21 @@ const MAX_TITLE_LENGTH = 100; const getGoodBrewTitle = (text)=>{ const tokens = Markdown.marked.lexer(text); - return (tokens.find((token)=>token.type == 'heading' || token.type == 'paragraph')?.text || 'No Title') + return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title') .slice(0, MAX_TITLE_LENGTH); }; const excludePropsFromUpdate = (brew)=>{ // Remove undesired properties - const propsToExclude = ['views', 'lastViewed']; + const propsToExclude = ['views', 'lastViewed', 'editId', 'shareId', 'googleId']; + for (const prop of propsToExclude) { + delete brew[prop]; + } + return brew; +}; + +const excludeGoogleProps = (brew)=>{ + const propsToExclude = ['views', 'lastViewed', 'pageCount', 'renderer', 'tags', 'systems', 'published', 'version', 'authors']; for (const prop of propsToExclude) { delete brew[prop]; } @@ -54,28 +109,12 @@ const beforeNewSave = (account, brew)=>{ brew.text = mergeBrewText(brew); }; -const newLocalBrew = async (brew)=>{ - const newHomebrew = new HomebrewModel(brew); - // Compress brew text to binary before saving - newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text); - // Delete the non-binary text field since it's not needed anymore - newHomebrew.text = undefined; - - let saved = await newHomebrew.save() - .catch((err)=>{ - console.error(err, err.toString(), err.stack); - throw `Error while creating new brew, ${err.toString()}`; - }); - - saved = saved.toObject(); - saved.gDrive = false; - return saved; -}; - const newGoogleBrew = async (account, brew, res)=>{ const oAuth2Client = GoogleActions.authCheck(account, res); - return await GoogleActions.newGoogleBrew(oAuth2Client, brew); + const newBrew = excludeGoogleProps(_.clone(brew)); + + return await GoogleActions.newGoogleBrew(oAuth2Client, newBrew); }; const newBrew = async (req, res)=>{ @@ -88,148 +127,178 @@ const newBrew = async (req, res)=>{ beforeNewSave(req.account, brew); - let saved; + const newHomebrew = new HomebrewModel(brew); + newHomebrew.editId = nanoid(12); + newHomebrew.shareId = nanoid(12); + + let googleId, saved; if(transferToGoogle) { - saved = await newGoogleBrew(req.account, brew, res) + googleId = await newGoogleBrew(req.account, newHomebrew, res) .catch((err)=>{ - res.status(err.status || err.response.status).send(err.message || err); + res.status(err?.status || err?.response?.status || 500).send(err?.message || err); }); + newHomebrew.textBin = undefined; + newHomebrew.text = undefined; } else { - saved = await newLocalBrew(brew) - .catch((err)=>{ - res.status(500).send(err); - }); + // Compress brew text to binary before saving + newHomebrew.textBin = zlib.deflateRawSync(newHomebrew.text); + // Delete the non-binary text field since it's not needed anymore + newHomebrew.text = undefined; } + if(transferToGoogle && !googleId) { + if(!res.headersSent) { + res.status(500).send('Unable to save document to Google Drive'); + } + return; + } else if(transferToGoogle) { + newHomebrew.googleId = googleId; + } + + saved = await newHomebrew.save() + .catch((err)=>{ + console.error(err, err.toString(), err.stack); + throw `Error while creating new brew, ${err.toString()}`; + }); if(!saved) return; + saved = saved.toObject(); + return res.status(200).send(saved); }; const updateBrew = async (req, res)=>{ - let brew = excludePropsFromUpdate(req.body); + let brew = Object.assign(req.brew, excludePropsFromUpdate(req.body)); + brew.text = mergeBrewText(brew); const { transferToGoogle, transferFromGoogle } = req.query; - let saved; if(brew.googleId && transferFromGoogle) { - beforeNewSave(req.account, brew); - - saved = await newLocalBrew(brew) + const deleted = await deleteGoogleBrew(req.account, brew.googleId, brew.editId, res) .catch((err)=>{ console.error(err); - res.status(500).send(err); + res.status(err?.status || err?.response?.status || 500).send(err.message || err); }); - if(!saved) return; + if(!deleted) { + if(res.headersSent) { + res.status(500).send('Unable to delete brew from Google'); + } + return; + } - await deleteGoogleBrew(req.account, `${brew.googleId}${brew.editId}`, res) - .catch((err)=>{ - console.error(err); - res.status(err.status || err.response.status).send(err.message || err); - }); + delete brew.googleId; } else if(!brew.googleId && transferToGoogle) { - saved = await newGoogleBrew(req.account, brew, res) + brew.googleId = await newGoogleBrew(req.account, excludeGoogleProps(_.clone(brew)), res) .catch((err)=>{ console.error(err); res.status(err.status || err.response.status).send(err.message || err); }); - if(!saved) return; - - await deleteLocalBrew(req.account, brew.editId) - .catch((err)=>{ - console.error(err); - res.status(err.status).send(err.message); - }); + if(!brew.googleId) { + if(res.headersSent) { + res.status(500).send('Unable to save brew to Google'); + } + return; + } } else if(brew.googleId) { - brew.text = mergeBrewText(brew); - - saved = await GoogleActions.updateGoogleBrew(brew) + const updated = await GoogleActions.updateGoogleBrew(excludeGoogleProps(_.clone(brew))) .catch((err)=>{ console.error(err); - res.status(err.response?.status || 500).send(err); + res.status(err?.response?.status || 500).send(err); }); + if(!updated) { + if(res.headersSent) { + res.status(500).send('Unable to save brew to Google'); + } + return; + } + } + + if(brew.googleId) { + brew.textBin = undefined; + brew.text = undefined; } else { - const dbBrew = await HomebrewModel.get({ editId: req.params.id }) - .catch((err)=>{ - console.error(err); - return res.status(500).send('Error while saving'); - }); - - brew = _.merge(dbBrew, brew); - brew.text = mergeBrewText(brew); - // Compress brew text to binary before saving brew.textBin = zlib.deflateRawSync(brew.text); // Delete the non-binary text field since it's not needed anymore brew.text = undefined; - brew.updatedAt = new Date(); - - if(req.account) { - brew.authors = _.uniq(_.concat(brew.authors, req.account.username)); - } - - brew.markModified('authors'); - brew.markModified('systems'); - - saved = await brew.save(); } - if(!saved) return; + brew.updatedAt = new Date(); + + if(req.account) { + brew.authors = _.uniq(_.concat(brew.authors, req.account.username)); + } + + if(!brew.markModified) { + brew = new HomebrewModel(brew); + } + + brew.markModified('authors'); + brew.markModified('systems'); + + const saved = await brew.save() + .catch((err)=>{ + res.status(err.status || 500).send(err.message || 'Unable to save brew to Homebrewery database'); + }); + if(!saved) { + if(!res.headersSent) { + res.status(500).send('Unable to save brew to Homebrewery database'); + } + } if(!res.headersSent) return res.status(200).send(saved); }; -const deleteBrew = async (req, res)=>{ - if(req.params.id.length > 12) { - const deleted = await deleteGoogleBrew(req.account, req.params.id, res) - .catch((err)=>{ - res.status(500).send(err); - }); - if(deleted) return res.status(200).send(); - } else { - const deleted = await deleteLocalBrew(req.account, req.params.id) - .catch((err)=>{ - res.status(err.status).send(err.message); - }); - if(deleted) return res.status(200).send(deleted); - return res.status(200).send(); - } -}; - -const deleteLocalBrew = async (account, id)=>{ - const brew = await HomebrewModel.findOne({ editId: id }); - if(!brew) { - throw { status: 404, message: 'Can not find homebrew with that id' }; - } - - if(account) { - // Remove current user as author - brew.authors = _.pull(brew.authors, account.username); - brew.markModified('authors'); - } - - if(brew.authors.length === 0) { - // Delete brew if there are no authors left - await brew.remove() - .catch((err)=>{ - console.error(err); - throw { status: 500, message: 'Error while removing' }; - }); - } else { - // Otherwise, save the brew with updated author list - return await brew.save() - .catch((err)=>{ - throw { status: 500, message: err }; - }); - } -}; - -const deleteGoogleBrew = async (account, id, res)=>{ +const deleteGoogleBrew = async (account, id, editId, res)=>{ const auth = await GoogleActions.authCheck(account, res); - await GoogleActions.deleteGoogleBrew(auth, id); + await GoogleActions.deleteGoogleBrew(auth, id, editId); return true; }; -router.post('/api', newBrew); -router.put('/api/:id', updateBrew); -router.put('/api/update/:id', updateBrew); -router.delete('/api/:id', deleteBrew); -router.get('/api/remove/:id', deleteBrew); +const deleteBrew = async (req, res)=>{ + const { brew, account } = req; + if(brew.googleId) { + const googleDeleted = await deleteGoogleBrew(account, brew.googleId, brew.editId, res) + .catch((err)=>{ + res.status(500).send(err); + }); + if(!googleDeleted) { + if(!res.headersSent) { + res.status(500).send('Unable to delete brew from Google'); + } + return; + } + } -module.exports = router; + if(brew._id) { + if(account) { + // Remove current user as author + brew.authors = _.pull(brew.authors, account.username); + brew.markModified('authors'); + } + + if(brew.authors.length === 0) { + // Delete brew if there are no authors left + await brew.remove() + .catch((err)=>{ + console.error(err); + throw { status: 500, message: 'Error while removing' }; + }); + } else { + // Otherwise, save the brew with updated author list + await brew.save() + .catch((err)=>{ + throw { status: 500, message: err }; + }); + } + } + + return res.status(204).send(); +}; + +router.post('/api', asyncHandler(newBrew)); +router.put('/api/:id', asyncHandler(getBrew('edit')), asyncHandler(updateBrew)); +router.put('/api/update/:id', asyncHandler(getBrew('edit')), asyncHandler(updateBrew)); +router.delete('/api/:id', asyncHandler(getBrew('edit')), asyncHandler(deleteBrew)); +router.get('/api/remove/:id', asyncHandler(getBrew('edit')), asyncHandler(deleteBrew)); + +module.exports = { + homebrewApi : router, + getBrew +}; diff --git a/server/homebrew.model.js b/server/homebrew.model.js index acc78a624..785baabe7 100644 --- a/server/homebrew.model.js +++ b/server/homebrew.model.js @@ -6,6 +6,7 @@ const zlib = require('zlib'); const HomebrewSchema = mongoose.Schema({ shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } }, + googleId : { type: String }, title : { type: String, default: '' }, text : { type: String, default: '' }, textBin : { type: Buffer },