mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-30 15:22:41 +00:00
Merge branch 'v3.15.2' of https://github.com/naturalcrit/homebrewery into scroll-to-element
This commit is contained in:
@@ -4,7 +4,7 @@ const _ = require('lodash');
|
||||
|
||||
import Dialog from '../../../components/dialog.jsx';
|
||||
|
||||
const DISMISS_KEY = 'dismiss_notification04-09-24';
|
||||
const DISMISS_KEY = 'dismiss_notification01-10-24';
|
||||
const DISMISS_BUTTON = <i className='fas fa-times dismiss' />;
|
||||
|
||||
const NotificationPopup = ()=>{
|
||||
@@ -15,6 +15,34 @@ const NotificationPopup = ()=>{
|
||||
<small>This website is always improving and we are still adding new features and squashing bugs. Keep the following in mind:</small>
|
||||
</div>
|
||||
<ul>
|
||||
<li key='ThrottlingError' style={{
|
||||
backgroundColor: '#910000',
|
||||
margin: '-10px -10px -10px -20px',
|
||||
padding: '10px 10px 10px 20px',
|
||||
fontSize: '1.0em'
|
||||
}}>
|
||||
<em>Known issue with saving/creating Google Drive files</em><br />
|
||||
Dear users. The <a href="https://github.com/naturalcrit/homebrewery/issues/3770">
|
||||
issue with saving to Google Drive</a> has resurfaced as of Oct 1, 2024 22:00 UTC.
|
||||
<br></br><br></br>
|
||||
Earlier we submitted a bug report to Google and have all but confirmed the issue
|
||||
lies on Google's end and the disruption has been affecting multiple other
|
||||
organizations besides us. Unfortunately, it means reliable interaction with
|
||||
Google remains out of our control until they can resolve their issue.
|
||||
<br></br><br></br>
|
||||
Brews saved to Google Drive are <em>not lost</em> and can still be viewed, just not updated.
|
||||
You can also access them via your Google Drive interface in the <code>/Hombrewery</code> folder.
|
||||
<br></br><br></br>
|
||||
If you need to urgently edit documents, you can detatch them from your Google Drive
|
||||
by transferring them to our Homebrewery storage. To do this, click the colored Google Drive
|
||||
icon next to the save button when on an edit page; you can transfer them back later,
|
||||
but this should allow you to edit while this issue is ongoing.
|
||||
<br></br><br></br>
|
||||
If you are experiencing errors creating new documents, you can similarly change your
|
||||
account settings to create new brews by default in the Homebrewery storage. Click
|
||||
your username and then "account", then change the "default save location".
|
||||
</li>
|
||||
|
||||
<li key='Vault'>
|
||||
<em>Search brews with our new page!</em><br />
|
||||
We have been working very hard in making this possible, now you can share your work and look at it in the new <a href='/vault'>Vault</a> page!
|
||||
|
||||
@@ -116,6 +116,17 @@ const ErrorNavItem = createClass({
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
if(HBErrorCode === '55') {
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer' onClick={clearError}>
|
||||
Looks like there are too many requests
|
||||
from this IP address in a short time.
|
||||
Please try again after a few minutes.
|
||||
</div>
|
||||
</Nav.item>;
|
||||
}
|
||||
|
||||
return <Nav.item className='save error' icon='fas fa-exclamation-triangle'>
|
||||
Oops!
|
||||
<div className='errorContainer'>
|
||||
|
||||
@@ -36,7 +36,7 @@ const RecentItems = createClass({
|
||||
//== Add current brew to appropriate recent items list (depending on storageKey) ==//
|
||||
if(this.props.storageKey == 'edit'){
|
||||
let editId = this.props.brew.editId;
|
||||
if(this.props.brew.googleId){
|
||||
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||
}
|
||||
edited = _.filter(edited, (brew)=>{
|
||||
@@ -51,7 +51,7 @@ const RecentItems = createClass({
|
||||
}
|
||||
if(this.props.storageKey == 'view'){
|
||||
let shareId = this.props.brew.shareId;
|
||||
if(this.props.brew.googleId){
|
||||
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||
shareId = `${this.props.brew.googleId}${this.props.brew.shareId}`;
|
||||
}
|
||||
viewed = _.filter(viewed, (brew)=>{
|
||||
@@ -83,7 +83,7 @@ const RecentItems = createClass({
|
||||
let edited = JSON.parse(localStorage.getItem(EDIT_KEY) || '[]');
|
||||
if(this.props.storageKey == 'edit') {
|
||||
let prevEditId = prevProps.brew.editId;
|
||||
if(prevProps.brew.googleId){
|
||||
if(prevProps.brew.googleId && !this.props.brew.stubbed){
|
||||
prevEditId = `${prevProps.brew.googleId}${prevProps.brew.editId}`;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ const RecentItems = createClass({
|
||||
return brew.id !== prevEditId;
|
||||
});
|
||||
let editId = this.props.brew.editId;
|
||||
if(this.props.brew.googleId){
|
||||
if(this.props.brew.googleId && !this.props.brew.stubbed){
|
||||
editId = `${this.props.brew.googleId}${this.props.brew.editId}`;
|
||||
}
|
||||
edited.unshift({
|
||||
|
||||
@@ -32,7 +32,7 @@ import { updateHistory, versionHistoryGarbageCollection } from '../../utils/vers
|
||||
|
||||
const googleDriveIcon = require('../../googleDrive.svg');
|
||||
|
||||
const SAVE_TIMEOUT = 3000;
|
||||
const SAVE_TIMEOUT = 16000;
|
||||
|
||||
const EditPage = createClass({
|
||||
displayName : 'EditPage',
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -26,6 +26,8 @@
|
||||
"express": "^4.21.0",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.8",
|
||||
"express-rate-limit": "^7.4.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
"fs-extra": "11.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
"expr-eval": "^2.0.2",
|
||||
"express": "^4.21.0",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.8",
|
||||
"express-rate-limit": "^7.4.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
"fs-extra": "11.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jwt-simple": "^0.5.6",
|
||||
|
||||
@@ -30,6 +30,10 @@ const sanitizeBrew = (brew, accessType)=>{
|
||||
return brew;
|
||||
};
|
||||
|
||||
app.set('trust proxy', 1 /* number of proxies between user and server */)
|
||||
app.get('/ip', (request, response) => response.send(request.ip))
|
||||
|
||||
|
||||
app.use('/', serveCompressedStaticAssets(`build`));
|
||||
app.use(require('./middleware/content-negotiation.js'));
|
||||
app.use(require('body-parser').json({ limit: '25mb' }));
|
||||
@@ -202,23 +206,6 @@ app.get('/download/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
||||
res.status(200).send(brew.text);
|
||||
});
|
||||
|
||||
//Serve brew metadata
|
||||
app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{
|
||||
const { brew } = req;
|
||||
sanitizeBrew(brew, 'share');
|
||||
|
||||
const fields = ['title', 'pageCount', 'description', 'authors', 'lang',
|
||||
'published', 'views', 'shareId', 'createdAt', 'updatedAt',
|
||||
'lastViewed', 'thumbnail', 'tags'
|
||||
];
|
||||
|
||||
const metadata = fields.reduce((acc, field)=>{
|
||||
if(brew[field] !== undefined) acc[field] = brew[field];
|
||||
return acc;
|
||||
}, {});
|
||||
res.status(200).json(metadata);
|
||||
});
|
||||
|
||||
//Serve brew styling
|
||||
app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);});
|
||||
|
||||
@@ -255,6 +242,8 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
brews.forEach(brew => brew.stubbed = true); //All brews from MongoDB are "stubbed"
|
||||
|
||||
if(ownAccount && req?.account?.googleId){
|
||||
const auth = await GoogleActions.authCheck(req.account, res);
|
||||
let googleBrews = await GoogleActions.listGoogleBrews(auth)
|
||||
@@ -262,12 +251,12 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
// If stub matches file from Google, use Google metadata over stub metadata
|
||||
if(googleBrews && googleBrews.length > 0) {
|
||||
for (const brew of brews.filter((brew)=>brew.googleId)) {
|
||||
const match = googleBrews.findIndex((b)=>b.editId === brew.editId);
|
||||
if(match !== -1) {
|
||||
brew.googleId = googleBrews[match].googleId;
|
||||
brew.stubbed = true;
|
||||
brew.pageCount = googleBrews[match].pageCount;
|
||||
brew.renderer = googleBrews[match].renderer;
|
||||
brew.version = googleBrews[match].version;
|
||||
@@ -276,6 +265,7 @@ app.get('/user/:username', async (req, res, next)=>{
|
||||
}
|
||||
}
|
||||
|
||||
//Remaining unstubbed google brews display current user as author
|
||||
googleBrews = googleBrews.map((brew)=>({ ...brew, authors: [req.account.username] }));
|
||||
brews = _.concat(brews, googleBrews);
|
||||
}
|
||||
@@ -378,7 +368,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r
|
||||
app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
const data = {};
|
||||
data.title = 'Account Information Page';
|
||||
|
||||
|
||||
if(!req.account) {
|
||||
res.set('WWW-Authenticate', 'Bearer realm="Authorization Required"');
|
||||
const error = new Error('No valid account');
|
||||
@@ -392,22 +382,12 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
let googleCount = [];
|
||||
if(req.account) {
|
||||
if(req.account.googleId) {
|
||||
try {
|
||||
auth = await GoogleActions.authCheck(req.account, res, false);
|
||||
} catch (e) {
|
||||
auth = undefined;
|
||||
console.log('Google auth check failed!');
|
||||
console.log(e);
|
||||
}
|
||||
if(auth.credentials.access_token) {
|
||||
try {
|
||||
googleCount = await GoogleActions.listGoogleBrews(auth);
|
||||
} catch (e) {
|
||||
googleCount = undefined;
|
||||
console.log('List Google files failed!');
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
auth = await GoogleActions.authCheck(req.account, res, false)
|
||||
|
||||
googleCount = await GoogleActions.listGoogleBrews(auth)
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
const query = { authors: req.account.username, googleId: { $exists: false } };
|
||||
@@ -421,7 +401,7 @@ app.get('/account', asyncHandler(async (req, res, next)=>{
|
||||
username : req.account.username,
|
||||
issued : req.account.issued,
|
||||
googleId : Boolean(req.account.googleId),
|
||||
authCheck : Boolean(req.account.googleId && auth.credentials.access_token),
|
||||
authCheck : Boolean(req.account.googleId && auth?.credentials.access_token),
|
||||
mongoCount : mongoCount,
|
||||
googleCount : googleCount?.length
|
||||
};
|
||||
@@ -453,17 +433,13 @@ if(isLocalEnvironment){
|
||||
|
||||
//Vault Page
|
||||
app.get('/vault', asyncHandler(async(req, res, next)=>{
|
||||
req.ogMeta = { ...defaultMetaTags,
|
||||
title : 'The Vault',
|
||||
description : 'Search for Brews'
|
||||
};
|
||||
return next();
|
||||
}));
|
||||
|
||||
//Send rendered page
|
||||
app.use(asyncHandler(async (req, res, next)=>{
|
||||
if(!req.route) return res.redirect('/'); // Catch-all for invalid routes
|
||||
|
||||
if (!req.route) return res.redirect('/'); // Catch-all for invalid routes
|
||||
|
||||
const page = await renderPage(req, res);
|
||||
if(!page) return;
|
||||
res.send(page);
|
||||
@@ -475,8 +451,7 @@ const renderPage = async (req, res)=>{
|
||||
const configuration = {
|
||||
local : isLocalEnvironment,
|
||||
publicUrl : config.get('publicUrl') ?? '',
|
||||
environment : nodeEnv,
|
||||
history : config.get('historyConfig') ?? {}
|
||||
environment : nodeEnv
|
||||
};
|
||||
const props = {
|
||||
version : require('./../package.json').version,
|
||||
@@ -520,7 +495,7 @@ app.use(async (err, req, res, next)=>{
|
||||
err.originalUrl = req.originalUrl;
|
||||
console.error(err);
|
||||
|
||||
if(err.originalUrl?.startsWith('/api/')) {
|
||||
if(err.originalUrl?.startsWith('/api')) {
|
||||
// console.log('API error');
|
||||
res.status(err.status || err.response?.status || 500).send(err);
|
||||
return;
|
||||
|
||||
@@ -25,6 +25,15 @@ if(!config.get('service_account')){
|
||||
|
||||
const defaultAuth = serviceAuth || config.get('google_api_key');
|
||||
|
||||
const retryConfig = {
|
||||
retry: 3, // Number of retry attempts
|
||||
retryDelay: 100, // Initial delay in milliseconds
|
||||
retryDelayMultiplier: 2, // Multiplier for exponential backoff
|
||||
maxRetryDelay: 32000, // Maximum delay in milliseconds
|
||||
httpMethodsToRetry: ['PATCH'], // Only retry PATCH requests
|
||||
statusCodesToRetry: [[429, 429]], // Only retry on 429 status code
|
||||
};
|
||||
|
||||
const GoogleActions = {
|
||||
|
||||
authCheck : (account, res, updateTokens=true)=>{
|
||||
@@ -112,9 +121,7 @@ const GoogleActions = {
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log(`Error Listing Google Brews`);
|
||||
console.error(err);
|
||||
throw (err);
|
||||
//TODO: Should break out here, but continues on for some reason.
|
||||
});
|
||||
fileList.push(...obj.data.files);
|
||||
NextPageToken = obj.data.nextPageToken;
|
||||
@@ -147,8 +154,9 @@ const GoogleActions = {
|
||||
return brews;
|
||||
},
|
||||
|
||||
updateGoogleBrew : async (brew)=>{
|
||||
const drive = googleDrive.drive({ version: 'v3', auth: defaultAuth });
|
||||
updateGoogleBrew : async (brew, auth = defaultAuth, userIp)=>{
|
||||
const drive = googleDrive.drive({ version: 'v3', auth: auth });
|
||||
console.log(auth == defaultAuth ? 'UPDATE w SERVICEACC' : 'UPDATE w USERACC')
|
||||
|
||||
await drive.files.update({
|
||||
fileId : brew.googleId,
|
||||
@@ -168,11 +176,14 @@ const GoogleActions = {
|
||||
media : {
|
||||
mimeType : 'text/plain',
|
||||
body : brew.text
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
'X-Forwarded-For': userIp, // Set the X-Forwarded-For header
|
||||
},
|
||||
retryConfig
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.log('Error saving to google');
|
||||
console.error(err);
|
||||
throw (err);
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const yaml = require('js-yaml');
|
||||
const asyncHandler = require('express-async-handler');
|
||||
const { nanoid } = require('nanoid');
|
||||
const { splitTextStyleAndMetadata } = require('../shared/helpers.js');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const { DEFAULT_BREW, DEFAULT_BREW_LOAD } = require('./brewDefaults.js');
|
||||
|
||||
@@ -242,11 +243,8 @@ const api = {
|
||||
|
||||
let googleId, saved;
|
||||
if(saveToGoogle) {
|
||||
googleId = await api.newGoogleBrew(req.account, newHomebrew, res)
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
res.status(err?.status || err?.response?.status || 500).send(err?.message || err);
|
||||
});
|
||||
googleId = await api.newGoogleBrew(req.account, newHomebrew, res);
|
||||
|
||||
if(!googleId) return;
|
||||
api.excludeStubProps(newHomebrew);
|
||||
newHomebrew.googleId = googleId;
|
||||
@@ -351,19 +349,13 @@ const api = {
|
||||
brew.googleId = undefined;
|
||||
} else if(!brew.googleId && saveToGoogle) {
|
||||
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
|
||||
brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res)
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
res.status(err.status || err.response.status).send(err.message || err);
|
||||
});
|
||||
brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res);
|
||||
|
||||
if(!brew.googleId) return;
|
||||
} else if(brew.googleId) {
|
||||
// If the google id exists and no other actions are being performed, update the google brew
|
||||
const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew))
|
||||
.catch((err)=>{
|
||||
console.error(err);
|
||||
res.status(err?.response?.status || 500).send(err);
|
||||
});
|
||||
const updated = await api.updateGoogleBrew(req.account, api.excludeGoogleProps(brew), res, req);
|
||||
|
||||
if(!updated) return;
|
||||
}
|
||||
|
||||
@@ -406,6 +398,15 @@ const api = {
|
||||
|
||||
res.status(200).send(saved);
|
||||
},
|
||||
|
||||
updateGoogleBrew : async (account, brew, res, req)=>{
|
||||
//let oAuth2Client;
|
||||
//if(account.googleId)
|
||||
// oAuth2Client = GoogleActions.authCheck(account, res);
|
||||
|
||||
return await GoogleActions.updateGoogleBrew(brew, undefined, req.ip);
|
||||
},
|
||||
|
||||
deleteGoogleBrew : async (account, id, editId, res)=>{
|
||||
const auth = await GoogleActions.authCheck(account, res);
|
||||
await GoogleActions.deleteGoogleBrew(auth, id, editId);
|
||||
|
||||
@@ -560,16 +560,6 @@ brew`);
|
||||
views : 0
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle google error', async()=>{
|
||||
google.newGoogleBrew = jest.fn(()=>{
|
||||
throw 'err';
|
||||
});
|
||||
await api.newBrew({ body: { text: 'asdf', title: '' }, query: { saveToGoogle: true }, account: { username: 'test user' } }, res);
|
||||
|
||||
expect(res.status).toHaveBeenCalledWith(500);
|
||||
expect(res.send).toHaveBeenCalledWith('err');
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteGoogleBrew', ()=>{
|
||||
|
||||
Reference in New Issue
Block a user