0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-27 20:12:40 +00:00

Merge pull request #1050 from naturalcrit/googleDriveIntegration

Google drive integration
This commit is contained in:
Trevor Buckner
2020-10-12 14:18:10 -04:00
committed by GitHub
26 changed files with 1097 additions and 180 deletions

View File

@@ -55,7 +55,7 @@ module.exports = {
'array-bracket-spacing' : ['warn', 'never'],
'arrow-spacing' : ['warn', { before: false, after: false }],
'comma-spacing' : ['warn', { before: false, after: true }],
'indent' : ['warn', 'tab'],
'indent' : ['warn', 'tab', { 'MemberExpression': 'off' }],
'keyword-spacing' : ['warn', {
before : true,
after : true,

View File

@@ -1,5 +1,8 @@
# changelog
### Wednesday, 07/10/2020 - v2.10.0
- Google Drive integration -- Sign in with your Google account to link it with your Homebrewery profile. A new button in the Edit page will let you transfer your file to your personal Google Drive storage, and Google will keep a backup of each version! No more lost work surprises!
### Friday, 28/08/2020 - v2.9.2
- Many dependency updates
- Finally fixed this changelog page to not run off the edge :P
@@ -40,11 +43,13 @@
### Friday, 03/03/2017 - v2.7.3
- Increasing the range on the Partial Page Rendering for a quick-fix for it getting out of sync on long brews.
```
```
### Saturday, 18/02/2017 - v2.7.2
- Adding ability to delete a brew from the user page, incase the user creates a brew that makes the edit page unrender-able. (re:309)
```
```
### Thursday, 19/01/2017 - v2.7.0
- Fixed saving multiple authors and multiple systems on brew metadata (thanks u/PalaNolho re:282)
@@ -81,13 +86,13 @@
- Added in a snippet for a split table
- Added an account nav item to new page
\page
### Sunday, 27/11/2016 - v2.5.1
- Fixed the column rendering on the new user page. Really should have tested that better
- Added a hover tooltip to fully read the brew description
- Made the brew items take up only 25% allowing you to view more per row.
\page
### Wednesday, 23/11/2016 - v2.5.0
- Metadata can now be added to brews
- Added a metadata editor onto the edit and new pages
@@ -125,6 +130,9 @@
### Friday, 29/07/2016 - v2.2.7
- Adding in descriptive note blocks. (Thanks calculuschild!)
```
```
### Thursday, 07/07/2016 - v2.2.6
- Added a new nav item on the homepage for accessing both recently viewed and edited brews (thanks [ChosenSeraph!](https://github.com/stolksdorf/homebrewery/issues/147))
@@ -149,6 +157,9 @@
- Finally added a syntax for doing spell lists. A bit in-depth about why this took so long. Essentially I'm running out of syntax to use in stardard Markdown. There are too many unique elements in the PHB-style to be mapped. I solved this earlier by stacking certain elements together (eg. an `<hr>` before a `blockquote` turns it into moster state block), but those are getting unweildly. I would like to simply wrap these in `div`s with classes, but unfortunately Markdown stops processing when within HTML blocks. To get around this I wrote my own override to the Markdown parser and lexer to process Markdown within a simple div class wrapper. This should open the door for more unique syntaxes in the future. Big step!
- Override Ctrl+P (and cmd+P) to launch to the print page. Many people try to just print either the editing or share page to get a PDF. While this dones;t make much sense, I do get a ton of issues about it. So now if you try to do this, it'll just bring you imediately to the print page. Everybody wins!
- The onboarding flow has also been confusing a few users (Homepage -> new -> save -> edit page). If you edit the Homepage text now, a Call to Action to save your work will pop-up.
\page
- Added a 'Recently Edited' and 'Recently Viewed' nav item to the edit and share page respectively. Each will remember the last 8 items you edited or viewed and when you viewed it. Makes use of the new title attribute of brews to easy navigatation.
- Paragraphs now indent properly after lists (thanks u/slitjen!)
@@ -156,8 +167,6 @@
- Updated the issue template for (hopefully) better reporting
- Added suggestion to use chrome while PDF printing
\page
### Wednesday, 25/05/2016 -v2.0.5
- The class table generators have the proper ability score improvement progression.

View File

@@ -4,7 +4,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames'); //Unused variable
const DISMISS_KEY = 'dismiss_notification7-24-19';
const DISMISS_KEY = 'dismiss_notification7-10-20';
const NotificationPopup = createClass({
getInitialState : function() {
@@ -22,17 +22,22 @@ const NotificationPopup = createClass({
notifications : {
psa : function(){
return <li key='psa'>
<em>Known bug: Grey Shadow Boxes </em> <br />
The shadows around certain brew elements such as notes and statblocks might appear as a solid grey box when generating a PDF. &nbsp;
<a target='_blank' href='https://old.reddit.com/r/homebrewery/comments/ch3v0d/psa_grey_boxesshadows_around_notes_stat_blocks_etc/'>
See this Reddit post
</a> for updates and possible workarounds.
<em>Google Drive Integration!</em> <br />
We have added Google Drive integration to the Homebrewery! <a target='_blank' href='http://naturalcrit.com/login'>Sign in</a> with
your Google account to link it with your Homebrewery profile. A new button in the Edit page will let you transfer your file to your personal
Google Drive storage, and Google will keep a backup of each version! No more lost work surprises!
<br /><br />
However, we are aware that there may be uncaught bugs. We encourage you to copy your brew into a text document before transferring to Google
Drive just in case any issues arise as this update is rolled out.
<br /><br />
<b>Note:</b> Transferring an existing brew to Google Drive will change the edit and share links of your document. If you have shared your
document online, remember to update the links there as well.
</li>;
},
faq : function(){
return <li key='faq'>
<em>Protect your work! </em> <br />
At the moment we do not save a history of your projects, so please make frequent backups of your brews! &nbsp;
If you opt not to use your Google Drive, keep in mind that we do not save a history of your projects. Please make frequent backups of your brews! &nbsp;
<a target='_blank' href='https://www.reddit.com/r/homebrewery/comments/adh6lh/faqs_psas_announcements/'>
See the FAQ
</a> to learn how to avoid losing your work!

View File

@@ -9,11 +9,11 @@
.notificationPopup{
position : relative;
float : right;
display : inline-block;
display : inline-block;
width : 350px;
padding : 20px;
padding : 15px;
padding-bottom : 10px;
padding-left : 85px;
padding-left : 55px;
background-color : @blue;
color : white;
a{
@@ -22,8 +22,8 @@
}
i.info{
position : absolute;
top : 24px;
left : 24px;
top : 12px;
left : 12px;
opacity : 0.8;
font-size : 2.5em;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -2,17 +2,33 @@ const React = require('react');
const createClass = require('create-react-class');
const Nav = require('naturalcrit/nav/nav.jsx');
module.exports = function(props){
if(global.account){
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
{global.account.username}
const Account = createClass({
getInitialState : function() {
return {
url : ''
};
},
componentDidMount : function(){
if(typeof window !== 'undefined'){
this.setState({
url : window.location.href
});
}
},
render : function(){
if(global.account){
return <Nav.item href={`/user/${global.account.username}`} color='yellow' icon='fa-user'>
{global.account.username}
</Nav.item>;
}
return <Nav.item href={`http://naturalcrit.com/login?redirect=${this.state.url}`} color='teal' icon='fa-sign-in'>
login
</Nav.item>;
}
let url = '';
if(typeof window !== 'undefined'){
url = window.location.href;
}
return <Nav.item href={`http://naturalcrit.com/login?redirect=${url}`} color='teal' icon='fa-sign-in'>
login
</Nav.item>;
};
});
module.exports = Account;

View File

@@ -4,6 +4,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const Nav = require('naturalcrit/nav/nav.jsx');
const PatreonNavItem = require('./patreon.navitem.jsx');
const Navbar = createClass({
getInitialState : function() {
@@ -40,7 +41,7 @@ const Navbar = createClass({
<div>The Homebrewery</div>
</Nav.item>
<Nav.item>{`v${this.state.ver}`}</Nav.item>
<PatreonNavItem />
{/*this.renderChromeWarning()*/}
</Nav.section>
{this.props.children}

View File

@@ -1,4 +1,12 @@
@navbarHeight : 28px;
@keyframes coloring {
//from {color: white;}
//to {color: red;}
0% {color: pink;}
50% {color: pink;}
75% {color: red;}
100% {color: pink;}
}
.homebrew nav{
.homebrewLogo{
.animate(color);
@@ -47,11 +55,16 @@
text-transform : initial;
}
.patreon.navItem{
border-left : 1px solid #666;
border-right : 1px solid #666;
&:hover i {
color: red;
}
i{
.animate(color);
&:hover{
color : @red;
}
animation-name: coloring;
animation-duration: 2s;
color: pink;
}
}
.recent.navItem{
@@ -125,4 +138,4 @@
text-align : center;
}
}
}
}

View File

@@ -6,9 +6,9 @@ module.exports = function(props){
return <Nav.item
className='patreon'
newTab={true}
href='https://www.patreon.com/stolksdorf'
href='https://www.patreon.com/NaturalCrit'
color='green'
icon='fa-heart'>
help out
</Nav.item>;
};
};

View File

@@ -1,3 +1,4 @@
/* eslint-disable max-lines */
require('./editPage.less');
const React = require('react');
const createClass = require('create-react-class');
@@ -20,8 +21,10 @@ const BrewRenderer = require('../../brewRenderer/brewRenderer.jsx');
const Markdown = require('naturalcrit/markdown.js');
const SAVE_TIMEOUT = 3000;
const googleDriveActive = require('../../googleDrive.png');
const googleDriveInactive = require('../../googleDriveMono.png');
const SAVE_TIMEOUT = 3000;
const EditPage = createClass({
getDefaultProps : function() {
@@ -32,6 +35,7 @@ const EditPage = createClass({
editId : null,
createdAt : null,
updatedAt : null,
gDrive : false,
title : '',
description : '',
@@ -49,13 +53,19 @@ const EditPage = createClass({
isSaving : false,
isPending : false,
saveGoogle : this.props.brew.googleId ? true : false,
errors : null,
htmlErrors : Markdown.validate(this.props.brew.text),
url : ''
};
},
savedBrew : null,
componentDidMount : function(){
this.setState({
url : window.location.href
});
this.trySave();
window.onbeforeunload = ()=>{
if(this.state.isSaving || this.state.isPending){
@@ -74,7 +84,6 @@ const EditPage = createClass({
document.removeEventListener('keydown', this.handleControlKeys);
},
handleControlKeys : function(e){
if(!(e.ctrlKey || e.metaKey)) return;
const S_KEY = 83;
@@ -126,7 +135,15 @@ const EditPage = createClass({
}
},
save : function(){
toggleGoogleStorage : function(){
this.setState((prevState)=>({
saveGoogle : !prevState.saveGoogle,
isSaving : false,
errors : null
}), ()=>this.trySave());
},
save : async function(){
if(this.debounceSave && this.debounceSave.cancel) this.debounceSave.cancel();
this.setState((prevState)=>({
@@ -135,22 +152,99 @@ const EditPage = createClass({
htmlErrors : Markdown.validate(prevState.brew.text)
}));
request
.put(`/api/${this.props.brew.editId}`)
.send(this.state.brew)
.end((err, res)=>{
if(err){
this.setState({
errors : err,
});
} else {
this.savedBrew = res.body;
this.setState({
isPending : false,
isSaving : false,
});
}
});
const transfer = this.state.saveGoogle == _.isNil(this.state.brew.googleId);
if(this.state.saveGoogle) {
if(transfer) {
const res = await request
.post('/api/newGoogle/')
.send(this.state.brew)
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
: 'Error Saving to Google!');
this.setState({ errors: err });
});
if(!res) { return; }
console.log('Deleting Local Copy');
await request.delete(`/api/${this.state.brew.editId}`)
.send()
.catch((err)=>{
console.log('Error deleting Local Copy');
});
this.savedBrew = res.body;
history.replaceState(null, null, `/edit/${this.savedBrew.googleId}${this.savedBrew.editId}`); //update URL to match doc ID
} else {
const res = await request
.put(`/api/updateGoogle/${this.state.brew.editId}`)
.send(this.state.brew)
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
: 'Error Saving to Google!');
this.setState({ errors: err });
return;
});
this.savedBrew = res.body;
}
} else {
if(transfer) {
const res = await request.post('/api')
.send(this.state.brew)
.catch((err)=>{
console.log('Error creating Local Copy');
this.setState({ errors: err });
return;
});
await request.get(`/api/removeGoogle/${this.state.brew.googleId}${this.state.brew.editId}`)
.send()
.catch((err)=>{
console.log('Error Deleting Google Brew');
});
this.savedBrew = res.body;
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`); //update URL to match doc ID
} else {
const res = await request
.put(`/api/update/${this.state.brew.editId}`)
.send(this.state.brew)
.catch((err)=>{
console.log('Error Updating Local Brew');
this.setState({ errors: err });
return;
});
this.savedBrew = res.body;
}
}
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, {
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId
}),
isPending : false,
isSaving : false,
}));
},
renderGoogleDriveIcon : function(){
if(this.state.saveGoogle) {
return <Nav.item className='googleDriveStorage' onClick={this.toggleGoogleStorage}>
<img src={googleDriveActive} alt='googleDriveActive' />
</Nav.item>;
} else {
return <Nav.item className='googleDriveStorage' onClick={this.toggleGoogleStorage}>
<img src={googleDriveInactive} alt='googleDriveInactive' />
</Nav.item>;
}
},
renderSaveButton : function(){
@@ -161,6 +255,19 @@ const EditPage = createClass({
errMsg += `\`\`\`\n${JSON.stringify(this.state.errors.response.error, null, ' ')}\n\`\`\``;
} catch (e){}
if(this.state.errors.status == '401'){
return <Nav.item className='save error' icon='fa-warning'>
Oops!
<div className='errorContainer'>
You must be signed in to a Google account
to save this to Google Drive!<br />
Sign in <a target='_blank' rel='noopener noreferrer'
href={`http://naturalcrit.com/login?redirect=${this.state.url}`}>
here</a>.
</div>
</Nav.item>;
}
return <Nav.item className='save error' icon='fa-warning'>
Oops!
<div className='errorContainer'>
@@ -183,6 +290,13 @@ const EditPage = createClass({
return <Nav.item className='save saved'>saved.</Nav.item>;
}
},
processShareId : function() {
return this.state.brew.googleId ?
this.state.brew.googleId + this.state.brew.shareId :
this.state.brew.shareId;
},
renderNavbar : function(){
return <Navbar>
<Nav.section>
@@ -190,12 +304,13 @@ const EditPage = createClass({
</Nav.section>
<Nav.section>
{this.renderGoogleDriveIcon()}
{this.renderSaveButton()}
<ReportIssue />
<Nav.item newTab={true} href={`/share/${this.props.brew.shareId}`} color='teal' icon='fa-share-alt'>
<Nav.item newTab={true} href={`/share/${this.processShareId()}`} color='teal' icon='fa-share-alt'>
Share
</Nav.item>
<PrintLink shareId={this.props.brew.shareId} />
<PrintLink shareId={this.processShareId()} />
<RecentNavItem brew={this.props.brew} storageKey='edit' />
<Account />
</Nav.section>

View File

@@ -15,8 +15,8 @@
top : 29px;
left : -20px;
z-index : 1000;
width : 120px;
padding : 8px;
width : 135px;
padding : 6px;
background-color : #333;
a{
color : @teal;
@@ -24,4 +24,9 @@
}
}
}
}
.googleDriveStorage img{
height : 20px;
padding : 0px;
margin : -5px;
}
}

View File

@@ -8,7 +8,6 @@ const { Meta } = require('vitreum/headtags');
const Nav = require('naturalcrit/nav/nav.jsx');
const Navbar = require('../../navbar/navbar.jsx');
const PatreonNavItem = require('../../navbar/patreon.navitem.jsx');
const IssueNavItem = require('../../navbar/issue.navitem.jsx');
const RecentNavItem = require('../../navbar/recent.navitem.jsx').both;
const AccountNavItem = require('../../navbar/account.navitem.jsx');
@@ -56,7 +55,6 @@ const HomePage = createClass({
renderNavbar : function(){
return <Navbar ver={this.props.ver}>
<Nav.section>
<PatreonNavItem />
<IssueNavItem />
<Nav.item newTab={true} href='/changelog' color='purple' icon='fa-file-text-o'>
Changelog

View File

@@ -24,6 +24,7 @@ const NewPage = createClass({
getInitialState : function() {
return {
metadata : {
gDrive : false,
title : '',
description : '',
tags : '',
@@ -32,11 +33,13 @@ const NewPage = createClass({
systems : []
},
text : '',
isSaving : false,
errors : []
text : '',
isSaving : false,
saveGoogle : (global.account && global.account.googleId ? true : false),
errors : []
};
},
componentDidMount : function() {
const storage = localStorage.getItem(KEY);
if(storage){
@@ -80,12 +83,30 @@ const NewPage = createClass({
localStorage.setItem(KEY, text);
},
save : function(){
save : async function(){
this.setState({
isSaving : true
});
request.post('/api')
console.log('saving new brew');
if(this.state.saveGoogle) {
const res = await request
.post('/api/newGoogle/')
.send(_.merge({}, this.state.metadata, { text: this.state.text }))
.catch((err)=>{
console.log(err.status === 401
? 'Not signed in!'
: 'Error Creating New Google Brew!');
this.setState({ isSaving: false });
return;
});
const brew = res.body;
localStorage.removeItem(KEY);
window.location = `/edit/${brew.googleId}${brew.editId}`;
} else {
request.post('/api')
.send(_.merge({}, this.state.metadata, {
text : this.state.text
}))
@@ -101,6 +122,8 @@ const NewPage = createClass({
localStorage.removeItem(KEY);
window.location = `/edit/${brew.editId}`;
});
}
},
renderSaveButton : function(){

View File

@@ -45,6 +45,12 @@ const SharePage = createClass({
}
},
processShareId : function() {
return this.props.brew.googleId ?
this.props.brew.googleId + this.props.brew.shareId :
this.props.brew.shareId;
},
render : function(){
return <div className='sharePage page'>
<Meta name='robots' content='noindex, nofollow' />
@@ -54,8 +60,8 @@ const SharePage = createClass({
</Nav.section>
<Nav.section>
<PrintLink shareId={this.props.brew.shareId} />
<Nav.item href={`/source/${this.props.brew.shareId}`} color='teal' icon='fa-code'>
<PrintLink shareId={this.processShareId()} />
<Nav.item href={`/source/${this.processShareId()}`} color='teal' icon='fa-code'>
source
</Nav.item>
<RecentNavItem brew={this.props.brew} storageKey='view' />

View File

@@ -6,6 +6,8 @@ const cx = require('classnames');
const moment = require('moment');
const request = require('superagent');
const googleDriveIcon = require('../../../googleDrive.png');
const BrewItem = createClass({
getDefaultProps : function() {
return {
@@ -27,11 +29,19 @@ const BrewItem = createClass({
if(!confirm('Are you REALLY sure? You will lose editor access to this document.')) return;
}
request.delete(`/api/${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
if(this.props.brew.googleId) {
request.get(`/api/removeGoogle/${this.props.brew.googleId}${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
} else {
request.delete(`/api/${this.props.brew.editId}`)
.send()
.end(function(err, res){
location.reload();
});
}
},
renderDeleteBrewLink : function(){
@@ -41,14 +51,41 @@ const BrewItem = createClass({
<i className='fa fa-trash' />
</a>;
},
renderEditLink : function(){
if(!this.props.brew.editId) return;
return <a href={`/edit/${this.props.brew.editId}`} target='_blank' rel='noopener noreferrer'>
let editLink = this.props.brew.editId;
if(this.props.brew.googleId) {
editLink = this.props.brew.googleId + editLink;
}
return <a href={`/edit/${editLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fa fa-pencil' />
</a>;
},
renderShareLink : function(){
if(!this.props.brew.shareId) return;
let shareLink = this.props.brew.shareId;
if(this.props.brew.googleId) {
shareLink = this.props.brew.googleId + shareLink;
}
return <a href={`/share/${shareLink}`} target='_blank' rel='noopener noreferrer'>
<i className='fa fa-share-alt' />
</a>;
},
renderGoogleDriveIcon : function(){
if(!this.props.brew.gDrive) return;
return <span>
<img className='googleDriveIcon' src={googleDriveIcon} alt='googleDriveIcon' />
</span>;
},
render : function(){
const brew = this.props.brew;
return <div className='brewItem'>
@@ -66,12 +103,11 @@ const BrewItem = createClass({
<span>
<i className='fa fa-refresh' /> {moment(brew.updatedAt).fromNow()}
</span>
{this.renderGoogleDriveIcon()}
</div>
<div className='links'>
<a href={`/share/${brew.shareId}`} target='_blank' rel='noopener noreferrer'>
<i className='fa fa-share-alt' />
</a>
{this.renderShareLink()}
{this.renderEditLink()}
{this.renderDeleteBrewLink()}
</div>

View File

@@ -7,6 +7,7 @@
box-sizing : border-box;
overflow : hidden;
width : 48%;
min-height : 80px;
margin-right : 15px;
margin-bottom : 15px;
padding : 5px 15px 5px 8px;
@@ -55,6 +56,14 @@
&:hover{
opacity : 1;
}
i{
cursor : pointer;
}
}
}
}
.googleDriveIcon {
height : 20px;
padding : 0px;
margin : -5px;
}
}

View File

@@ -22,8 +22,9 @@ const BrewItem = require('./brewItem/brewItem.jsx');
const UserPage = createClass({
getDefaultProps : function() {
return {
username : '',
brews : []
username : '',
brews : [],
googleBrews : []
};
},

266
package-lock.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "homebrewery",
"version": "2.8.2",
"version": "2.10.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -1732,6 +1732,14 @@
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"requires": {
"event-target-shim": "^5.0.0"
}
},
"accepts": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
@@ -1782,6 +1790,29 @@
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz",
"integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ=="
},
"agent-base": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.1.tgz",
"integrity": "sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==",
"requires": {
"debug": "4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
@@ -1937,6 +1968,11 @@
"function-bind": "^1.1.1"
}
},
"arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug=="
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
@@ -2087,6 +2123,11 @@
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"bignumber.js": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz",
"integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="
},
"binary-extensions": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
@@ -2487,6 +2528,11 @@
"ieee754": "^1.1.4"
}
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@@ -3018,6 +3064,11 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
"deep-object-diff": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.0.tgz",
"integrity": "sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw=="
},
"defer-to-connect": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz",
@@ -3193,6 +3244,14 @@
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -3553,6 +3612,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
},
"events": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz",
@@ -3648,6 +3712,11 @@
}
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
@@ -3755,6 +3824,11 @@
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
"integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA=="
},
"fast-text-encoding": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz",
"integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig=="
},
"fbjs": {
"version": "0.8.16",
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
@@ -3894,6 +3968,39 @@
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
"dev": true
},
"gaxios": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.1.0.tgz",
"integrity": "sha512-DDTn3KXVJJigtz+g0J3vhcfbDbKtAroSTxauWsdnP57sM5KZ3d2c/3D9RKFJ86s43hfw6WULg6TXYw/AYiBlpA==",
"requires": {
"abort-controller": "^3.0.0",
"extend": "^3.0.2",
"https-proxy-agent": "^5.0.0",
"is-stream": "^2.0.0",
"node-fetch": "^2.3.0"
},
"dependencies": {
"is-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
"integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA=="
}
}
},
"gcp-metadata": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.4.tgz",
"integrity": "sha512-5J/GIH0yWt/56R3dNaNWPGQ/zXsZOddYECfJaqxFWgrZ9HC2Kvc5vl9upOgUUHKzURjAVf2N+f6tEJiojqXUuA==",
"requires": {
"gaxios": "^3.0.0",
"json-bigint": "^1.0.0"
}
},
"gensync": {
"version": "1.0.0-beta.1",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz",
@@ -3955,6 +4062,52 @@
"type-fest": "^0.8.1"
}
},
"google-auth-library": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.6.tgz",
"integrity": "sha512-fWYdRdg55HSJoRq9k568jJA1lrhg9i2xgfhVIMJbskUmbDpJGHsbv9l41DGhCDXM21F9Kn4kUwdysgxSYBYJUw==",
"requires": {
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
"fast-text-encoding": "^1.0.0",
"gaxios": "^3.0.0",
"gcp-metadata": "^4.1.0",
"gtoken": "^5.0.0",
"jws": "^4.0.0",
"lru-cache": "^6.0.0"
}
},
"google-p12-pem": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.2.tgz",
"integrity": "sha512-tbjzndQvSIHGBLzHnhDs3cL4RBjLbLXc2pYvGH+imGVu5b4RMAttUTdnmW2UH0t11QeBTXZ7wlXPS7hrypO/tg==",
"requires": {
"node-forge": "^0.9.0"
}
},
"googleapis": {
"version": "59.0.0",
"resolved": "https://registry.npmjs.org/googleapis/-/googleapis-59.0.0.tgz",
"integrity": "sha512-GV/E4KRN89a4GxSk7D7cwUfRYgcJHR05sOgm/WGdwc/u8dxNXG5lWmz9gF5ZwFGk2yKtVxL4VZNn4zBuZ6rmGg==",
"requires": {
"google-auth-library": "^6.0.0",
"googleapis-common": "^4.4.0"
}
},
"googleapis-common": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-4.4.0.tgz",
"integrity": "sha512-Bgrs8/1OZQFFIfVuX38L9t48rPAkVUXttZy6NzhhXxFOEMSHgfFIjxou7RIXOkBHxmx2pVwct9WjKkbnqMYImQ==",
"requires": {
"extend": "^3.0.2",
"gaxios": "^3.0.0",
"google-auth-library": "^6.0.0",
"qs": "^6.7.0",
"url-template": "^2.0.8",
"uuid": "^8.0.0"
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -3978,6 +4131,24 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
"integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
},
"gtoken": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.3.tgz",
"integrity": "sha512-Nyd1wZCMRc2dj/mAD0LlfQLcAO06uKdpKJXvK85SGrF5+5+Bpfil9u/2aw35ltvEHjvl0h5FMKN5knEU+9JrOg==",
"requires": {
"gaxios": "^3.0.0",
"google-p12-pem": "^3.0.0",
"jws": "^4.0.0",
"mime": "^2.2.0"
},
"dependencies": {
"mime": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
}
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -4154,6 +4325,30 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"requires": {
"agent-base": "6",
"debug": "4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"iconv-lite": {
"version": "0.4.21",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz",
@@ -4553,6 +4748,14 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
},
"json-bigint": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
"requires": {
"bignumber.js": "^9.0.0"
}
},
"json-buffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz",
@@ -4622,6 +4825,25 @@
"object.assign": "^4.1.0"
}
},
"jwa": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
"integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
"requires": {
"jwa": "^2.0.0",
"safe-buffer": "^5.0.1"
}
},
"jwt-simple": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/jwt-simple/-/jwt-simple-0.5.6.tgz",
@@ -4783,6 +5005,14 @@
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
"integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -5203,9 +5433,9 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nanoid": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz",
"integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA=="
"version": "3.1.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
"integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A=="
},
"nanomatch": {
"version": "1.2.13",
@@ -5269,6 +5499,11 @@
"is-stream": "^1.0.1"
}
},
"node-forge": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.1.tgz",
"integrity": "sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ=="
},
"node-releases": {
"version": "1.1.60",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz",
@@ -6591,14 +6826,6 @@
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg=="
},
"shortid": {
"version": "2.2.15",
"resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.15.tgz",
"integrity": "sha512-5EaCy2mx2Jgc/Fdn9uuDuNIIfWBpzY4XIlhoqtXF6qsf+/+SGZ+FxDdX/ZsMZiWupIWNqAEmiNY4RC+LSmCeOw==",
"requires": {
"nanoid": "^2.1.0"
}
},
"side-channel": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz",
@@ -7496,6 +7723,11 @@
"prepend-http": "^2.0.0"
}
},
"url-template": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
"integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE="
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
@@ -7519,6 +7751,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
},
"v8-compile-cache": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
@@ -7856,6 +8093,11 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"yargs": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "homebrewery",
"description": "Create authentic looking D&D homebrews using only markdown",
"version": "2.8.2",
"version": "2.10.0",
"engines": {
"node": "12.16.x"
},
@@ -50,19 +50,20 @@
"create-react-class": "^15.6.3",
"express": "^4.17.1",
"fs-extra": "9.0.1",
"googleapis": "59.0.0",
"jwt-simple": "^0.5.6",
"less": "^3.12.2",
"lodash": "^4.17.20",
"marked": "^0.3.19",
"moment": "^2.27.0",
"mongoose": "^5.10.0",
"nanoid": "3.1.12",
"nconf": "^0.10.0",
"prop-types": "15.7.2",
"query-string": "6.13.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-router-dom": "5.2.0",
"shortid": "^2.2.15",
"superagent": "^6.0.0",
"vitreum": "github:calculuschild/vitreum#21a8e1c9421f1d3a3b474c12f480feb2fbd28c5b"
},

View File

@@ -1,7 +1,7 @@
const fs = require('fs-extra');
const Proj = require('./project.json');
const { pack } = require('vitreum');
const { pack, watchFile, livereload } = require('vitreum');
const isDev = !!process.argv.find((arg)=>arg=='--dev');
const lessTransform = require('vitreum/transforms/less.js');
@@ -29,3 +29,12 @@ pack('./client/homebrew/homebrew.jsx', {
})
.then(build)
.catch(console.error);
//In development set up a watch server and livereload
if(isDev){
livereload('./build');
watchFile('./server.js', {
watch : ['./homebrew'] // Watch additional folders if you want
});
}

133
server.js
View File

@@ -3,6 +3,9 @@ const jwt = require('jwt-simple');
const express = require('express');
const app = express();
const homebrewApi = require('./server/homebrew.api.js');
const GoogleActions = require('./server/googleActions.js');
app.use(express.static(`${__dirname}/build`));
app.use(require('body-parser').json({ limit: '25mb' }));
app.use(require('cookie-parser')());
@@ -24,21 +27,30 @@ mongoose.connection.on('error', ()=>{
throw 'Can not connect to Mongo';
});
//Account Middleware
app.use((req, res, next)=>{
if(req.cookies && req.cookies.nc_session){
try {
req.account = jwt.decode(req.cookies.nc_session, config.get('secret'));
//console.log("Just loaded up JWT from cookie:");
//console.log(req.account);
} catch (e){}
}
req.config = {
google_client_id : config.get('google_client_id'),
google_client_secret : config.get('google_client_secret')
};
return next();
});
app.use(require('./server/homebrew.api.js'));
app.use(homebrewApi);
app.use(require('./server/admin.api.js'));
//app.use('/user',require('./server/user.routes.js'));
const HomebrewModel = require('./server/homebrew.model.js').model;
const welcomeText = require('fs').readFileSync('./client/homebrew/pages/homePage/welcome_msg.md', 'utf8');
@@ -53,70 +65,142 @@ app.get('/robots.txt', (req, res)=>{
//Source page
app.get('/source/:id', (req, res)=>{
HomebrewModel.get({ shareId: req.params.id })
if(req.params.id.length > 12) {
const googleId = req.params.id.slice(0, -12);
const shareId = req.params.id.slice(-12);
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
.then((brew)=>{
const text = brew.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
return res.send(`<code><pre style="white-space: pre-wrap;">${text}</pre></code>`);
})
.catch((err)=>{
console.log(err);
return res.status(404).send('Could not find Homebrew with that id');
return res.status(400).send('Can\'t get brew from Google');
});
} else {
HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{
const text = brew.text.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
return res.send(`<code><pre style="white-space: pre-wrap;">${text}</pre></code>`);
})
.catch((err)=>{
console.log(err);
return res.status(404).send('Could not find Homebrew with that id');
});
}
});
//User Page
app.get('/user/:username', (req, res, next)=>{
app.get('/user/:username', async (req, res, next)=>{
const fullAccess = req.account && (req.account.username == req.params.username);
HomebrewModel.getByUser(req.params.username, fullAccess)
.then((brews)=>{
req.brews = brews;
return next();
})
let googleBrews = [];
if(req.account && req.account.googleId){
googleBrews = await GoogleActions.listGoogleBrews(req, res)
.catch((err)=>{
console.log(err);
console.error(err);
});
}
const brews = await HomebrewModel.getByUser(req.params.username, fullAccess)
.catch((err)=>{
console.log(err);
});
if(googleBrews) {
req.brews = _.concat(brews, googleBrews);
} else {req.brews = brews;}
return next();
});
//Edit Page
app.get('/edit/:id', (req, res, next)=>{
HomebrewModel.get({ editId: req.params.id })
if(req.params.id.length > 12) {
const googleId = req.params.id.slice(0, -12);
const editId = req.params.id.slice(-12);
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, editId, 'edit')
.then((brew)=>{
req.brew = brew.sanatize();
req.brew = brew; //TODO Need to sanitize later
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
return res.status(400).send('Can\'t get brew from Google');
});
} else {
HomebrewModel.get({ editId: req.params.id })
.then((brew)=>{
req.brew = brew.sanatize();
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
});
}
});
//Share Page
app.get('/share/:id', (req, res, next)=>{
HomebrewModel.get({ shareId: req.params.id })
if(req.params.id.length > 12) {
const googleId = req.params.id.slice(0, -12);
const shareId = req.params.id.slice(-12);
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
.then((brew)=>{
return brew.increaseView();
})
.then((brew)=>{
req.brew = brew.sanatize(true);
req.brew = brew; //TODO Need to sanitize later
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
return res.status(400).send('Can\'t get brew from Google');
});
} else {
HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{
return brew.increaseView();
})
.then((brew)=>{
req.brew = brew.sanatize(true);
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
});
}
});
//Print Page
app.get('/print/:id', (req, res, next)=>{
HomebrewModel.get({ shareId: req.params.id })
if(req.params.id.length > 12) {
const googleId = req.params.id.slice(0, -12);
const shareId = req.params.id.slice(-12);
GoogleActions.readFileMetadata(config.get('google_api_key'), googleId, shareId, 'share')
.then((brew)=>{
req.brew = brew.sanatize(true);
req.brew = brew; //TODO Need to sanitize later
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
return res.status(400).send('Can\'t get brew from Google');
});
} else {
HomebrewModel.get({ shareId: req.params.id })
.then((brew)=>{
req.brew = brew.sanatize(true);
return next();
})
.catch((err)=>{
console.log(err);
return res.status(400).send(`Can't get that`);
});
}
});
app.get('/source/:id', (req, res)=>{
});
@@ -131,10 +215,11 @@ app.use((req, res)=>{
changelog : changelogText,
brew : req.brew,
brews : req.brews,
googleBrews : req.googleBrews,
account : req.account,
};
templateFn('homebrew', props)
.then((page)=>res.send(page))
.then((page)=>{res.send(page);})
.catch((err)=>{
console.log(err);
return res.sendStatus(500);

312
server/googleActions.js Normal file
View File

@@ -0,0 +1,312 @@
/* eslint-disable max-lines */
const _ = require('lodash');
const { google } = require('googleapis');
const { nanoid } = require('nanoid');
const token = require('./token.js');
const config = require('nconf')
.argv()
.env({ lowerCase: true }) // Load environment variables
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
.file('defaults', { file: 'config/default.json' });
//let oAuth2Client;
GoogleActions = {
authCheck : (account, res)=>{
if(!account || !account.googleId){ // If not signed into Google
const err = new Error('Not Signed In');
err.status = 401;
throw err;
}
const oAuth2Client = new google.auth.OAuth2(
config.get('google_client_id'),
config.get('google_client_secret'),
'/auth/google/redirect'
);
oAuth2Client.setCredentials({
access_token : account.googleAccessToken, //Comment out to refresh token
refresh_token : account.googleRefreshToken
});
oAuth2Client.on('tokens', (tokens)=>{
if(tokens.refresh_token) {
account.googleRefreshToken = tokens.refresh_token;
}
account.googleAccessToken = tokens.access_token;
const JWTToken = token.generateAccessToken(account);
//Save updated token to cookie
//res.cookie('nc_session', JWTToken, { maxAge: 1000*60*60*24*365, path: '/', sameSite: 'lax' });
res.cookie('nc_session', JWTToken, { maxAge: 1000*60*60*24*365, path: '/', sameSite: 'lax', domain: '.naturalcrit.com' });
});
return oAuth2Client;
},
getGoogleFolder : async (auth)=>{
const drive = google.drive({ version: 'v3', auth: auth });
fileMetadata = {
'name' : 'Homebrewery',
'mimeType' : 'application/vnd.google-apps.folder'
};
const obj = await drive.files.list({
q : 'mimeType = \'application/vnd.google-apps.folder\''
})
.catch((err)=>{
console.log('Error searching Google Drive Folders');
console.error(err);
});
let folderId;
if(obj.data.files.length == 0){
const obj = await drive.files.create({
resource : fileMetadata
})
.catch((err)=>{
console.log('Error creating google app folder');
console.error(err);
});
console.log('created new drive folder with ID:');
console.log(obj.data.id);
folderId = obj.data.id;
} else {
folderId = obj.data.files[0].id;
}
return folderId;
},
listGoogleBrews : async (req, res)=>{
oAuth2Client = GoogleActions.authCheck(req.account, res);
const drive = google.drive({ version: 'v3', auth: oAuth2Client });
const obj = await drive.files.list({
pageSize : 100,
fields : 'nextPageToken, files(id, name, modifiedTime, properties)',
q : 'mimeType != \'application/vnd.google-apps.folder\' and trashed = false'
})
.catch((err)=>{
return console.error(`Error Listing Google Brews: ${err}`);
});
if(!obj.data.files.length) {
console.log('No files found.');
}
const brews = obj.data.files.map((file)=>{
return {
text : '',
shareId : file.properties.shareId,
editId : file.properties.editId,
createdAt : null,
updatedAt : file.modifiedTime,
gDrive : true,
googleId : file.id,
title : file.properties.title,
description : '',
tags : '',
published : false,
authors : [req.account.username], //TODO: properly save and load authors to google drive
systems : []
};
});
return brews;
},
existsGoogleBrew : async (auth, id)=>{
const drive = google.drive({ version: 'v3', auth: auth });
const result = await drive.files.get({ fileId: id })
.catch((err)=>{
return false;
});
if(result){return true;}
return false;
},
updateGoogleBrew : async (auth, brew)=>{
const drive = google.drive({ version: 'v3', auth: auth });
if(await GoogleActions.existsGoogleBrew(auth, brew.googleId) == true) {
await drive.files.update({
fileId : brew.googleId,
resource : { name : `${brew.title}.txt`,
properties : { title: brew.title } //AppProperties is not accessible via API key
},
media : { mimeType : 'text/plain',
body : brew.text }
})
.catch((err)=>{
console.log('Error saving to google');
console.error(err);
//return res.status(500).send('Error while saving');
});
}
return (brew);
},
newGoogleBrew : async (auth, brew)=>{
const drive = google.drive({ version: 'v3', auth: auth });
const media = {
mimeType : 'text/plain',
body : brew.text
};
const folderId = await GoogleActions.getGoogleFolder(auth);
const fileMetadata = {
'name' : `${brew.title}.txt`,
'parents' : [folderId],
'properties' : { //AppProperties is not accessible
'shareId' : nanoid(12),
'editId' : nanoid(12),
'title' : brew.title,
}
};
const obj = await drive.files.create({
resource : fileMetadata,
media : media
})
.catch((err)=>{
console.error(err);
return res.status(500).send('Error while creating google brew');
});
if(!obj) return;
await drive.permissions.create({
resource : { type : 'anyone',
role : 'writer' },
fileId : obj.data.id,
fields : 'id',
})
.catch((err)=>{
console.log('Error updating permissions');
console.error(err);
});
const newHomebrew = {
text : brew.text,
shareId : fileMetadata.properties.shareId,
editId : fileMetadata.properties.editId,
createdAt : null,
updatedAt : null,
gDrive : true,
googleId : obj.data.id,
title : brew.title,
description : '',
tags : '',
published : false,
authors : [],
systems : []
};
return newHomebrew;
},
readFileMetadata : async (auth, id, accessId, accessType)=>{
const drive = google.drive({ version: 'v3', auth: auth });
console.log(auth);
const obj = await drive.files.get({
fileId : id,
fields : 'properties'
})
.catch((err)=>{
console.log('Error loading from Google');
console.error(err);
return;
});
console.log(`ACCESS TYPE: ${accessType}`);
if(obj) {
if(accessType == 'edit' && obj.data.properties.editId != accessId){
throw ('Edit ID does not match');
} else if(accessType == 'share' && obj.data.properties.shareId != accessId){
throw ('Share ID does not match');
}
const file = await drive.files.get({
fileId : id,
alt : 'media'
})
.catch((err)=>{
console.log('Error getting file contents from Google');
console.error(err);
});
const brew = {
text : file.data,
shareId : obj.data.properties.shareId,
editId : obj.data.properties.editId,
createdAt : null,
updatedAt : null,
gDrive : true,
googleId : id,
title : obj.data.properties.title,
description : '',
tags : '',
published : false,
authors : [],
systems : []
};
return (brew);
}
},
deleteGoogleBrew : async (req, res, id)=>{
oAuth2Client = GoogleActions.authCheck(req.account, res);
const drive = google.drive({ version: 'v3', auth: oAuth2Client });
const googleId = id.slice(0, -12);
const accessId = id.slice(-12);
const obj = await drive.files.get({
fileId : googleId,
fields : 'properties'
})
.catch((err)=>{
console.log('Error loading from Google');
console.error(err);
return;
});
if(obj && obj.data.properties.editId != accessId) {
throw ('Not authorized to delete this Google brew');
}
await drive.files.delete({
fileId : googleId
})
.catch((err)=>{
console.log('Can\'t delete Google file');
console.error(err);
});
return res.status(200).send();
}
};
module.exports = GoogleActions;

View File

@@ -2,6 +2,7 @@ const _ = require('lodash');
const HomebrewModel = require('./homebrew.model.js').model;
const router = require('express').Router();
const zlib = require('zlib');
const GoogleActions = require('./googleActions.js');
// const getTopBrews = (cb) => {
// HomebrewModel.find().sort({ views: -1 }).limit(5).exec(function(err, brews) {
@@ -20,17 +21,18 @@ const getGoodBrewTitle = (text)=>{
};
const newBrew = (req, res)=>{
const authors = (req.account) ? [req.account.username] : [];
const brew = req.body;
brew.authors = (req.account) ? [req.account.username] : [];
const newHomebrew = new HomebrewModel(_.merge({},
req.body,
{ authors: authors }
));
if(!newHomebrew.title) {
newHomebrew.title = getGoodBrewTitle(newHomebrew.text);
if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text);
}
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
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
@@ -41,7 +43,10 @@ const newBrew = (req, res)=>{
console.error(err, err.toString(), err.stack);
return res.status(500).send(`Error while creating new brew, ${err.toString()}`);
}
return res.json(obj);
obj = obj.toObject();
obj.gDrive = false;
return res.status(200).send(obj);
});
};
@@ -103,49 +108,48 @@ const deleteBrew = (req, res)=>{
});
};
const newGoogleBrew = async (req, res, next)=>{
let oAuth2Client;
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
const brew = req.body;
brew.authors = (req.account) ? [req.account.username] : [];
if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text);
}
delete brew.editId;
delete brew.shareId;
delete brew.googleId;
req.body = brew;
console.log(oAuth2Client);
const newBrew = await GoogleActions.newGoogleBrew(oAuth2Client, brew);
return res.status(200).send(newBrew);
};
const updateGoogleBrew = async (req, res, next)=>{
let oAuth2Client;
try { oAuth2Client = GoogleActions.authCheck(req.account, res); } catch (err) { return res.status(err.status).send(err.message); }
const updatedBrew = await GoogleActions.updateGoogleBrew(oAuth2Client, req.body);
return res.status(200).send(updatedBrew);
};
router.post('/api', newBrew);
router.post('/api/newGoogle/', newGoogleBrew);
router.put('/api/:id', updateBrew);
router.put('/api/update/:id', updateBrew);
router.put('/api/updateGoogle/:id', updateGoogleBrew);
router.delete('/api/:id', deleteBrew);
router.get('/api/remove/:id', deleteBrew);
router.get('/api/removeGoogle/:id', (req, res)=>{GoogleActions.deleteGoogleBrew(req, res, req.params.id);});
module.exports = router;
/*
module.exports = function(app) {
app;
app.get('/api/search', mw.adminOnly, function(req, res) {
var page = req.query.page || 0;
var count = req.query.count || 20;
var query = {};
if (req.query && req.query.id) {
query = {
"$or": [{
editId : req.query.id
}, {
shareId : req.query.id
}]
};
}
HomebrewModel.find(query, {
text : 0 //omit the text
}, {
skip: page*count,
limit: count*1
}, function(err, objs) {
if (err) console.error(err);
return res.json({
page : page,
count : count,
total : homebrewTotal,
brews : objs
});
});
})
return app;
}
*/

View File

@@ -1,11 +1,11 @@
const mongoose = require('mongoose');
const shortid = require('shortid');
const { nanoid } = require('nanoid');
const _ = require('lodash');
const zlib = require('zlib');
const HomebrewSchema = mongoose.Schema({
shareId : { type: String, default: shortid.generate, index: { unique: true } },
editId : { type: String, default: shortid.generate, index: { unique: true } },
shareId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } },
editId : { type: String, default: ()=>{return nanoid(12);}, index: { unique: true } },
title : { type: String, default: '' },
text : { type: String, default: '' },
textBin : { type: Buffer },
@@ -24,7 +24,6 @@ const HomebrewSchema = mongoose.Schema({
}, { versionKey: false });
HomebrewSchema.methods.sanatize = function(full=false){
const brew = this.toJSON();
delete brew._id;
@@ -35,7 +34,6 @@ HomebrewSchema.methods.sanatize = function(full=false){
return brew;
};
HomebrewSchema.methods.increaseView = function(){
return new Promise((resolve, reject)=>{
this.lastViewed = new Date();
@@ -47,8 +45,6 @@ HomebrewSchema.methods.increaseView = function(){
});
};
HomebrewSchema.statics.get = function(query){
return new Promise((resolve, reject)=>{
Homebrew.find(query, (err, brews)=>{
@@ -77,11 +73,9 @@ HomebrewSchema.statics.getByUser = function(username, allowAccess=false){
});
};
const Homebrew = mongoose.model('Homebrew', HomebrewSchema);
module.exports = {
schema : HomebrewSchema,
model : Homebrew,
};
};

33
server/token.js Normal file
View File

@@ -0,0 +1,33 @@
const jwt = require('jwt-simple');
// Load configuration values
const config = require('nconf')
.argv()
.env({ lowerCase: true }) // Load environment variables
.file('environment', { file: `config/${process.env.NODE_ENV}.json` })
.file('defaults', { file: 'config/default.json' });
// Generate an Access Token for the given User ID
const generateAccessToken = (account)=>{
const payload = account;
// When the token was issued
payload.issued = (new Date());
// Which service issued the Token
payload.issuer = config.get('authentication_token_issuer');
// Which service is the token intended for
payload.audience = config.get('authentication_token_audience');
// The signing key for signing the token
delete payload.password;
delete payload._id;
const secret = config.get('authentication_token_secret');
const token = jwt.encode(payload, secret);
return token;
};
module.exports = {
generateAccessToken : generateAccessToken
};