mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-31 23:52:48 +00:00
Merge branch 'master' into addEditorThemes-#362
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -80,6 +80,16 @@ pre {
|
|||||||
## changelog
|
## changelog
|
||||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||||
|
|
||||||
|
### Friday 28/06/2023 - v3.9.1
|
||||||
|
{{taskList
|
||||||
|
|
||||||
|
##### G-Ambatte
|
||||||
|
|
||||||
|
* [x] Better error pages with more useful information
|
||||||
|
|
||||||
|
Fixes issue [#1924](https://github.com/naturalcrit/homebrewery/issues/1924)
|
||||||
|
}}
|
||||||
|
|
||||||
### Friday 02/06/2023 - v3.9.0
|
### Friday 02/06/2023 - v3.9.0
|
||||||
{{taskList
|
{{taskList
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const EditPage = require('./pages/editPage/editPage.jsx');
|
|||||||
const UserPage = require('./pages/userPage/userPage.jsx');
|
const UserPage = require('./pages/userPage/userPage.jsx');
|
||||||
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
const SharePage = require('./pages/sharePage/sharePage.jsx');
|
||||||
const NewPage = require('./pages/newPage/newPage.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 PrintPage = require('./pages/printPage/printPage.jsx');
|
||||||
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
const AccountPage = require('./pages/accountPage/accountPage.jsx');
|
||||||
|
|
||||||
@@ -78,6 +78,7 @@ const Homebrew = createClass({
|
|||||||
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
<Route path='/faq' element={<WithRoute el={SharePage} brew={this.props.brew} />} />
|
||||||
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} uiItems={this.props.brew.uiItems} />} />
|
<Route path='/account' element={<WithRoute el={AccountPage} brew={this.props.brew} uiItems={this.props.brew.uiItems} />} />
|
||||||
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/legacy' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
|
<Route path='/error' element={<WithRoute el={ErrorPage} brew={this.props.brew} />} />
|
||||||
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
<Route path='/*' element={<WithRoute el={HomePage} brew={this.props.brew} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,47 +1,52 @@
|
|||||||
.uiPage{
|
.homebrew {
|
||||||
.content{
|
.uiPage.sitePage {
|
||||||
overflow-y : hidden;
|
.content {
|
||||||
width : 90vw;
|
width : ~"min(90vw, 1000px)";
|
||||||
background-color: #f0f0f0;
|
padding : 2% 4%;
|
||||||
font-family: 'Open Sans';
|
margin-top : 25px;
|
||||||
margin-left: auto;
|
margin-right : auto;
|
||||||
margin-right: auto;
|
margin-left : auto;
|
||||||
margin-top: 25px;
|
overflow-y : scroll;
|
||||||
padding: 2% 4%;
|
font-family : 'Open Sans';
|
||||||
font-size: 0.8em;
|
font-size : 0.8em;
|
||||||
line-height: 1.8em;
|
line-height : 1.8em;
|
||||||
.dataGroup{
|
background-color : #F0F0F0;
|
||||||
padding: 6px 20px 15px;
|
.dataGroup {
|
||||||
border: 2px solid black;
|
padding : 6px 20px 15px;
|
||||||
border-radius: 5px;
|
margin : 5px 0px;
|
||||||
margin: 5px 0px;
|
border : 2px solid black;
|
||||||
}
|
border-radius : 5px;
|
||||||
h1, h2, h3, h4{
|
}
|
||||||
font-weight: 900;
|
h1, h2, h3, h4 {
|
||||||
text-transform: uppercase;
|
width : 100%;
|
||||||
margin: 0.5em 30% 0.25em 0;
|
margin : 0.5em 30% 0.25em 0;
|
||||||
border-bottom: 2px solid slategrey;
|
font-weight : 900;
|
||||||
}
|
text-transform : uppercase;
|
||||||
h1 {
|
border-bottom : 2px solid slategrey;
|
||||||
font-size: 2em;
|
}
|
||||||
border-bottom: 2px solid darkslategrey;
|
h1 {
|
||||||
margin-bottom: 0.5em;
|
margin-right : 0;
|
||||||
margin-right: 0;
|
margin-bottom : 0.5em;
|
||||||
}
|
font-size : 2em;
|
||||||
h2 {
|
border-bottom : 2px solid darkslategrey;
|
||||||
font-size: 1.75em;
|
}
|
||||||
}
|
h2 { font-size : 1.75em; }
|
||||||
h3 {
|
h3 {
|
||||||
font-size: 1.5em;
|
font-size : 1.5em;
|
||||||
svg {
|
svg { width : 19px; }
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,44 +4,37 @@ const createClass = require('create-react-class');
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const cx = require('classnames');
|
const cx = require('classnames');
|
||||||
|
|
||||||
const Nav = require('naturalcrit/nav/nav.jsx');
|
const UIPage = require('../basePages/uiPage/uiPage.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 BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
|
const Markdown = require('../../../../shared/naturalcrit/markdown.js');
|
||||||
|
|
||||||
|
const ErrorIndex = require('./errors/errorIndex.js');
|
||||||
|
|
||||||
const ErrorPage = createClass({
|
const ErrorPage = createClass({
|
||||||
|
displayName : 'ErrorPage',
|
||||||
|
|
||||||
getDefaultProps : function() {
|
getDefaultProps : function() {
|
||||||
return {
|
return {
|
||||||
ver : '0.0.0',
|
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(){
|
render : function(){
|
||||||
return <div className='errorPage sitePage'>
|
const errorText = ErrorIndex(this.props)[this.props.brew.HBErrorCode.toString()] || '';
|
||||||
<Navbar ver={this.props.ver}>
|
|
||||||
<Nav.section>
|
|
||||||
<Nav.item className='errorTitle'>
|
|
||||||
Crit Fail!
|
|
||||||
</Nav.item>
|
|
||||||
</Nav.section>
|
|
||||||
|
|
||||||
<Nav.section>
|
return <UIPage brew={{ title: 'Crit Fail!' }}>
|
||||||
<PatreonNavItem />
|
<div className='dataGroup'>
|
||||||
<HelpNavItem />
|
<div className='errorTitle'>
|
||||||
<RecentNavItem />
|
<h1>{`Error ${this.props.brew.status || '000'}`}</h1>
|
||||||
</Nav.section>
|
<h4>{this.props.brew.text || 'No error text'}</h4>
|
||||||
</Navbar>
|
</div>
|
||||||
|
<hr />
|
||||||
<div className='content'>
|
<div dangerouslySetInnerHTML={{ __html: Markdown.render(errorText) }} />
|
||||||
<BrewRenderer text={this.text} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</UIPage>;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
.errorPage{
|
.homebrew {
|
||||||
.errorTitle{
|
.uiPage.sitePage {
|
||||||
background-color: @orange;
|
.errorTitle {
|
||||||
|
//background-color: @orange;
|
||||||
|
color : #D02727;
|
||||||
|
text-align : center;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
h1, h2, h3, h4 { border-bottom : none; }
|
||||||
|
hr { border-bottom : 2px solid slategrey; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
126
client/homebrew/pages/errorPage/errors/errorIndex.js
Normal file
126
client/homebrew/pages/errorPage/errors/errorIndex.js
Normal file
@@ -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;
|
||||||
38
package-lock.json
generated
38
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"version": "3.9.0",
|
"version": "3.9.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"version": "3.9.0",
|
"version": "3.9.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router-dom": "6.13.0",
|
"react-router-dom": "6.14.0",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^6.1.0",
|
"superagent": "^6.1.0",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^15.8.0",
|
"stylelint": "^15.9.0",
|
||||||
"stylelint-config-recess-order": "^4.2.0",
|
"stylelint-config-recess-order": "^4.2.0",
|
||||||
"stylelint-config-recommended": "^12.0.0",
|
"stylelint-config-recommended": "^12.0.0",
|
||||||
"stylelint-stylistic": "^0.4.2",
|
"stylelint-stylistic": "^0.4.2",
|
||||||
@@ -2810,9 +2810,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.6.3",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.0.tgz",
|
||||||
"integrity": "sha512-EXJysQ7J3veRECd0kZFQwYYd5sJMcq2O/m60zu1W2l3oVQ9xtub8jTOtYRE0+M2iomyG/W3Ps7+vp2kna0C27Q==",
|
"integrity": "sha512-Eu1V3kz3mV0wUpVTiFHuaT8UD1gj/0VnoFHQYX35xlslQUpe8CuYoKFn9d4WZFHm3yDywz6ALZuGdnUPKrNeAw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
@@ -14284,11 +14284,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "6.13.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.0.tgz",
|
||||||
"integrity": "sha512-Si6KnfEnJw7gUQkNa70dlpI1bul46FuSxX5t5WwlUBxE25DAz2BjVkwaK8Y2s242bQrZPXCpmwLPtIO5pv4tXg==",
|
"integrity": "sha512-OD+vkrcGbvlwkspUFDgMzsu1RXwdjNh83YgG/28lBnDzgslhCgxIqoExLlxsfTpIygp7fc+Hd3esloNwzkm2xA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.6.3"
|
"@remix-run/router": "1.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -14298,12 +14298,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-router-dom": {
|
"node_modules/react-router-dom": {
|
||||||
"version": "6.13.0",
|
"version": "6.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.0.tgz",
|
||||||
"integrity": "sha512-6Nqoqd7fgwxxVGdbiMHTpDHCYPq62d7Wk1Of7B82vH7ZPwwsRaIa22zRZKPPg413R5REVNiyuQPKDG1bubcOFA==",
|
"integrity": "sha512-YEwlApKwzMMMbGbhh+Q7MsloTldcwMgHxUY/1g0uA62+B1hZo2jsybCWIDCL8zvIDB1FA0pBKY9chHbZHt+2dQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@remix-run/router": "1.6.3",
|
"@remix-run/router": "1.7.0",
|
||||||
"react-router": "6.13.0"
|
"react-router": "6.14.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -15736,9 +15736,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/stylelint": {
|
"node_modules/stylelint": {
|
||||||
"version": "15.8.0",
|
"version": "15.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-15.9.0.tgz",
|
||||||
"integrity": "sha512-x9qBk84F3MEjMEUNCE7MtWmfj9G9y5XzJ0cpQeJdy2l/IoqjC8Ih0N0ytmOTnXE4Yv0J7I1cmVRQUVNSPCxTsA==",
|
"integrity": "sha512-sXtAZi64CllWr6A+8ymDWnlIaYwuAa7XRmGnJxLQXFNnLjd3Izm4HAD+loKVaZ7cpK6SLxhAUX1lwPJKGCn0mg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@csstools/css-parser-algorithms": "^2.2.0",
|
"@csstools/css-parser-algorithms": "^2.2.0",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "homebrewery",
|
"name": "homebrewery",
|
||||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||||
"version": "3.9.0",
|
"version": "3.9.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.16.x"
|
"node": ">=18.16.x"
|
||||||
},
|
},
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-frame-component": "^4.1.3",
|
"react-frame-component": "^4.1.3",
|
||||||
"react-router-dom": "6.13.0",
|
"react-router-dom": "6.14.0",
|
||||||
"sanitize-filename": "1.6.3",
|
"sanitize-filename": "1.6.3",
|
||||||
"superagent": "^6.1.0",
|
"superagent": "^6.1.0",
|
||||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-expect-message": "^1.1.3",
|
"jest-expect-message": "^1.1.3",
|
||||||
"postcss-less": "^6.0.0",
|
"postcss-less": "^6.0.0",
|
||||||
"stylelint": "^15.8.0",
|
"stylelint": "^15.9.0",
|
||||||
"stylelint-config-recess-order": "^4.2.0",
|
"stylelint-config-recess-order": "^4.2.0",
|
||||||
"stylelint-config-recommended": "^12.0.0",
|
"stylelint-config-recommended": "^12.0.0",
|
||||||
"stylelint-stylistic": "^0.4.2",
|
"stylelint-stylistic": "^0.4.2",
|
||||||
|
|||||||
@@ -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
|
// Set working directory to project root
|
||||||
process.chdir(`${__dirname}/..`);
|
process.chdir(`${__dirname}/..`);
|
||||||
|
|
||||||
@@ -399,7 +399,6 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
|||||||
return next();
|
return next();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
const nodeEnv = config.get('node_env');
|
const nodeEnv = config.get('node_env');
|
||||||
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
|
const isLocalEnvironment = config.get('local_environments').includes(nodeEnv);
|
||||||
// Local only
|
// Local only
|
||||||
@@ -416,8 +415,7 @@ if(isLocalEnvironment){
|
|||||||
|
|
||||||
//Render the page
|
//Render the page
|
||||||
const templateFn = require('./../client/template.js');
|
const templateFn = require('./../client/template.js');
|
||||||
app.use(asyncHandler(async (req, res, next)=>{
|
const renderPage = async (req, res)=>{
|
||||||
|
|
||||||
// Create configuration object
|
// Create configuration object
|
||||||
const configuration = {
|
const configuration = {
|
||||||
local : isLocalEnvironment,
|
local : isLocalEnvironment,
|
||||||
@@ -427,7 +425,7 @@ app.use(asyncHandler(async (req, res, next)=>{
|
|||||||
};
|
};
|
||||||
const props = {
|
const props = {
|
||||||
version : require('./../package.json').version,
|
version : require('./../package.json').version,
|
||||||
url : req.originalUrl,
|
url : req.customUrl || req.originalUrl,
|
||||||
brew : req.brew,
|
brew : req.brew,
|
||||||
brews : req.brews,
|
brews : req.brews,
|
||||||
googleBrews : req.googleBrews,
|
googleBrews : req.googleBrews,
|
||||||
@@ -441,15 +439,20 @@ app.use(asyncHandler(async (req, res, next)=>{
|
|||||||
const page = await templateFn('homebrew', title, props)
|
const page = await templateFn('homebrew', title, props)
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.log(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;
|
if(!page) return;
|
||||||
res.send(page);
|
res.send(page);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//v=====----- Error-Handling Middleware -----=====v//
|
//v=====----- Error-Handling Middleware -----=====v//
|
||||||
//Format Errors so all fields will be sent
|
//Format Errors as plain objects so all fields will appear in the string sent
|
||||||
const replaceErrors = (key, value)=>{
|
const formatErrors = (key, value)=>{
|
||||||
if(value instanceof Error) {
|
if(value instanceof Error) {
|
||||||
const error = {};
|
const error = {};
|
||||||
Object.getOwnPropertyNames(value).forEach(function (key) {
|
Object.getOwnPropertyNames(value).forEach(function (key) {
|
||||||
@@ -461,13 +464,30 @@ const replaceErrors = (key, value)=>{
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPureError = (error)=>{
|
const getPureError = (error)=>{
|
||||||
return JSON.parse(JSON.stringify(error, replaceErrors));
|
return JSON.parse(JSON.stringify(error, formatErrors));
|
||||||
};
|
};
|
||||||
|
|
||||||
app.use((err, req, res, next)=>{
|
app.use(async (err, req, res, next)=>{
|
||||||
const status = err.status || 500;
|
const status = err.status || err.code || 500;
|
||||||
console.error(err);
|
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)=>{
|
app.use((req, res)=>{
|
||||||
|
|||||||
@@ -57,7 +57,14 @@ const api = {
|
|||||||
googleError = err;
|
googleError = err;
|
||||||
});
|
});
|
||||||
// Throw any error caught while attempting to retrieve Google brew.
|
// 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
|
// 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;
|
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 isAuthor = stub?.authors?.includes(req.account?.username);
|
||||||
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
|
const isInvited = stub?.invitedAuthors?.includes(req.account?.username);
|
||||||
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
|
if(accessType === 'edit' && (authorsExist && !(isAuthor || isInvited))) {
|
||||||
throw `The current logged in user does not have editor access to this brew.
|
const accessError = { name: 'Access Error', status: 401 };
|
||||||
|
if(req.account){
|
||||||
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.`;
|
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 after all of that we still don't have a brew, throw an exception
|
||||||
if(!stub && !stubOnly) {
|
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
|
// 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()
|
saved = await newHomebrew.save()
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.error(err, err.toString(), err.stack);
|
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;
|
if(!saved) return;
|
||||||
saved = saved.toObject();
|
saved = saved.toObject();
|
||||||
@@ -283,10 +292,13 @@ If you believe you should have access to this brew, ask the file owner to invite
|
|||||||
try {
|
try {
|
||||||
await api.getBrew('edit')(req, res, ()=>{});
|
await api.getBrew('edit')(req, res, ()=>{});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const { id, googleId } = api.getId(req);
|
// Only if the error code is HBErrorCode '02', that is, Google returned "404 - Not Found"
|
||||||
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
|
if(err.HBErrorCode == '02') {
|
||||||
await HomebrewModel.deleteOne({ editId: id });
|
const { id, googleId } = api.getId(req);
|
||||||
return next();
|
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;
|
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 })
|
await HomebrewModel.deleteOne({ _id: brew._id })
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
console.error(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 {
|
} else {
|
||||||
if(shouldDeleteGoogleBrew) {
|
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()
|
brew.markModified('authors'); //Mongo will not properly update arrays without markModified()
|
||||||
await brew.save()
|
await brew.save()
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
throw { status: 500, message: err };
|
throw { name: 'BrewAuthorDelete Error', message: err, status: 500, HBErrorCode: '08', brewId: brew._id };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ describe('Tests for api', ()=>{
|
|||||||
|
|
||||||
describe('getBrew', ()=>{
|
describe('getBrew', ()=>{
|
||||||
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
|
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', ()=>{
|
it('returns middleware', ()=>{
|
||||||
const getFn = api.getBrew('share');
|
const getFn = api.getBrew('share');
|
||||||
@@ -183,7 +183,7 @@ describe('Tests for api', ()=>{
|
|||||||
expect(next).toHaveBeenCalled();
|
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 }));
|
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
|
||||||
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
|
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
|
||||||
|
|
||||||
@@ -197,9 +197,24 @@ describe('Tests for api', ()=>{
|
|||||||
err = e;
|
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'] });
|
||||||
|
});
|
||||||
|
|
||||||
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.`);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ()=>{
|
it('does not throw if no authors', async ()=>{
|
||||||
@@ -545,7 +560,7 @@ brew`);
|
|||||||
|
|
||||||
describe('deleteBrew', ()=>{
|
describe('deleteBrew', ()=>{
|
||||||
it('should handle case where fetching the brew returns an error', async ()=>{
|
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' }));
|
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
|
||||||
model.deleteOne = jest.fn(async ()=>{});
|
model.deleteOne = jest.fn(async ()=>{});
|
||||||
const next = jest.fn(()=>{});
|
const next = jest.fn(()=>{});
|
||||||
|
|||||||
Reference in New Issue
Block a user