0
0
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:
Víctor Losada Hernández
2024-10-07 11:47:13 +02:00
10 changed files with 102 additions and 83 deletions

View File

@@ -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!

View File

@@ -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'>

View File

@@ -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({

View File

@@ -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
View File

@@ -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",

View File

@@ -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",

View File

@@ -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;

View File

@@ -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);
});

View File

@@ -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);

View File

@@ -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', ()=>{