mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-24 07:43:01 +00:00
Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f6ba7a388 | ||
|
|
0274fb214c | ||
|
|
15519f142d | ||
|
|
6b258886a4 | ||
|
|
d23a88c997 | ||
|
|
096e17ab5a | ||
|
|
53d5f9f6e0 | ||
|
|
43b4fe75e2 | ||
|
|
816860dc4f | ||
|
|
314f758d62 | ||
|
|
c799aaa7cb | ||
|
|
2f5bc8db54 | ||
|
|
7c61a27084 | ||
|
|
ad3e83da22 | ||
|
|
8888704b58 | ||
|
|
a3dc5e78fd | ||
|
|
e5febc1fef | ||
|
|
354d01e980 | ||
|
|
63e043593a | ||
|
|
770d0c141d | ||
|
|
02c0176070 | ||
|
|
f847de852b | ||
|
|
86413b5767 | ||
|
|
747c976a14 | ||
|
|
326c28a11d | ||
|
|
263471bcbb | ||
|
|
9478454063 | ||
|
|
a9a9804517 | ||
|
|
0bde44ec2f | ||
|
|
13ad179a1b | ||
|
|
b72acd9e59 | ||
|
|
d0a1ef9571 | ||
|
|
d1f049871f | ||
|
|
070184b309 | ||
|
|
cbb41676e0 | ||
|
|
81130dd514 | ||
|
|
61d3edca17 | ||
|
|
248b56a706 | ||
|
|
90f8d1d6da | ||
|
|
8f08b71475 | ||
|
|
cc6527029c | ||
|
|
8a110567fc | ||
|
|
4e2f6b1d26 | ||
|
|
6b8db74a2b | ||
|
|
4c629772cc | ||
|
|
208593d203 | ||
|
|
99019be152 | ||
|
|
fa73e1707d | ||
|
|
d7de2e3d21 | ||
|
|
903ff4fd09 | ||
|
|
feaabacc94 | ||
|
|
bc0846c190 | ||
|
|
ecdcaadfa9 | ||
|
|
db2478f73d | ||
|
|
5d6a7e692f | ||
|
|
0fbeca1536 | ||
|
|
31d58f9075 | ||
|
|
89e6bada56 | ||
|
|
fec1766e26 | ||
|
|
f26e3d6cd1 | ||
|
|
2e6fcafc68 | ||
|
|
13b43e8902 | ||
|
|
837708fc0c | ||
|
|
2e305d5636 | ||
|
|
f9711de634 | ||
|
|
2c6779bb1c | ||
|
|
0867b142da |
72
changelog.md
72
changelog.md
@@ -7,6 +7,11 @@ h5 {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.page .taskList {
|
||||
display:block;
|
||||
break-inside:auto;
|
||||
}
|
||||
|
||||
.taskList li input {
|
||||
list-style-type : none;
|
||||
margin-left : -0.52cm;
|
||||
@@ -35,6 +40,14 @@ pre {
|
||||
margin-top : 0.1cm;
|
||||
}
|
||||
|
||||
.page ul + h5 {
|
||||
margin-top: 0.25cm;
|
||||
}
|
||||
|
||||
.page p + h5 {
|
||||
margin-top: 0.25cm;
|
||||
}
|
||||
|
||||
.page .openSans {
|
||||
font-family: 'Open Sans';
|
||||
font-size: 0.9em;
|
||||
@@ -44,6 +57,54 @@ pre {
|
||||
## changelog
|
||||
For a full record of development, visit our [Github Page](https://github.com/naturalcrit/homebrewery).
|
||||
|
||||
### Friday 23/12/2022 - v3.5.0
|
||||
{{taskList
|
||||
|
||||
##### Jeddai
|
||||
|
||||
* [x] Only brew owners or invited authors can edit a brew
|
||||
|
||||
- Visiting an `/edit` page of a brew that does not list you as an author will result in an error page. Authors can be added to any brew by opening its {{fa,fa-info-circle}} **Properties** menu and typing the author's username (case-sensitive) into the **Invited Authors** bubble.
|
||||
- Warn user if a newer brew version has been saved on another device
|
||||
|
||||
Fixes issues [#1987](https://github.com/naturalcrit/homebrewery/issues/1987)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Monday 05/12/2022 - v3.4.1
|
||||
{{taskList
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Fix Account page incorrect last login time
|
||||
|
||||
Fixes issues [#2521](https://github.com/naturalcrit/homebrewery/issues/2521)
|
||||
|
||||
##### Gazook
|
||||
|
||||
* [x] Fix crashing on iOS and Safari browsers
|
||||
|
||||
Fixes issues [#2531](https://github.com/naturalcrit/homebrewery/issues/2531)
|
||||
}}
|
||||
|
||||
### Saturday 10/12/2022 - v3.4.2
|
||||
{{taskList
|
||||
|
||||
##### Jeddai
|
||||
|
||||
* [x] Fix broken tags editor
|
||||
|
||||
* [x] Reduce server load to fix some saving issues
|
||||
|
||||
Fixes issues [#2322](https://github.com/naturalcrit/homebrewery/issues/2322)
|
||||
|
||||
##### G-Ambatte
|
||||
|
||||
* [x] Account page help link for Google Drive errors
|
||||
|
||||
Fixes issues [#2520](https://github.com/naturalcrit/homebrewery/issues/2520)
|
||||
}}
|
||||
|
||||
### Monday 05/12/2022 - v3.4.1
|
||||
{{taskList
|
||||
@@ -142,12 +203,7 @@ Fixes issues [#2135](https://github.com/naturalcrit/homebrewery/issues/2135)
|
||||
* [x] Fix brew settings being lost on first save
|
||||
|
||||
Fixes issues [#2427](https://github.com/naturalcrit/homebrewery/issues/2427)
|
||||
}}
|
||||
|
||||
\column
|
||||
|
||||
|
||||
{{taskList
|
||||
##### Gazook:
|
||||
|
||||
* [x] Several updates to bug reporting and error popups
|
||||
@@ -197,6 +253,10 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [
|
||||
Fixes issues: [#1797](https://github.com/naturalcrit/homebrewery/issues/1797), [#2315](https://github.com/naturalcrit/homebrewery/issues/2315), [#2326](https://github.com/naturalcrit/homebrewery/issues/2326), [#2328](https://github.com/naturalcrit/homebrewery/issues/2328)
|
||||
}}
|
||||
|
||||
|
||||
|
||||
\page
|
||||
|
||||
### Wednesday 31/08/2022 - v3.2.1
|
||||
{{taskList
|
||||
|
||||
@@ -223,8 +283,6 @@ Fixes issues [#2317](https://github.com/naturalcrit/homebrewery/issues/2317), [
|
||||
Fixes issues: [#2301](https://github.com/naturalcrit/homebrewery/issues/2301), [#2303](https://github.com/naturalcrit/homebrewery/issues/2303), [#2121](https://github.com/naturalcrit/homebrewery/issues/2121)
|
||||
}}
|
||||
|
||||
\page
|
||||
|
||||
### Saturday 27/08/2022 - v3.2.0
|
||||
{{taskList
|
||||
|
||||
|
||||
@@ -139,10 +139,10 @@ const Editor = createClass({
|
||||
|
||||
// Highlight injectors {style}
|
||||
if(line.includes('{') && line.includes('}')){
|
||||
const regex = /(?<!{){(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\1}/g;
|
||||
const regex = /(?:^|[^{\n])({(?=((?::(?:"[\w,\-()#%. ]*"|[\w\-()#%.]*)|[^"':{}\s]*)*))\2})/gm;
|
||||
let match;
|
||||
while ((match = regex.exec(line)) != null) {
|
||||
codeMirror.markText({ line: lineNumber, ch: match.index }, { line: lineNumber, ch: match.index + match[0].length }, { className: 'injection' });
|
||||
codeMirror.markText({ line: lineNumber, ch: line.indexOf(match[1]) }, { line: lineNumber, ch: line.indexOf(match[1]) + match[1].length }, { className: 'injection' });
|
||||
}
|
||||
}
|
||||
// Highlight inline spans {{content}}
|
||||
|
||||
@@ -9,12 +9,18 @@ const Nav = require('naturalcrit/nav/nav.jsx');
|
||||
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
|
||||
|
||||
const Themes = require('themes/themes.json');
|
||||
const validations = require('./validations.js')
|
||||
const validations = require('./validations.js');
|
||||
|
||||
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
|
||||
|
||||
const homebreweryThumbnail = require('../../thumbnail.png');
|
||||
|
||||
const callIfExists = (val, fn, ...args)=>{
|
||||
if(val[fn]) {
|
||||
val[fn](...args);
|
||||
}
|
||||
};
|
||||
|
||||
const MetadataEditor = createClass({
|
||||
displayName : 'MetadataEditor',
|
||||
getDefaultProps : function() {
|
||||
@@ -53,28 +59,25 @@ const MetadataEditor = createClass({
|
||||
},
|
||||
|
||||
handleFieldChange : function(name, e){
|
||||
e.persist();
|
||||
|
||||
// load validation rules, and check input value against them
|
||||
const inputRules = validations[name] ?? [];
|
||||
const validationErr = inputRules.map((rule)=>rule(e.target.value)).filter(Boolean);
|
||||
|
||||
// if no validation rules, save to props
|
||||
if(validationErr.length === 0){
|
||||
e.target.setCustomValidity('');
|
||||
callIfExists(e.target, 'setCustomValidity', '');
|
||||
this.props.onChange({
|
||||
...this.props.metadata,
|
||||
[name] : e.target.value
|
||||
});
|
||||
} else {
|
||||
// if validation issues, display built-in browser error popup with each error.
|
||||
console.log(validationErr);
|
||||
const errMessage = validationErr.map((err)=>{
|
||||
return `- ${err}`;
|
||||
}).join('\n');
|
||||
e.target.setCustomValidity(errMessage);
|
||||
e.target.reportValidity();
|
||||
};
|
||||
callIfExists(e.target, 'setCustomValidity', errMessage);
|
||||
callIfExists(e.target, 'reportValidity');
|
||||
}
|
||||
},
|
||||
|
||||
handleSystem : function(system, e){
|
||||
@@ -247,6 +250,8 @@ const MetadataEditor = createClass({
|
||||
|
||||
render : function(){
|
||||
return <div className='metadataEditor'>
|
||||
<h1 className='sectionHead'>Brew</h1>
|
||||
|
||||
<div className='field title'>
|
||||
<label>title</label>
|
||||
<input type='text' className='value'
|
||||
@@ -280,8 +285,6 @@ const MetadataEditor = createClass({
|
||||
values={this.props.metadata.tags}
|
||||
onChange={(e)=>this.handleFieldChange('tags', e)}/>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<div className='field systems'>
|
||||
<label>systems</label>
|
||||
<div className='value'>
|
||||
@@ -293,6 +296,23 @@ const MetadataEditor = createClass({
|
||||
|
||||
{this.renderRenderOptions()}
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Authors</h1>
|
||||
|
||||
{this.renderAuthors()}
|
||||
|
||||
<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
|
||||
validators={[(v)=>!this.props.metadata.authors.includes(v)]}
|
||||
placeholder='invite author' unique={true}
|
||||
values={this.props.metadata.invitedAuthors}
|
||||
notes={['Invited authors are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
|
||||
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h1 className='sectionHead'>Privacy</h1>
|
||||
|
||||
<div className='field publish'>
|
||||
<label>publish</label>
|
||||
<div className='value'>
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
height : calc(100vh - 54px); // 54px is the height of the navbar + snippet bar. probably a better way to dynamic get this.
|
||||
overflow-y : auto;
|
||||
|
||||
.sectionHead {
|
||||
font-weight: 1000;
|
||||
margin: 20px 0;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -30,6 +39,7 @@
|
||||
}
|
||||
.field{
|
||||
display : flex;
|
||||
flex-wrap : wrap;
|
||||
width : 100%;
|
||||
min-width : 200px;
|
||||
&>label{
|
||||
@@ -78,6 +88,11 @@
|
||||
font-size : 0.8em;
|
||||
}
|
||||
}
|
||||
|
||||
small {
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,10 +143,6 @@
|
||||
button.unpublish{
|
||||
.button(@silver);
|
||||
}
|
||||
small{
|
||||
font-size : 0.6em;
|
||||
font-style : italic;
|
||||
}
|
||||
}
|
||||
|
||||
.delete.field .value{
|
||||
@@ -196,6 +207,7 @@
|
||||
}
|
||||
.field .list {
|
||||
display: flex;
|
||||
flex: 1 0;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
|
||||
@@ -9,7 +9,9 @@ const StringArrayEditor = createClass({
|
||||
label : '',
|
||||
values : [],
|
||||
valuePatterns : null,
|
||||
validators : [],
|
||||
placeholder : '',
|
||||
notes : [],
|
||||
unique : false,
|
||||
cannotEdit : [],
|
||||
onChange : ()=>{}
|
||||
@@ -83,7 +85,8 @@ const StringArrayEditor = createClass({
|
||||
}
|
||||
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
|
||||
const uniqueIfSet = !this.props.unique || !values.includes(value);
|
||||
return matchesPatterns && uniqueIfSet;
|
||||
const passesValidators = !this.props.validators || this.props.validators.every((validator)=>validator(value));
|
||||
return matchesPatterns && uniqueIfSet && passesValidators;
|
||||
},
|
||||
|
||||
handleValueInputKeyDown : function(event, index) {
|
||||
@@ -123,17 +126,21 @@ const StringArrayEditor = createClass({
|
||||
</div>
|
||||
);
|
||||
|
||||
return <div className='field values'>
|
||||
return <div className='field'>
|
||||
<label>{this.props.label}</label>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
<div style={{ flex: '1 0' }}>
|
||||
<div className='list'>
|
||||
{valueElements}
|
||||
<div className='input-group'>
|
||||
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
|
||||
value={this.state.temporaryValue}
|
||||
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
|
||||
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
|
||||
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}><i className='fa fa-check fa-fw'/></div> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.props.notes ? this.props.notes.map((n)=><p><small>{n}</small></p>) : null}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ const AccountPage = createClass({
|
||||
},
|
||||
|
||||
renderUiItems : function() {
|
||||
// console.log(this.props.uiItems);
|
||||
return <>
|
||||
<div className='dataGroup'>
|
||||
<h1>Account Information <i className='fas fa-user'></i></h1>
|
||||
@@ -51,12 +50,16 @@ const AccountPage = createClass({
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Homebrewery Information <NaturalCritIcon /></h3>
|
||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount || '-'}</p>
|
||||
<p><strong>Brews on Homebrewery: </strong> {this.props.uiItems.mongoCount}</p>
|
||||
</div>
|
||||
<div className='dataGroup'>
|
||||
<h3>Google Information <i className='fab fa-google-drive'></i></h3>
|
||||
<p><strong>Linked to Google: </strong> {this.props.uiItems.googleId ? 'YES' : 'NO'}</p>
|
||||
{this.props.uiItems.googleId ? <p><strong>Brews on Google Drive: </strong> {this.props.uiItems.fileCount || '-'}</p> : '' }
|
||||
{this.props.uiItems.googleId &&
|
||||
<p>
|
||||
<strong>Brews on Google Drive: </strong> {this.props.uiItems.googleCount ?? <>Unable to retrieve files - <a href='https://github.com/naturalcrit/homebrewery/discussions/1580'>follow these steps to renew your Google credentials.</a></>}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
</>;
|
||||
},
|
||||
|
||||
@@ -230,7 +230,8 @@ const EditPage = createClass({
|
||||
brew : { ...prevState.brew,
|
||||
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
|
||||
editId : this.savedBrew.editId,
|
||||
shareId : this.savedBrew.shareId
|
||||
shareId : this.savedBrew.shareId,
|
||||
version : this.savedBrew.version
|
||||
},
|
||||
isPending : false,
|
||||
isSaving : false,
|
||||
@@ -329,6 +330,16 @@ const EditPage = createClass({
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(this.state.errors.response.error.status === 409) {
|
||||
const message = this.state.errors.response.body?.message;
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
{message ? message : 'Conflict: please refresh to get latest changes'}
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
|
||||
2129
package-lock.json
generated
2129
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "homebrewery",
|
||||
"description": "Create authentic looking D&D homebrews using only markdown",
|
||||
"version": "3.4.1",
|
||||
"version": "3.5.0",
|
||||
"engines": {
|
||||
"node": "16.11.x"
|
||||
},
|
||||
@@ -51,7 +51,7 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/plugin-transform-runtime": "^7.19.6",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
@@ -65,31 +65,31 @@
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
"fs-extra": "11.1.0",
|
||||
"googleapis": "109.0.1",
|
||||
"googleapis": "110.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
"less": "^3.13.1",
|
||||
"lodash": "^4.17.21",
|
||||
"marked": "4.2.3",
|
||||
"marked": "4.2.4",
|
||||
"marked-extended-tables": "^1.0.5",
|
||||
"markedLegacy": "npm:marked@^0.3.19",
|
||||
"moment": "^2.29.4",
|
||||
"mongoose": "^6.7.5",
|
||||
"mongoose": "^6.8.1",
|
||||
"nanoid": "3.3.4",
|
||||
"nconf": "^0.12.0",
|
||||
"npm": "^8.10.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-frame-component": "4.1.3",
|
||||
"react-router-dom": "6.4.4",
|
||||
"react-router-dom": "6.6.0",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"superagent": "^6.1.0",
|
||||
"vitreum": "git+https://git@github.com/calculuschild/vitreum.git"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.28.0",
|
||||
"eslint": "^8.30.0",
|
||||
"eslint-plugin-react": "^7.31.11",
|
||||
"jest": "^29.2.2",
|
||||
"supertest": "^6.3.1"
|
||||
"supertest": "^6.3.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,6 @@ app.get('/edit/:id', asyncHandler(getBrew('edit')), (req, res, next)=>{
|
||||
title : req.brew.title || 'Untitled Brew',
|
||||
description : req.brew.description || 'No description.',
|
||||
image : req.brew.thumbnail || defaultMetaTags.image,
|
||||
|
||||
type : 'article'
|
||||
};
|
||||
|
||||
@@ -341,7 +340,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
data.title = 'Account Information Page';
|
||||
|
||||
let auth;
|
||||
let files;
|
||||
let googleCount = [];
|
||||
if(req.account) {
|
||||
if(req.account.googleId) {
|
||||
try {
|
||||
@@ -353,9 +352,9 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
}
|
||||
if(auth.credentials.access_token) {
|
||||
try {
|
||||
files = await GoogleActions.listGoogleBrews(auth);
|
||||
googleCount = await GoogleActions.listGoogleBrews(auth);
|
||||
} catch (e) {
|
||||
files = undefined;
|
||||
googleCount = undefined;
|
||||
console.log('List Google files failed!');
|
||||
console.log(e);
|
||||
}
|
||||
@@ -363,18 +362,19 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
}
|
||||
|
||||
const query = { authors: req.account.username, googleId: { $exists: false } };
|
||||
const brews = await HomebrewModel.find(query, 'id')
|
||||
const mongoCount = await HomebrewModel.countDocuments(query)
|
||||
.catch((err)=>{
|
||||
mongoCount = 0;
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
data.uiItems = {
|
||||
username : req.account.username,
|
||||
issued : req.account.issued,
|
||||
mongoCount : brews.length,
|
||||
googleId : Boolean(req.account.googleId),
|
||||
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
|
||||
fileCount : files?.length || '-'
|
||||
username : req.account.username,
|
||||
issued : req.account.issued,
|
||||
googleId : Boolean(req.account.googleId),
|
||||
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
|
||||
mongoCount : mongoCount,
|
||||
googleCount : googleCount?.length
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -253,7 +253,6 @@ const GoogleActions = {
|
||||
text : file.data,
|
||||
|
||||
description : obj.data.description,
|
||||
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,
|
||||
|
||||
@@ -27,7 +27,7 @@ const getId = (req)=>{
|
||||
return { id, googleId };
|
||||
};
|
||||
|
||||
const getBrew = (accessType)=>{
|
||||
const getBrew = (accessType, stubOnly = false)=>{
|
||||
// Create middleware with the accessType passed in as part of the scope
|
||||
return async (req, res, next)=>{
|
||||
// Get relevant IDs for the brew
|
||||
@@ -45,7 +45,7 @@ const getBrew = (accessType)=>{
|
||||
stub = stub?.toObject();
|
||||
|
||||
// If there is a google id, try to find the google brew
|
||||
if(googleId || stub?.googleId) {
|
||||
if(!stubOnly && (googleId || stub?.googleId)) {
|
||||
let googleError;
|
||||
const googleBrew = await GoogleActions.getGoogleBrew(googleId || stub?.googleId, id, accessType)
|
||||
.catch((err)=>{
|
||||
@@ -57,16 +57,24 @@ const getBrew = (accessType)=>{
|
||||
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
|
||||
stub = stub ? _.assign({ ...excludeStubProps(stub), stubbed: true }, excludeGoogleProps(googleBrew)) : googleBrew;
|
||||
}
|
||||
const authorsExist = stub?.authors?.length > 0;
|
||||
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.`;
|
||||
}
|
||||
|
||||
// If after all of that we still don't have a brew, throw an exception
|
||||
if(!stub) {
|
||||
if(!stub && !stubOnly) {
|
||||
throw 'Brew not found in Homebrewery database or Google Drive';
|
||||
}
|
||||
|
||||
if(typeof stub.tags === 'string') {
|
||||
if(typeof stub?.tags === 'string') {
|
||||
stub.tags = [];
|
||||
}
|
||||
req.brew = stub;
|
||||
req.brew = stub || {};
|
||||
|
||||
next();
|
||||
};
|
||||
@@ -108,7 +116,7 @@ const excludePropsFromUpdate = (brew)=>{
|
||||
|
||||
const excludeGoogleProps = (brew)=>{
|
||||
const modified = _.clone(brew);
|
||||
const propsToExclude = ['tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail'];
|
||||
const propsToExclude = ['version', 'tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail'];
|
||||
for (const prop of propsToExclude) {
|
||||
delete modified[prop];
|
||||
}
|
||||
@@ -116,7 +124,7 @@ const excludeGoogleProps = (brew)=>{
|
||||
};
|
||||
|
||||
const excludeStubProps = (brew)=>{
|
||||
const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount', 'version'];
|
||||
const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount'];
|
||||
for (const prop of propsToExclude) {
|
||||
brew[prop] = undefined;
|
||||
}
|
||||
@@ -184,7 +192,13 @@ const newBrew = async (req, res)=>{
|
||||
|
||||
const updateBrew = async (req, res)=>{
|
||||
// Initialize brew from request and body, destructure query params, set a constant for the google id, and set the initial value for the after-save method
|
||||
let brew = _.assign(req.brew, excludePropsFromUpdate(req.body));
|
||||
const brewFromClient = excludePropsFromUpdate(req.body);
|
||||
if(req.brew.version > brewFromClient.version) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.status(409).send(JSON.stringify({ message: `The brew has been changed on a different device. Please save your changes elsewhere, refresh, and try again.` }));
|
||||
}
|
||||
|
||||
let brew = _.assign(req.brew, brewFromClient);
|
||||
const { saveToGoogle, removeFromGoogle } = req.query;
|
||||
const googleId = brew.googleId;
|
||||
let afterSave = async ()=>true;
|
||||
@@ -230,28 +244,29 @@ const updateBrew = async (req, res)=>{
|
||||
brew.text = undefined;
|
||||
}
|
||||
brew.updatedAt = new Date();
|
||||
brew.version += 1;
|
||||
|
||||
if(req.account) {
|
||||
brew.authors = _.uniq(_.concat(brew.authors, req.account.username));
|
||||
brew.invitedAuthors = _.uniq(_.filter(brew.invitedAuthors, (a)=>req.account.username !== a));
|
||||
}
|
||||
|
||||
// Fetch the brew from the database again (if it existed there to begin with), and assign the existing brew to it
|
||||
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
|
||||
|
||||
if(!brew.markModified) {
|
||||
// If it wasn't in the database, create a new db brew
|
||||
brew = new HomebrewModel(brew);
|
||||
// define a function to catch our save errors
|
||||
const saveError = (err)=>{
|
||||
console.error(err);
|
||||
res.status(err.status || 500).send(err.message || 'Unable to save brew to Homebrewery database');
|
||||
};
|
||||
let saved;
|
||||
if(!brew._id) {
|
||||
// if the brew does not have a stub id, create and save it, then write the new value back to the brew.
|
||||
saved = await new HomebrewModel(brew).save().catch(saveError);
|
||||
brew = saved?.toObject();
|
||||
} else {
|
||||
// if the brew does have a stub id, update it using the stub id as the key.
|
||||
brew = _.assign(await HomebrewModel.findOne({ _id: brew._id }), brew);
|
||||
saved = await brew.save()
|
||||
.catch(saveError);
|
||||
}
|
||||
|
||||
brew.markModified('authors');
|
||||
brew.markModified('systems');
|
||||
|
||||
// Save the database brew
|
||||
const saved = await brew.save()
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
res.status(err.status || 500).send(err.message || 'Unable to save brew to Homebrewery database');
|
||||
});
|
||||
if(!saved) return;
|
||||
// Call and wait for afterSave to complete
|
||||
const after = await afterSave();
|
||||
@@ -327,8 +342,8 @@ const deleteBrew = async (req, res, next)=>{
|
||||
};
|
||||
|
||||
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.put('/api/:id', asyncHandler(getBrew('edit', true)), asyncHandler(updateBrew));
|
||||
router.put('/api/update/:id', asyncHandler(getBrew('edit', true)), asyncHandler(updateBrew));
|
||||
router.delete('/api/:id', asyncHandler(deleteBrew));
|
||||
router.get('/api/remove/:id', asyncHandler(deleteBrew));
|
||||
|
||||
|
||||
@@ -12,13 +12,14 @@ const HomebrewSchema = mongoose.Schema({
|
||||
textBin : { type: Buffer },
|
||||
pageCount : { type: Number, default: 1 },
|
||||
|
||||
description : { type: String, default: '' },
|
||||
tags : [String],
|
||||
systems : [String],
|
||||
renderer : { type: String, default: '' },
|
||||
authors : [String],
|
||||
published : { type: Boolean, default: false },
|
||||
thumbnail : { type: String, default: '' },
|
||||
description : { type: String, default: '' },
|
||||
tags : [String],
|
||||
systems : [String],
|
||||
renderer : { type: String, default: '' },
|
||||
authors : [String],
|
||||
invitedAuthors : [String],
|
||||
published : { type: Boolean, default: false },
|
||||
thumbnail : { type: String, default: '' },
|
||||
|
||||
createdAt : { type: Date, default: Date.now },
|
||||
updatedAt : { type: Date, default: Date.now },
|
||||
|
||||
Reference in New Issue
Block a user