Oops!
diff --git a/client/homebrew/navbar/recent.navitem.jsx b/client/homebrew/navbar/recent.navitem.jsx
index 431bdd8df..a6cbbf406 100644
--- a/client/homebrew/navbar/recent.navitem.jsx
+++ b/client/homebrew/navbar/recent.navitem.jsx
@@ -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({
diff --git a/client/homebrew/pages/editPage/editPage.jsx b/client/homebrew/pages/editPage/editPage.jsx
index 18cfb2d41..039e89dda 100644
--- a/client/homebrew/pages/editPage/editPage.jsx
+++ b/client/homebrew/pages/editPage/editPage.jsx
@@ -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',
diff --git a/package-lock.json b/package-lock.json
index 16d308bf5..bca0b538d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index ff702fda7..7e405a2f4 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/server/app.js b/server/app.js
index f5864caae..c309e2901 100644
--- a/server/app.js
+++ b/server/app.js
@@ -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;
diff --git a/server/googleActions.js b/server/googleActions.js
index 93367248e..953ae27fb 100644
--- a/server/googleActions.js
+++ b/server/googleActions.js
@@ -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);
});
diff --git a/server/homebrew.api.js b/server/homebrew.api.js
index f15376af7..601fe8758 100644
--- a/server/homebrew.api.js
+++ b/server/homebrew.api.js
@@ -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);
diff --git a/server/homebrew.api.spec.js b/server/homebrew.api.spec.js
index dd4641c09..a1222cb57 100644
--- a/server/homebrew.api.spec.js
+++ b/server/homebrew.api.spec.js
@@ -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', ()=>{