diff --git a/services/notifications/app.js b/services/notifications/app.js index f83d7f0282..1aad0f6e18 100644 --- a/services/notifications/app.js +++ b/services/notifications/app.js @@ -15,6 +15,7 @@ const app = express() const methodOverride = require('method-override') const bodyParser = require('body-parser') const errorHandler = require('errorhandler') +const mongodb = require('./app/js/mongodb') const controller = require('./app/js/NotificationsController') metrics.memory.monitor(logger) @@ -62,9 +63,18 @@ const port = Settings.internal != null ? Settings.internal.notifications : undefined, (x1) => x1.port ) || 3042 -app.listen(port, host, () => - logger.info(`notifications starting up, listening on ${host}:${port}`) -) + +mongodb + .waitForDb() + .then(() => { + app.listen(port, host, () => + logger.info(`notifications starting up, listening on ${host}:${port}`) + ) + }) + .catch((err) => { + logger.fatal({ err }, 'Cannot connect to mongo. Exiting.') + process.exit(1) + }) function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null diff --git a/services/notifications/app/js/Notifications.js b/services/notifications/app/js/Notifications.js index d808ecb2c5..f26e8fa898 100644 --- a/services/notifications/app/js/Notifications.js +++ b/services/notifications/app/js/Notifications.js @@ -12,13 +12,8 @@ * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let Notifications -const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') -const mongojs = require('mongojs') -const db = mongojs(Settings.mongo != null ? Settings.mongo.url : undefined, [ - 'notifications' -]) -const { ObjectId } = require('mongojs') +const { db, ObjectId } = require('./mongodb') const metrics = require('metrics-sharelatex') module.exports = Notifications = { @@ -30,9 +25,7 @@ module.exports = Notifications = { user_id: ObjectId(user_id), templateKey: { $exists: true } } - return db.notifications.find(query, (err, notifications) => - callback(err, notifications) - ) + db.notifications.find(query).toArray(callback) }, _countExistingNotifications(user_id, notification, callback) { @@ -85,7 +78,7 @@ module.exports = Notifications = { return callback(err) } } - return db.notifications.update( + db.notifications.updateOne( { user_id: doc.user_id, key: notification.key }, { $set: doc }, { upsert: true }, @@ -100,7 +93,7 @@ module.exports = Notifications = { _id: ObjectId(notification_id) } const updateOperation = { $unset: { templateKey: true, messageOpts: true } } - return db.notifications.update(searchOps, updateOperation, callback) + db.notifications.updateOne(searchOps, updateOperation, callback) }, removeNotificationKey(user_id, notification_key, callback) { @@ -109,19 +102,19 @@ module.exports = Notifications = { key: notification_key } const updateOperation = { $unset: { templateKey: true } } - return db.notifications.update(searchOps, updateOperation, callback) + db.notifications.updateOne(searchOps, updateOperation, callback) }, removeNotificationByKeyOnly(notification_key, callback) { const searchOps = { key: notification_key } const updateOperation = { $unset: { templateKey: true } } - return db.notifications.update(searchOps, updateOperation, callback) + db.notifications.updateOne(searchOps, updateOperation, callback) }, // hard delete of doc, rather than removing the templateKey deleteNotificationByKeyOnly(notification_key, callback) { const searchOps = { key: notification_key } - return db.notifications.remove(searchOps, { justOne: true }, callback) + db.notifications.deleteOne(searchOps, callback) } } ;['getUserNotifications', 'addNotification'].map((method) => diff --git a/services/notifications/app/js/mongodb.js b/services/notifications/app/js/mongodb.js new file mode 100644 index 0000000000..ba88a9df34 --- /dev/null +++ b/services/notifications/app/js/mongodb.js @@ -0,0 +1,21 @@ +const Settings = require('settings-sharelatex') +const { MongoClient, ObjectId } = require('mongodb') + +const clientPromise = MongoClient.connect(Settings.mongo.url) + +async function waitForDb() { + await clientPromise +} + +const db = {} +waitForDb().then(async function () { + const internalDb = (await clientPromise).db() + + db.notifications = internalDb.collection('notifications') +}) + +module.exports = { + db, + ObjectId, + waitForDb +} diff --git a/services/notifications/package-lock.json b/services/notifications/package-lock.json index e69431c729..b5f661f3a4 100644 --- a/services/notifications/package-lock.json +++ b/services/notifications/package-lock.json @@ -1471,9 +1471,9 @@ "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" }, "bl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.0.tgz", - "integrity": "sha512-wbgvOpqopSr7uq6fJrLH8EsvYMJf9gzfo2jCsL2eTy75qXPukA4pCgHamOQkZtY5vmfVtjB+P3LNlMHW5CEZXA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", "requires": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" @@ -1542,9 +1542,9 @@ "dev": true }, "bson": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.4.tgz", - "integrity": "sha512-S/yKGU1syOMzO86+dGpg2qGoDL0zvzcb262G+gqEy6TgP6rt6z6qxSFX/8X6vLC91P7G7C3nLs0+bvDzmvBA3Q==" + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" }, "buffer-equal-constant-time": { "version": "1.0.1", @@ -4507,12 +4507,12 @@ "optional": true }, "mongodb": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.5.5.tgz", - "integrity": "sha512-GCjDxR3UOltDq00Zcpzql6dQo1sVry60OXJY3TDmFc2SWFY6c8Gn1Ardidc5jDirvJrx2GC3knGOImKphbSL3A==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.0.tgz", + "integrity": "sha512-/XWWub1mHZVoqEsUppE0GV7u9kanLvHxho6EvBxQbShXTKYF9trhZC2NzbulRGeG7xMJHD8IOWRcdKx5LPjAjQ==", "requires": { "bl": "^2.2.0", - "bson": "^1.1.1", + "bson": "^1.1.4", "denque": "^1.4.1", "require_optional": "^1.0.1", "safe-buffer": "^5.1.2", diff --git a/services/notifications/package.json b/services/notifications/package.json index 420c4fdcbc..7a5fe3300a 100644 --- a/services/notifications/package.json +++ b/services/notifications/package.json @@ -25,6 +25,7 @@ "logger-sharelatex": "^2.2.0", "method-override": "^3.0.0", "metrics-sharelatex": "^2.6.2", + "mongodb": "^3.6.0", "mongojs": "^3.1.0", "node-statsd": "0.1.1", "request": "^2.88.2", diff --git a/services/notifications/test/unit/js/NotificationsTests.js b/services/notifications/test/unit/js/NotificationsTests.js index f0feaee15f..ed33556423 100644 --- a/services/notifications/test/unit/js/NotificationsTests.js +++ b/services/notifications/test/unit/js/NotificationsTests.js @@ -19,7 +19,7 @@ const should = chai.should() const modulePath = '../../../app/js/Notifications.js' const SandboxedModule = require('sandboxed-module') const assert = require('assert') -const { ObjectId } = require('mongojs') +const { ObjectId } = require('mongodb') const user_id = '51dc93e6fb625a261300003b' const notification_id = 'fb625a26f09d' @@ -27,25 +27,19 @@ const notification_key = 'notification-key' describe('Notifications Tests', function () { beforeEach(function () { - const self = this - this.findStub = sinon.stub() - this.insertStub = sinon.stub() + this.findToArrayStub = sinon.stub() + this.findStub = sinon.stub().returns({ toArray: this.findToArrayStub }) this.countStub = sinon.stub() - this.updateStub = sinon.stub() - this.removeStub = sinon.stub() - this.mongojs = () => { - return { - notifications: { - update: self.mongojsUpdate, - find: this.findStub, - insert: this.insertStub, - count: this.countStub, - update: this.updateStub, - remove: this.removeStub - } + this.updateOneStub = sinon.stub() + this.deleteOneStub = sinon.stub() + this.db = { + notifications: { + find: this.findStub, + count: this.countStub, + updateOne: this.updateOneStub, + deleteOne: this.deleteOneStub } } - this.mongojs.ObjectId = ObjectId this.notifications = SandboxedModule.require(modulePath, { requires: { @@ -54,7 +48,7 @@ describe('Notifications Tests', function () { error() {} }, 'settings-sharelatex': {}, - mongojs: this.mongojs, + './mongodb': { db: this.db, ObjectId }, 'metrics-sharelatex': { timeAsyncMethod: sinon.stub() } }, globals: { @@ -73,7 +67,7 @@ describe('Notifications Tests', function () { describe('getUserNotifications', function () { return it('should find all notifications and return i', function (done) { - this.findStub.callsArgWith(1, null, this.stubbedNotificationArray) + this.findToArrayStub.callsArgWith(0, null, this.stubbedNotificationArray) return this.notifications.getUserNotifications( user_id, (err, notifications) => { @@ -106,7 +100,7 @@ describe('Notifications Tests', function () { user_id: this.stubbedNotification.user_id, key: 'notification-key' } - this.updateStub.yields() + this.updateOneStub.yields() return this.countStub.yields(null, 0) }) @@ -117,7 +111,7 @@ describe('Notifications Tests', function () { (err) => { expect(err).not.exists sinon.assert.calledWith( - this.updateStub, + this.updateOneStub, this.expectedQuery, { $set: this.expectedDocument }, { upsert: true } @@ -138,7 +132,7 @@ describe('Notifications Tests', function () { this.stubbedNotification, (err) => { expect(err).not.exists - sinon.assert.notCalled(this.updateStub) + sinon.assert.notCalled(this.updateOneStub) return done() } ) @@ -152,7 +146,7 @@ describe('Notifications Tests', function () { (err) => { expect(err).not.exists sinon.assert.calledWith( - this.updateStub, + this.updateOneStub, this.expectedQuery, { $set: this.expectedDocument }, { upsert: true } @@ -192,7 +186,7 @@ describe('Notifications Tests', function () { (err) => { expect(err).not.exists sinon.assert.calledWith( - this.updateStub, + this.updateOneStub, this.expectedQuery, { $set: this.expectedDocument }, { upsert: true } @@ -227,7 +221,7 @@ describe('Notifications Tests', function () { this.stubbedNotification, (err) => { ;(err instanceof Error).should.equal(true) - sinon.assert.notCalled(this.updateStub) + sinon.assert.notCalled(this.updateOneStub) return done() } ) @@ -237,7 +231,7 @@ describe('Notifications Tests', function () { describe('removeNotificationId', function () { return it('should mark the notification id as read', function (done) { - this.updateStub.callsArgWith(2, null) + this.updateOneStub.callsArgWith(2, null) return this.notifications.removeNotificationId( user_id, @@ -250,8 +244,8 @@ describe('Notifications Tests', function () { const updateOperation = { $unset: { templateKey: true, messageOpts: true } } - assert.deepEqual(this.updateStub.args[0][0], searchOps) - assert.deepEqual(this.updateStub.args[0][1], updateOperation) + assert.deepEqual(this.updateOneStub.args[0][0], searchOps) + assert.deepEqual(this.updateOneStub.args[0][1], updateOperation) return done() } ) @@ -260,7 +254,7 @@ describe('Notifications Tests', function () { describe('removeNotificationKey', function () { return it('should mark the notification key as read', function (done) { - this.updateStub.callsArgWith(2, null) + this.updateOneStub.callsArgWith(2, null) return this.notifications.removeNotificationKey( user_id, @@ -273,8 +267,8 @@ describe('Notifications Tests', function () { const updateOperation = { $unset: { templateKey: true } } - assert.deepEqual(this.updateStub.args[0][0], searchOps) - assert.deepEqual(this.updateStub.args[0][1], updateOperation) + assert.deepEqual(this.updateOneStub.args[0][0], searchOps) + assert.deepEqual(this.updateOneStub.args[0][1], updateOperation) return done() } ) @@ -283,15 +277,15 @@ describe('Notifications Tests', function () { describe('removeNotificationByKeyOnly', function () { return it('should mark the notification key as read', function (done) { - this.updateStub.callsArgWith(2, null) + this.updateOneStub.callsArgWith(2, null) return this.notifications.removeNotificationByKeyOnly( notification_key, (err) => { const searchOps = { key: notification_key } const updateOperation = { $unset: { templateKey: true } } - assert.deepEqual(this.updateStub.args[0][0], searchOps) - assert.deepEqual(this.updateStub.args[0][1], updateOperation) + assert.deepEqual(this.updateOneStub.args[0][0], searchOps) + assert.deepEqual(this.updateOneStub.args[0][1], updateOperation) return done() } ) @@ -300,15 +294,13 @@ describe('Notifications Tests', function () { return describe('deleteNotificationByKeyOnly', function () { return it('should completely remove the notification', function (done) { - this.removeStub.callsArgWith(2, null) + this.deleteOneStub.callsArgWith(1, null) return this.notifications.deleteNotificationByKeyOnly( notification_key, (err) => { const searchOps = { key: notification_key } - const opts = { justOne: true } - assert.deepEqual(this.removeStub.args[0][0], searchOps) - assert.deepEqual(this.removeStub.args[0][1], opts) + assert.deepEqual(this.deleteOneStub.args[0][0], searchOps) return done() } )