diff --git a/services/notifications/app/js/HealthCheckController.js b/services/notifications/app/js/HealthCheckController.js index 57b24892b1..df14b9a496 100644 --- a/services/notifications/app/js/HealthCheckController.js +++ b/services/notifications/app/js/HealthCheckController.js @@ -11,79 +11,112 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const { ObjectId } = require("mongojs"); -const request = require("request"); -const async = require("async"); -const _ = require("underscore"); -const settings = require("settings-sharelatex"); -const { port } = settings.internal.notifications; -const logger = require("logger-sharelatex"); +const { ObjectId } = require('mongojs') +const request = require('request') +const async = require('async') +const _ = require('underscore') +const settings = require('settings-sharelatex') +const { port } = settings.internal.notifications +const logger = require('logger-sharelatex') -const mongojs = require('mongojs'); -const Settings = require('settings-sharelatex'); -const db = mongojs(Settings.mongo != null ? Settings.mongo.url : undefined, ['notifications']); +const mongojs = require('mongojs') +const Settings = require('settings-sharelatex') +const db = mongojs(Settings.mongo != null ? Settings.mongo.url : undefined, [ + 'notifications' +]) -module.exports = { - check(callback){ - const user_id = ObjectId(); - const cleanupNotifications = callback=> db.notifications.remove({user_id}, callback); +module.exports = { + check(callback) { + const user_id = ObjectId() + const cleanupNotifications = callback => + db.notifications.remove({ user_id }, callback) - let notification_key = `smoke-test-notification-${ObjectId()}`; - const getOpts = endPath=> ({url:`http://localhost:${port}/user/${user_id}${endPath}`, timeout:5000}); - logger.log({user_id, opts:getOpts(), key:notification_key, user_id}, "Health Check: running"); - const jobs = [ - function(cb){ - const opts = getOpts("/"); - opts.json = {key: notification_key, messageOpts:'', templateKey:'f4g5', user_id}; - return request.post(opts, cb); - }, - function(cb){ - const opts = getOpts("/"); - opts.json = true; - return request.get(opts, function(err, res, body){ - if (err != null) { - logger.err({err}, "Health Check: error getting notification"); - return callback(err); - } else if (res.statusCode !== 200) { - const e = `status code not 200 ${res.statusCode}`; - logger.err({err}, e); - return cb(e); - } - const hasNotification = _.some(body, notification=> (notification.key === notification_key) && (notification.user_id === user_id.toString())); - if (hasNotification) { - return cb(null, body); - } else { - logger.err({body, notification_key}, "Health Check: notification not in response"); - return cb("notification not found in response"); - } - }); - } - ]; - return async.series(jobs, function(err, body){ - if (err != null) { - logger.err({err}, "Health Check: error running health check"); - return cleanupNotifications(() => callback(err)); - } else { - const notification_id = body[1][0]._id; - notification_key = body[1][0].key; - let opts = getOpts(`/notification/${notification_id}`); - logger.log({notification_id, notification_key}, "Health Check: doing cleanup"); - return request.del(opts, function(err, res, body){ - if (err != null) { - logger.err(err, opts, "Health Check: error cleaning up notification"); - return callback(err); - } - opts = getOpts(""); - opts.json = {key: notification_key}; - return request.del(opts, function(err, res, body){ - if (err != null) { - logger.err(err, opts, "Health Check: error cleaning up notification"); - return callback(err); - } - return cleanupNotifications(callback); - }); - }); - } - }); - } -}; + let notification_key = `smoke-test-notification-${ObjectId()}` + const getOpts = endPath => ({ + url: `http://localhost:${port}/user/${user_id}${endPath}`, + timeout: 5000 + }) + logger.log( + { user_id, opts: getOpts(), key: notification_key, user_id }, + 'Health Check: running' + ) + const jobs = [ + function(cb) { + const opts = getOpts('/') + opts.json = { + key: notification_key, + messageOpts: '', + templateKey: 'f4g5', + user_id + } + return request.post(opts, cb) + }, + function(cb) { + const opts = getOpts('/') + opts.json = true + return request.get(opts, function(err, res, body) { + if (err != null) { + logger.err({ err }, 'Health Check: error getting notification') + return callback(err) + } else if (res.statusCode !== 200) { + const e = `status code not 200 ${res.statusCode}` + logger.err({ err }, e) + return cb(e) + } + const hasNotification = _.some( + body, + notification => + notification.key === notification_key && + notification.user_id === user_id.toString() + ) + if (hasNotification) { + return cb(null, body) + } else { + logger.err( + { body, notification_key }, + 'Health Check: notification not in response' + ) + return cb('notification not found in response') + } + }) + } + ] + return async.series(jobs, function(err, body) { + if (err != null) { + logger.err({ err }, 'Health Check: error running health check') + return cleanupNotifications(() => callback(err)) + } else { + const notification_id = body[1][0]._id + notification_key = body[1][0].key + let opts = getOpts(`/notification/${notification_id}`) + logger.log( + { notification_id, notification_key }, + 'Health Check: doing cleanup' + ) + return request.del(opts, function(err, res, body) { + if (err != null) { + logger.err( + err, + opts, + 'Health Check: error cleaning up notification' + ) + return callback(err) + } + opts = getOpts('') + opts.json = { key: notification_key } + return request.del(opts, function(err, res, body) { + if (err != null) { + logger.err( + err, + opts, + 'Health Check: error cleaning up notification' + ) + return callback(err) + } + return cleanupNotifications(callback) + }) + }) + } + }) + } +} diff --git a/services/notifications/app/js/Notifications.js b/services/notifications/app/js/Notifications.js index f1edf123c9..ec4007e50f 100644 --- a/services/notifications/app/js/Notifications.js +++ b/services/notifications/app/js/Notifications.js @@ -11,104 +11,120 @@ * DS207: Consider shorter variations of null checks * 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 metrics = require('metrics-sharelatex'); +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 metrics = require('metrics-sharelatex') -module.exports = (Notifications = { +module.exports = Notifications = { + getUserNotifications(user_id, callback) { + if (callback == null) { + callback = function(err, notifications) {} + } + const query = { + user_id: ObjectId(user_id), + templateKey: { $exists: true } + } + return db.notifications.find(query, (err, notifications) => + callback(err, notifications) + ) + }, - getUserNotifications(user_id, callback){ - if (callback == null) { callback = function(err, notifications){}; } - const query = { - user_id: ObjectId(user_id), - templateKey: {"$exists":true} - }; - return db.notifications.find(query, (err, notifications)=> callback(err, notifications)); - }, + _countExistingNotifications(user_id, notification, callback) { + if (callback == null) { + callback = function(err, count) {} + } + const query = { + user_id: ObjectId(user_id), + key: notification.key + } + return db.notifications.count(query, function(err, count) { + if (err != null) { + return callback(err) + } + return callback(null, count) + }) + }, + addNotification(user_id, notification, callback) { + return this._countExistingNotifications(user_id, notification, function( + err, + count + ) { + if (err != null) { + return callback(err) + } + if (count !== 0 && !notification.forceCreate) { + return callback() + } + const doc = { + user_id: ObjectId(user_id), + key: notification.key, + messageOpts: notification.messageOpts, + templateKey: notification.templateKey + } + // TTL index on the optional `expires` field, which should arrive as an iso date-string, corresponding to + // a datetime in the future when the document should be automatically removed. + // in Mongo, TTL indexes only work on date fields, and ignore the document when that field is missing + // see `README.md` for instruction on creating TTL index + if (notification.expires != null) { + try { + doc.expires = new Date(notification.expires) + const _testValue = doc.expires.toISOString() + } catch (error) { + err = error + logger.error( + { user_id, expires: notification.expires }, + 'error converting `expires` field to Date' + ) + return callback(err) + } + } + return db.notifications.update( + { user_id: doc.user_id, key: notification.key }, + doc, + { upsert: true }, + callback + ) + }) + }, - _countExistingNotifications(user_id, notification, callback){ - if (callback == null) { callback = function(err, count){}; } - const query = { - user_id: ObjectId(user_id), - key: notification.key - }; - return db.notifications.count(query, function(err, count){ - if (err != null) { return callback(err); } - return callback(null, count); - }); - }, + removeNotificationId(user_id, notification_id, callback) { + const searchOps = { + user_id: ObjectId(user_id), + _id: ObjectId(notification_id) + } + const updateOperation = { $unset: { templateKey: true, messageOpts: true } } + return db.notifications.update(searchOps, updateOperation, callback) + }, - addNotification(user_id, notification, callback){ - return this._countExistingNotifications(user_id, notification, function(err, count){ - if (err != null) { return callback(err); } - if ((count !== 0) && !notification.forceCreate) { return callback(); } - const doc = { - user_id: ObjectId(user_id), - key: notification.key, - messageOpts: notification.messageOpts, - templateKey: notification.templateKey - }; - // TTL index on the optional `expires` field, which should arrive as an iso date-string, corresponding to - // a datetime in the future when the document should be automatically removed. - // in Mongo, TTL indexes only work on date fields, and ignore the document when that field is missing - // see `README.md` for instruction on creating TTL index - if (notification.expires != null) { - try { - doc.expires = new Date(notification.expires); - const _testValue = doc.expires.toISOString(); - } catch (error) { - err = error; - logger.error({user_id, expires: notification.expires}, "error converting `expires` field to Date"); - return callback(err); - } - } - return db.notifications.update({user_id: doc.user_id, key: notification.key}, doc, {upsert: true}, callback); - }); - }, + removeNotificationKey(user_id, notification_key, callback) { + const searchOps = { + user_id: ObjectId(user_id), + key: notification_key + } + const updateOperation = { $unset: { templateKey: true } } + return db.notifications.update(searchOps, updateOperation, callback) + }, - removeNotificationId(user_id, notification_id, callback){ - const searchOps = { - user_id:ObjectId(user_id), - _id:ObjectId(notification_id) - }; - const updateOperation = - {"$unset": {templateKey:true, messageOpts: true}}; - return db.notifications.update(searchOps, updateOperation, callback); - }, + removeNotificationByKeyOnly(notification_key, callback) { + const searchOps = { key: notification_key } + const updateOperation = { $unset: { templateKey: true } } + return db.notifications.update(searchOps, updateOperation, callback) + }, - removeNotificationKey(user_id, notification_key, callback){ - const searchOps = { - user_id:ObjectId(user_id), - key: notification_key - }; - const updateOperation = - {"$unset": {templateKey:true}}; - return db.notifications.update(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) + } +} - removeNotificationByKeyOnly(notification_key, callback){ - const searchOps = - {key: notification_key}; - const updateOperation = - {"$unset": {templateKey:true}}; - return db.notifications.update(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); - } -}); - - -[ - 'getUserNotifications', - 'addNotification' -].map(method => metrics.timeAsyncMethod(Notifications, method, 'mongo.Notifications', logger)); +;['getUserNotifications', 'addNotification'].map(method => + metrics.timeAsyncMethod(Notifications, method, 'mongo.Notifications', logger) +) diff --git a/services/notifications/app/js/NotificationsController.js b/services/notifications/app/js/NotificationsController.js index 82201ba645..27587da25d 100644 --- a/services/notifications/app/js/NotificationsController.js +++ b/services/notifications/app/js/NotificationsController.js @@ -10,46 +10,77 @@ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ -const Notifications = require("./Notifications"); -const logger = require("logger-sharelatex"); -const metrics = require('metrics-sharelatex'); +const Notifications = require('./Notifications') +const logger = require('logger-sharelatex') +const metrics = require('metrics-sharelatex') module.exports = { + getUserNotifications(req, res) { + logger.log( + { user_id: req.params.user_id }, + 'getting user unread notifications' + ) + metrics.inc('getUserNotifications') + return Notifications.getUserNotifications( + req.params.user_id, + (err, notifications) => res.json(notifications) + ) + }, - getUserNotifications(req, res){ - logger.log({user_id: req.params.user_id}, "getting user unread notifications"); - metrics.inc("getUserNotifications"); - return Notifications.getUserNotifications(req.params.user_id, (err, notifications)=> res.json(notifications)); - }, + addNotification(req, res) { + logger.log( + { user_id: req.params.user_id, notification: req.body }, + 'adding notification' + ) + metrics.inc('addNotification') + return Notifications.addNotification(req.params.user_id, req.body, function( + err, + notifications + ) { + if (err != null) { + return res.send(500) + } else { + return res.send() + } + }) + }, - addNotification(req, res){ - logger.log({user_id: req.params.user_id, notification:req.body}, "adding notification"); - metrics.inc("addNotification"); - return Notifications.addNotification(req.params.user_id, req.body, function(err, notifications){ - if (err != null) { - return res.send(500); - } else { - return res.send(); - } - }); - }, + removeNotificationId(req, res) { + logger.log( + { + user_id: req.params.user_id, + notification_id: req.params.notification_id + }, + 'mark id notification as read' + ) + metrics.inc('removeNotificationId') + return Notifications.removeNotificationId( + req.params.user_id, + req.params.notification_id, + (err, notifications) => res.send() + ) + }, - removeNotificationId(req, res){ - logger.log({user_id: req.params.user_id, notification_id: req.params.notification_id}, "mark id notification as read"); - metrics.inc("removeNotificationId"); - return Notifications.removeNotificationId(req.params.user_id, req.params.notification_id, (err, notifications)=> res.send()); - }, + removeNotificationKey(req, res) { + logger.log( + { user_id: req.params.user_id, notification_key: req.body.key }, + 'mark key notification as read' + ) + metrics.inc('removeNotificationKey') + return Notifications.removeNotificationKey( + req.params.user_id, + req.body.key, + (err, notifications) => res.send() + ) + }, - removeNotificationKey(req, res){ - logger.log({user_id: req.params.user_id, notification_key: req.body.key}, "mark key notification as read"); - metrics.inc("removeNotificationKey"); - return Notifications.removeNotificationKey(req.params.user_id, req.body.key, (err, notifications)=> res.send()); - }, - - removeNotificationByKeyOnly(req, res){ - const notification_key = req.params.key; - logger.log({notification_key}, "mark notification as read by key only"); - metrics.inc("removeNotificationKey"); - return Notifications.removeNotificationByKeyOnly(notification_key, (err, notifications)=> res.send()); - } -}; + removeNotificationByKeyOnly(req, res) { + const notification_key = req.params.key + logger.log({ notification_key }, 'mark notification as read by key only') + metrics.inc('removeNotificationKey') + return Notifications.removeNotificationByKeyOnly( + notification_key, + (err, notifications) => res.send() + ) + } +}