diff --git a/client/homebrew/homebrew.jsx b/client/homebrew/homebrew.jsx
index f6dccbdb76..a08a39ea07 100644
--- a/client/homebrew/homebrew.jsx
+++ b/client/homebrew/homebrew.jsx
@@ -9,7 +9,7 @@ const EditPage = require('./pages/editPage/editPage.jsx');
const UserPage = require('./pages/userPage/userPage.jsx');
const SharePage = require('./pages/sharePage/sharePage.jsx');
const NewPage = require('./pages/newPage/newPage.jsx');
-//const ErrorPage = require('./pages/errorPage/errorPage.jsx');
+const ErrorPage = require('./pages/errorPage/errorPage.jsx');
const PrintPage = require('./pages/printPage/printPage.jsx');
const AccountPage = require('./pages/accountPage/accountPage.jsx');
@@ -78,6 +78,7 @@ const Homebrew = createClass({
} />
} />
} />
+ } />
} />
} />
diff --git a/client/homebrew/pages/basePages/uiPage/uiPage.less b/client/homebrew/pages/basePages/uiPage/uiPage.less
index 9780b84ff9..fc5ed583d5 100644
--- a/client/homebrew/pages/basePages/uiPage/uiPage.less
+++ b/client/homebrew/pages/basePages/uiPage/uiPage.less
@@ -1,47 +1,52 @@
-.uiPage{
- .content{
- overflow-y : hidden;
- width : 90vw;
- background-color: #f0f0f0;
- font-family: 'Open Sans';
- margin-left: auto;
- margin-right: auto;
- margin-top: 25px;
- padding: 2% 4%;
- font-size: 0.8em;
- line-height: 1.8em;
- .dataGroup{
- padding: 6px 20px 15px;
- border: 2px solid black;
- border-radius: 5px;
- margin: 5px 0px;
- }
- h1, h2, h3, h4{
- font-weight: 900;
- text-transform: uppercase;
- margin: 0.5em 30% 0.25em 0;
- border-bottom: 2px solid slategrey;
- }
- h1 {
- font-size: 2em;
- border-bottom: 2px solid darkslategrey;
- margin-bottom: 0.5em;
- margin-right: 0;
- }
- h2 {
- font-size: 1.75em;
- }
- h3 {
- font-size: 1.5em;
- svg {
- width: 19px;
+.homebrew {
+ .uiPage.sitePage {
+ .content {
+ width : ~"min(90vw, 1000px)";
+ padding : 2% 4%;
+ margin-top : 25px;
+ margin-right : auto;
+ margin-left : auto;
+ overflow-y : scroll;
+ font-family : 'Open Sans';
+ font-size : 0.8em;
+ line-height : 1.8em;
+ background-color : #F0F0F0;
+ .dataGroup {
+ padding : 6px 20px 15px;
+ margin : 5px 0px;
+ border : 2px solid black;
+ border-radius : 5px;
+ }
+ h1, h2, h3, h4 {
+ width : 100%;
+ margin : 0.5em 30% 0.25em 0;
+ font-weight : 900;
+ text-transform : uppercase;
+ border-bottom : 2px solid slategrey;
+ }
+ h1 {
+ margin-right : 0;
+ margin-bottom : 0.5em;
+ font-size : 2em;
+ border-bottom : 2px solid darkslategrey;
+ }
+ h2 { font-size : 1.75em; }
+ h3 {
+ font-size : 1.5em;
+ svg { width : 19px; }
+ }
+ h4 { font-size : 1.25em; }
+ strong { font-weight : bold; }
+ em { font-style : italic; }
+ ul {
+ padding-left : 1.25em;
+ list-style : square;
+ }
+ .blank {
+ height : 1em;
+ margin-top : 0;
+ & + * { margin-top : 0; }
}
- }
- h4 {
- font-size: 1.25em;
- }
- strong {
- font-weight: bold;
}
}
-}
+}
\ No newline at end of file
diff --git a/client/homebrew/pages/errorPage/errorPage.jsx b/client/homebrew/pages/errorPage/errorPage.jsx
index 560ab56259..33da050171 100644
--- a/client/homebrew/pages/errorPage/errorPage.jsx
+++ b/client/homebrew/pages/errorPage/errorPage.jsx
@@ -4,44 +4,37 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
-const Nav = require('naturalcrit/nav/nav.jsx');
-const Navbar = require('../../navbar/navbar.jsx');
-const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
-const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
-const HelpNavItem = require('../../navbar/help.navitem.jsx');
+const UIPage = require('../basePages/uiPage/uiPage.jsx');
-const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
+const Markdown = require('../../../../shared/naturalcrit/markdown.js');
+
+const ErrorIndex = require('./errors/errorIndex.js');
const ErrorPage = createClass({
+ displayName : 'ErrorPage',
+
getDefaultProps : function() {
return {
ver : '0.0.0',
- errorId : ''
+ errorId : '',
+ text : '# Oops \n We could not find a brew with that id. **Sorry!**',
+ error : {}
};
},
- text : '# Oops \n We could not find a brew with that id. **Sorry!**',
-
render : function(){
- return
-
-
-
- Crit Fail!
-
-
-
-
-
-
-
-
-
-
-
-
+ const errorText = ErrorIndex(this.props)[this.props.brew.HBErrorCode.toString()] || '';
+
+ return
+
+
+
{`Error ${this.props.brew.status || '000'}`}
+ {this.props.brew.text || 'No error text'}
+
+
+
- ;
+ ;
}
});
diff --git a/client/homebrew/pages/errorPage/errorPage.less b/client/homebrew/pages/errorPage/errorPage.less
index 48ba0f93e2..2d10301e05 100644
--- a/client/homebrew/pages/errorPage/errorPage.less
+++ b/client/homebrew/pages/errorPage/errorPage.less
@@ -1,5 +1,13 @@
-.errorPage{
- .errorTitle{
- background-color: @orange;
+.homebrew {
+ .uiPage.sitePage {
+ .errorTitle {
+ //background-color: @orange;
+ color : #D02727;
+ text-align : center;
+ }
+ .content {
+ h1, h2, h3, h4 { border-bottom : none; }
+ hr { border-bottom : 2px solid slategrey; }
+ }
}
}
\ No newline at end of file
diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js
new file mode 100644
index 0000000000..a7e61d08da
--- /dev/null
+++ b/client/homebrew/pages/errorPage/errors/errorIndex.js
@@ -0,0 +1,126 @@
+const dedent = require('dedent-tabs').default;
+
+const loginUrl = 'https://www.naturalcrit.com/login';
+
+const errorIndex = (props)=>{
+ return {
+ // Default catch all
+ '00' : dedent`
+ ## An unknown error occurred!
+
+ We aren't sure what happened, but our server wasn't able to find what you
+ were looking for.`,
+
+ // General Google load error
+ '01' : dedent`
+ ## An error occurred while retrieving this brew from Google Drive!
+
+ Google reported an error while attempting to retrieve a brew from this link.`,
+
+ // Google Drive - 404 : brew deleted or access denied
+ '02' : dedent`
+ ## We can't find this brew in Google Drive!
+
+ This file was saved on Google Drive, but this link doesn't work anymore.
+ ${ props.brew.authors?.length > 0
+ ? `Note that this brew belongs to the Homebrewery account **${ props.brew.authors[0] }**,
+ ${ props.brew.account
+ ? `which is
+ ${props.brew.authors[0] == props.brew.account
+ ? `your account.`
+ : `not your account (you are currently signed in as **${props.brew.account}**).`
+ }`
+ : 'and you are not currently signed in to any account.'
+ }`
+ : ''
+ }
+ The Homebrewery cannot delete files from Google Drive on its own, so there
+ are three most likely possibilities:
+ :
+ - **The Google Drive files may have been accidentally deleted.** Look in
+ the Google Drive account that owns this brew (or ask the owner to do so),
+ and make sure the Homebrewery folder is still there, and that it holds your brews
+ as text files.
+ - **You may have changed the sharing settings for your files.** If the files
+ are still on Google Drive, change all of them to be shared *with everyone who has
+ the link* so the Homebrewery can access them.
+ - **The Google Account may be closed.** Google may have removed the account
+ due to inactivity or violating a Google policy. Make sure the owner can
+ still access Google Drive normally and upload/download files to it.
+ :
+ If the file isn't found, Google Drive usually puts your file in your Trash folder for
+ 30 days. Assuming the trash hasn't been emptied yet, it might be worth checking.
+ You can also find the Activity tab on the right side of the Google Drive page, which
+ shows the recent activity on Google Drive. This can help you pin down the exact date
+ the brew was deleted or moved, and by whom.
+ :
+ If the brew still isn't found, some people have had success asking Google to recover
+ accidentally deleted files at this link:
+ https://support.google.com/drive/answer/1716222?hl=en&ref_topic=7000946.
+ At the bottom of the page there is a button that says *Send yourself an Email*
+ and you will receive instructions on how to request the files be restored.
+ :
+ Also note, if you prefer not to use your Google Drive for storage, you can always
+ change the storage location of a brew by clicking the Google drive icon by the
+ brew title and choosing *transfer my brew to/from Google Drive*.`,
+
+ // User is not Authors list
+ '03' : dedent`
+ ## Current signed-in user does not have editor access to this brew.
+
+ If you believe you should have access to this brew, ask one of its authors to invite you
+ as an author by opening the Edit page for the brew, viewing the {{fa,fa-info-circle}}
+ **Properties** tab, and adding your username to the "invited authors" list. You can
+ then try to access this document again.
+
+ **Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
+
+ **Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
+
+ // User is not signed in; must be a user on the Authors List
+ '04' : dedent`
+ ## Sign-in required to edit this brew.
+
+ You must be logged in to one of the accounts listed as an author of this brew.
+ User is not logged in. Please log in [here](${loginUrl}).
+
+ **Brew Title:** ${props.brew.brewTitle || 'Unable to show title'}
+
+ **Current Authors:** ${props.brew.authors?.map((author)=>{return `${author}`;}).join(', ') || 'Unable to list authors'}`,
+
+ // Brew load error
+ '05' : dedent`
+ ## No Homebrewery document could be found.
+
+ The server could not locate the Homebrewery document. It was likely deleted by
+ its owner.
+
+ **Requested access:** ${props.brew.accessType}
+
+ **Brew ID:** ${props.brew.brewId}`,
+
+ // Brew save error
+ '06' : dedent`
+ ## Unable to save Homebrewery document.
+
+ An error occurred wil attempting to save the Homebrewery document.`,
+
+ // Brew delete error
+ '07' : dedent`
+ ## Unable to delete Homebrewery document.
+
+ An error occurred while attempting to remove the Homebrewery document.
+
+ **Brew ID:** ${props.brew.brewId}`,
+
+ // Author delete error
+ '08' : dedent`
+ ## Unable to remove user from Homebrewery document.
+
+ An error occurred while attempting to remove the user from the Homebrewery document author list!
+
+ **Brew ID:** ${props.brew.brewId}`,
+ };
+};
+
+module.exports = errorIndex;
\ No newline at end of file
diff --git a/server/app.js b/server/app.js
index 22c9d9dac0..901349d971 100644
--- a/server/app.js
+++ b/server/app.js
@@ -1,4 +1,4 @@
-/*eslint max-lines: ["warn", {"max": 400, "skipBlankLines": true, "skipComments": true}]*/
+/*eslint max-lines: ["warn", {"max": 500, "skipBlankLines": true, "skipComments": true}]*/
// Set working directory to project root
process.chdir(`${__dirname}/..`);
@@ -397,7 +397,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
return next();
}));
-
const nodeEnv = config.get('node_env');
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
// Local only
@@ -414,8 +413,7 @@ if(isLocalEnvironment){
//Render the page
const templateFn = require('./../client/template.js');
-app.use(asyncHandler(async (req, res, next)=>{
-
+const renderPage = async (req, res)=>{
// Create configuration object
const configuration = {
local : isLocalEnvironment,
@@ -424,7 +422,7 @@ app.use(asyncHandler(async (req, res, next)=>{
};
const props = {
version : require('./../package.json').version,
- url : req.originalUrl,
+ url : req.customUrl || req.originalUrl,
brew : req.brew,
brews : req.brews,
googleBrews : req.googleBrews,
@@ -438,15 +436,20 @@ app.use(asyncHandler(async (req, res, next)=>{
const page = await templateFn('homebrew', title, props)
.catch((err)=>{
console.log(err);
- return res.sendStatus(500);
});
+ return page;
+};
+
+//Send rendered page
+app.use(asyncHandler(async (req, res, next)=>{
+ const page = await renderPage(req, res);
if(!page) return;
res.send(page);
}));
//v=====----- Error-Handling Middleware -----=====v//
-//Format Errors so all fields will be sent
-const replaceErrors = (key, value)=>{
+//Format Errors as plain objects so all fields will appear in the string sent
+const formatErrors = (key, value)=>{
if(value instanceof Error) {
const error = {};
Object.getOwnPropertyNames(value).forEach(function (key) {
@@ -458,13 +461,30 @@ const replaceErrors = (key, value)=>{
};
const getPureError = (error)=>{
- return JSON.parse(JSON.stringify(error, replaceErrors));
+ return JSON.parse(JSON.stringify(error, formatErrors));
};
-app.use((err, req, res, next)=>{
- const status = err.status || 500;
+app.use(async (err, req, res, next)=>{
+ const status = err.status || err.code || 500;
console.error(err);
- res.status(status).send(getPureError(err));
+
+ req.ogMeta = { ...defaultMetaTags,
+ title : 'Error Page',
+ description : 'Something went wrong!'
+ };
+ req.brew = {
+ ...err,
+ title : 'Error - Something went wrong!',
+ text : err.errors?.map((error)=>{return error.message;}).join('\n\n') || err.message || 'Unknown error!',
+ status : status,
+ HBErrorCode : err.HBErrorCode ?? '00',
+ pureError : getPureError(err)
+ };
+ req.customUrl= '/error';
+
+ const page = await renderPage(req, res);
+ if(!page) return;
+ res.send(page);
});
app.use((req, res)=>{
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
index 39fa021e5e..60c86b6d42 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -57,7 +57,14 @@ const api = {
googleError = err;
});
// Throw any error caught while attempting to retrieve Google brew.
- if(googleError) throw googleError;
+ if(googleError) {
+ const reason = googleError.errors?.[0].reason;
+ if(reason == 'notFound') {
+ throw { ...googleError, HBErrorCode: '02', authors: stub?.authors, account: req.account?.username };
+ } else {
+ throw { ...googleError, HBErrorCode: '01' };
+ }
+ }
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
}
@@ -65,14 +72,16 @@ const api = {
const isAuthor = stub?.authors?.includes(req.account?.username);
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
- throw `The current logged in user does not have editor access to this brew.
-
-If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`;
+ const accessError = { name: 'Access Error', status: 401 };
+ if(req.account){
+ throw { ...accessError, message: 'User is not an Author', HBErrorCode: '03', authors: stub.authors, brewTitle: stub.title };
+ }
+ throw { ...accessError, message: 'User is not logged in', HBErrorCode: '04', authors: stub.authors, brewTitle: stub.title };
}
// If after all of that we still don't have a brew, throw an exception
if(!stub && !stubOnly) {
- throw 'Brew not found in Homebrewery database or Google Drive';
+ throw { name: 'BrewLoad Error', message: 'Brew not found', status: 404, HBErrorCode: '05', accessType: accessType, brewId: id };
}
// Clean up brew: fill in missing fields with defaults / fix old invalid values
@@ -181,7 +190,7 @@ If you believe you should have access to this brew, ask the file owner to invite
saved = await newHomebrew.save()
.catch((err)=>{
console.error(err, err.toString(), err.stack);
- throw `Error while creating new brew, ${err.toString()}`;
+ throw { name: 'BrewSave Error', message: `Error while creating new brew, ${err.toString()}`, status: 500, HBErrorCode: '06' };
});
if(!saved) return;
saved = saved.toObject();
@@ -283,10 +292,13 @@ If you believe you should have access to this brew, ask the file owner to invite
try {
await api.getBrew('edit')(req, res, ()=>{});
} catch (err) {
- const { id, googleId } = api.getId(req);
- console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
- await HomebrewModel.deleteOne({ editId: id });
- return next();
+ // Only if the error code is HBErrorCode '02', that is, Google returned "404 - Not Found"
+ if(err.HBErrorCode == '02') {
+ const { id, googleId } = api.getId(req);
+ console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
+ await HomebrewModel.deleteOne({ editId: id });
+ return next();
+ }
}
let brew = req.brew;
@@ -308,7 +320,7 @@ If you believe you should have access to this brew, ask the file owner to invite
await HomebrewModel.deleteOne({ _id: brew._id })
.catch((err)=>{
console.error(err);
- throw { status: 500, message: 'Error while removing' };
+ throw { name: 'BrewDelete Error', message: 'Error while removing', status: 500, HBErrorCode: '07', brewId: brew._id };
});
} else {
if(shouldDeleteGoogleBrew) {
@@ -320,7 +332,7 @@ If you believe you should have access to this brew, ask the file owner to invite
brew.markModified('authors'); //Mongo will not properly update arrays without markModified()
await brew.save()
.catch((err)=>{
- throw { status: 500, message: err };
+ throw { name: 'BrewAuthorDelete Error', message: err, status: 500, HBErrorCode: '08', brewId: brew._id };
});
}
}
diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js
index c6443be7bb..0adbcda4ff 100644
--- a/server/homebrew.api.spec.js
+++ b/server/homebrew.api.spec.js
@@ -125,7 +125,7 @@ describe('Tests for api', ()=>{
describe('getBrew', ()=>{
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
- const notFoundError = 'Brew not found in Homebrewery database or Google Drive';
+ const notFoundError = { HBErrorCode: '05', message: 'Brew not found', name: 'BrewLoad Error', status: 404, accessType: 'share', brewId: '1' };
it('returns middleware', ()=>{
const getFn = api.getBrew('share');
@@ -183,7 +183,7 @@ describe('Tests for api', ()=>{
expect(next).toHaveBeenCalled();
});
- it('throws if invalid author', async ()=>{
+ it('throws if not logged in as author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
@@ -197,9 +197,24 @@ describe('Tests for api', ()=>{
err = e;
}
- expect(err).toEqual(`The current logged in user does not have editor access to this brew.
+ expect(err).toEqual({ HBErrorCode: '04', message: 'User is not logged in', name: 'Access Error', status: 401, brewTitle: 'test brew', authors: ['a'] });
+ });
+
+ it('throws if logged in as invalid author', async ()=>{
+ api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
+ model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
+
+ const fn = api.getBrew('edit', true);
+ const req = { brew: {}, account: { username: 'b' } };
+
+ let err;
+ try {
+ await fn(req, null, null);
+ } catch (e) {
+ err = e;
+ }
-If you believe you should have access to this brew, ask the file owner to invite you as an author by opening the brew, viewing the Properties tab, and adding your username to the "invited authors" list. You can then try to access this document again.`);
+ expect(err).toEqual({ HBErrorCode: '03', message: 'User is not an Author', name: 'Access Error', status: 401, brewTitle: 'test brew', authors: ['a'] });
});
it('does not throw if no authors', async ()=>{
@@ -545,7 +560,7 @@ brew`);
describe('deleteBrew', ()=>{
it('should handle case where fetching the brew returns an error', async ()=>{
- api.getBrew = jest.fn(()=>async ()=>{ throw 'err'; });
+ api.getBrew = jest.fn(()=>async ()=>{ throw { message: 'err', HBErrorCode: '02' }; });
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
model.deleteOne = jest.fn(async ()=>{});
const next = jest.fn(()=>{});