diff --git a/client/homebrew/navbar/error-navitem.jsx b/client/homebrew/navbar/error-navitem.jsx index 0271224b7..a2650dbc8 100644 --- a/client/homebrew/navbar/error-navitem.jsx +++ b/client/homebrew/navbar/error-navitem.jsx @@ -112,6 +112,15 @@ const ErrorNavItem = ({ error = '', clearError })=>{ ; } + if(HBErrorCode === '13') { + return + Oops! + + Server has lost connection to the database. + + ; + } + if(errorCode === 'ECONNABORTED') { return Oops! diff --git a/client/homebrew/pages/errorPage/errors/errorIndex.js b/client/homebrew/pages/errorPage/errors/errorIndex.js index c0220b648..89abd570f 100644 --- a/client/homebrew/pages/errorPage/errors/errorIndex.js +++ b/client/homebrew/pages/errorPage/errors/errorIndex.js @@ -196,6 +196,12 @@ const errorIndex = (props)=>{ **Brew ID:** ${props.brew.brewId}`, + // Database Connection Lost + '13' : dedent` + ## Database connection has been lost. + + The server could not communicate with the database.`, + //account page when account is not defined '50' : dedent` ## You are not signed in diff --git a/server/app.js b/server/app.js index afba0997b..673d70c68 100644 --- a/server/app.js +++ b/server/app.js @@ -35,6 +35,7 @@ import contentNegotiation from './middleware/content-negotiation.js'; import bodyParser from 'body-parser'; import cookieParser from 'cookie-parser'; import forceSSL from './forcessl.mw.js'; +import dbCheck from './middleware/dbCheck.js'; const sanitizeBrew = (brew, accessType)=>{ @@ -274,7 +275,7 @@ app.get('/metadata/:id', asyncHandler(getBrew('share')), (req, res)=>{ app.get('/css/:id', asyncHandler(getBrew('share')), (req, res)=>{getCSS(req, res);}); //User Page -app.get('/user/:username', async (req, res, next)=>{ +app.get('/user/:username', dbCheck, async (req, res, next)=>{ const ownAccount = req.account && (req.account.username == req.params.username); req.ogMeta = { ...defaultMetaTags, @@ -346,7 +347,7 @@ app.get('/user/:username', async (req, res, next)=>{ }); //Change author name on brews -app.put('/api/user/rename', async (req, res)=>{ +app.put('/api/user/rename', dbCheck, async (req, res)=>{ const { username, newUsername } = req.body; const ownAccount = req.account && (req.account.username == newUsername); @@ -432,7 +433,7 @@ app.get('/new', asyncHandler(async(req, res, next)=>{ })); //Share Page -app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ +app.get('/share/:id', dbCheck, asyncHandler(getBrew('share')), asyncHandler(async (req, res, next)=>{ const { brew } = req; req.ogMeta = { ...defaultMetaTags, title : `${req.brew.title || 'Untitled Brew'} - ${req.brew.authors[0] || 'No author.'}`, @@ -459,7 +460,7 @@ app.get('/share/:id', asyncHandler(getBrew('share')), asyncHandler(async (req, r })); //Account Page -app.get('/account', asyncHandler(async (req, res, next)=>{ +app.get('/account', dbCheck, asyncHandler(async (req, res, next)=>{ const data = {}; data.title = 'Account Information Page'; diff --git a/server/db.js b/server/db.js index 196eba650..4930e4cd6 100644 --- a/server/db.js +++ b/server/db.js @@ -22,6 +22,14 @@ const handleConnectionError = (error)=>{ } }; +const addListeners = (conn)=>{ + conn.connection.on('disconnecting', ()=>{console.log('Mongo disconnecting...');}); + conn.connection.on('disconnected', ()=>{console.log('Mongo disconnected!');}); + conn.connection.on('connecting', ()=>{console.log('Mongo connecting...');}); + conn.connection.on('connected', ()=>{console.log('Mongo connected!');}); + return conn; +}; + const disconnect = async ()=>{ return await Mongoose.disconnect(); }; @@ -31,10 +39,12 @@ const connect = async (config)=>{ retryWrites : false, autoIndex : (config.get('local_environments').includes(config.get('node_env'))) }) - .catch((error)=>handleConnectionError(error)); + .then(addListeners(Mongoose)) + .catch((error)=>handleConnectionError(error)); }; export default { connect, disconnect }; + diff --git a/server/homebrew.api.js b/server/homebrew.api.js index 049dac1ce..c931cb657 100644 --- a/server/homebrew.api.js +++ b/server/homebrew.api.js @@ -13,6 +13,7 @@ import { md5 } from 'hash-wasm'; import { splitTextStyleAndMetadata, brewSnippetsToJSON, debugTextMismatch } from '../shared/helpers.js'; import checkClientVersion from './middleware/check-client-version.js'; +import dbCheck from './middleware/dbCheck.js'; const router = express.Router(); @@ -530,6 +531,8 @@ const api = { } }; +router.use(dbCheck); + router.post('/api', checkClientVersion, asyncHandler(api.newBrew)); router.put('/api/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew)); router.put('/api/update/:id', checkClientVersion, asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew)); diff --git a/server/middleware/dbCheck.js b/server/middleware/dbCheck.js new file mode 100644 index 000000000..f486eab52 --- /dev/null +++ b/server/middleware/dbCheck.js @@ -0,0 +1,15 @@ +import mongoose from 'mongoose'; +import config from '../config.js'; + +export default (req, res, next)=>{ + // Bypass DB checks during testing + if(config.get('node_env') == 'test') return next(); + + if(mongoose.connection.readyState == 1) return next(); + throw { + HBErrorCode : '13', + name : 'Database Connection Error', + message : 'Unable to connect to database', + status : mongoose.connection.readyState + }; +}; diff --git a/server/middleware/dbCheck.spec.js b/server/middleware/dbCheck.spec.js new file mode 100644 index 000000000..0c37d40ab --- /dev/null +++ b/server/middleware/dbCheck.spec.js @@ -0,0 +1,28 @@ +import mongoose from 'mongoose'; +import dbCheck from './dbCheck.js'; +import config from '../config.js'; + +describe('dbCheck middleware', ()=>{ + const next = jest.fn(); + + afterEach(()=>jest.clearAllMocks()); + + it('should skip check in test mode', ()=>{ + config.get = jest.fn(()=>'test'); + expect(()=>dbCheck({}, {}, next)).not.toThrow(); + expect(next).toHaveBeenCalled(); + }); + + it('should call next if readyState == 1', ()=>{ + config.get = jest.fn(()=>'production'); + mongoose.connection.readyState = 1; + dbCheck({}, {}, next); + expect(next).toHaveBeenCalled(); + }); + + it('should throw if readyState != 1', ()=>{ + config.get = jest.fn(()=>'production'); + mongoose.connection.readyState = 99; + expect(()=>dbCheck({}, {}, next)).toThrow(/Unable to connect/); + }); +}); \ No newline at end of file