Moved notifications controller code into a service module

refs #12537

- notifications controllers were overbloated with non controller related code and were identical. It is important to reduce unnecessary code ahead of v4 API introduction
- Follow up commit will transform newly created module into a class following DI pattern
This commit is contained in:
Naz 2021-01-14 15:55:55 +13:00
parent aff4a7055e
commit d2f0f0d7bc
3 changed files with 201 additions and 302 deletions

View File

@ -1,33 +1,6 @@
const moment = require('moment-timezone'); const notifications = require('../../services/notifications/notifications');
const semver = require('semver');
const Promise = require('bluebird');
const _ = require('lodash');
const settingsCache = require('../../services/settings/cache');
const ghostVersion = require('../../lib/ghost-version');
const {i18n} = require('../../lib/common');
const errors = require('@tryghost/errors');
const ObjectId = require('bson-objectid');
const api = require('./index'); const api = require('./index');
const internalContext = {context: {internal: true}}; const internalContext = {context: {internal: true}};
const _private = {};
_private.fetchAllNotifications = () => {
let allNotifications = settingsCache.get('notifications');
allNotifications.forEach((notification) => {
notification.addedAt = moment(notification.addedAt).toDate();
});
return allNotifications;
};
_private.wasSeen = (notification, user) => {
if (notification.seenBy === undefined) {
return notification.seen;
} else {
return notification.seenBy.includes(user.id);
}
};
module.exports = { module.exports = {
docName: 'notifications', docName: 'notifications',
@ -35,39 +8,11 @@ module.exports = {
browse: { browse: {
permissions: true, permissions: true,
query(frame) { query(frame) {
let allNotifications = _private.fetchAllNotifications(); return notifications.browse({
allNotifications = _.orderBy(allNotifications, 'addedAt', 'desc'); user: {
id: frame.user.id
allNotifications = allNotifications.filter((notification) => {
// NOTE: Filtering by version below is just a patch for bigger problem - notifications are not removed
// after Ghost update. Logic below should be removed when Ghost upgrade detection
// is done (https://github.com/TryGhost/Ghost/issues/10236) and notifications are
// be removed permanently on upgrade event.
const ghost20RegEx = /Ghost 2.0 is now available/gi;
// CASE: do not return old release notification
if (notification.message && (!notification.custom || notification.message.match(ghost20RegEx))) {
let notificationVersion = notification.message.match(/(\d+\.)(\d+\.)(\d+)/);
if (notification.message.match(ghost20RegEx)) {
notificationVersion = '2.0.0';
} else if (notificationVersion){
notificationVersion = notificationVersion[0];
}
const blogVersion = ghostVersion.full.match(/^(\d+\.)(\d+\.)(\d+)/);
if (notificationVersion && blogVersion && semver.gt(notificationVersion, blogVersion[0])) {
return true;
} else {
return false;
}
} }
return !_private.wasSeen(notification, frame.user);
}); });
return allNotifications;
} }
}, },
@ -81,61 +26,10 @@ module.exports = {
}, },
permissions: true, permissions: true,
query(frame) { query(frame) {
const defaults = { const {allNotifications, notificationsToAdd} = notifications.add({
dismissible: true, notifications: frame.data.notifications
location: 'bottom',
status: 'alert',
id: ObjectId.generate()
};
const overrides = {
seen: false,
addedAt: moment().toDate()
};
let notificationsToCheck = frame.data.notifications;
let notificationsToAdd = [];
const allNotifications = _private.fetchAllNotifications();
notificationsToCheck.forEach((notification) => {
const isDuplicate = allNotifications.find((n) => {
return n.id === notification.id;
});
if (!isDuplicate) {
notificationsToAdd.push(Object.assign({}, defaults, notification, overrides));
}
}); });
const hasReleaseNotification = notificationsToCheck.find((notification) => {
return !notification.custom;
});
// CASE: remove any existing release notifications if a new release notification comes in
if (hasReleaseNotification) {
_.remove(allNotifications, (el) => {
return !el.custom;
});
}
// CASE: nothing to add, skip
if (!notificationsToAdd.length) {
return Promise.resolve();
}
const releaseNotificationsToAdd = notificationsToAdd.filter((notification) => {
return !notification.custom;
});
// CASE: reorder notifications before save
if (releaseNotificationsToAdd.length > 1) {
notificationsToAdd = notificationsToAdd.filter((notification) => {
return notification.custom;
});
notificationsToAdd.push(_.orderBy(releaseNotificationsToAdd, 'created_at', 'desc')[0]);
}
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{
key: 'notifications', key: 'notifications',
@ -160,41 +54,13 @@ module.exports = {
}, },
permissions: true, permissions: true,
query(frame) { query(frame) {
const allNotifications = _private.fetchAllNotifications(); const allNotifications = notifications.destroy({
notificationId: frame.options.notification_id,
const notificationToMarkAsSeen = allNotifications.find((notification) => { user: {
return notification.id === frame.options.notification_id; id: frame.user.id
}
}); });
const notificationToMarkAsSeenIndex = allNotifications.findIndex((notification) => {
return notification.id === frame.options.notification_id;
});
if (notificationToMarkAsSeenIndex > -1 && !notificationToMarkAsSeen.dismissible) {
return Promise.reject(new errors.NoPermissionError({
message: i18n.t('errors.api.notifications.noPermissionToDismissNotif')
}));
}
if (notificationToMarkAsSeenIndex < 0) {
return Promise.reject(new errors.NotFoundError({
message: i18n.t('errors.api.notifications.notificationDoesNotExist')
}));
}
if (_private.wasSeen(notificationToMarkAsSeen, frame.user)) {
return Promise.resolve();
}
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
allNotifications[notificationToMarkAsSeenIndex].seen = true;
if (!allNotifications[notificationToMarkAsSeenIndex].seenBy) {
allNotifications[notificationToMarkAsSeenIndex].seenBy = [];
}
allNotifications[notificationToMarkAsSeenIndex].seenBy.push(frame.user.id);
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{
key: 'notifications', key: 'notifications',
@ -215,12 +81,7 @@ module.exports = {
method: 'destroy' method: 'destroy'
}, },
query() { query() {
const allNotifications = _private.fetchAllNotifications(); const allNotifications = notifications.destroyAll();
allNotifications.forEach((notification) => {
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
notification.seen = true;
});
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{

View File

@ -1,33 +1,6 @@
const moment = require('moment-timezone'); const notifications = require('../../services/notifications/notifications');
const semver = require('semver');
const Promise = require('bluebird');
const _ = require('lodash');
const settingsCache = require('../../services/settings/cache');
const ghostVersion = require('../../lib/ghost-version');
const {i18n} = require('../../lib/common');
const errors = require('@tryghost/errors');
const ObjectId = require('bson-objectid');
const api = require('./index'); const api = require('./index');
const internalContext = {context: {internal: true}}; const internalContext = {context: {internal: true}};
const _private = {};
_private.fetchAllNotifications = () => {
let allNotifications = settingsCache.get('notifications');
allNotifications.forEach((notification) => {
notification.addedAt = moment(notification.addedAt).toDate();
});
return allNotifications;
};
_private.wasSeen = (notification, user) => {
if (notification.seenBy === undefined) {
return notification.seen;
} else {
return notification.seenBy.includes(user.id);
}
};
module.exports = { module.exports = {
docName: 'notifications', docName: 'notifications',
@ -35,39 +8,11 @@ module.exports = {
browse: { browse: {
permissions: true, permissions: true,
query(frame) { query(frame) {
let allNotifications = _private.fetchAllNotifications(); return notifications.browse({
allNotifications = _.orderBy(allNotifications, 'addedAt', 'desc'); user: {
id: frame.user.id
allNotifications = allNotifications.filter((notification) => {
// NOTE: Filtering by version below is just a patch for bigger problem - notifications are not removed
// after Ghost update. Logic below should be removed when Ghost upgrade detection
// is done (https://github.com/TryGhost/Ghost/issues/10236) and notifications are
// be removed permanently on upgrade event.
const ghost20RegEx = /Ghost 2.0 is now available/gi;
// CASE: do not return old release notification
if (notification.message && (!notification.custom || notification.message.match(ghost20RegEx))) {
let notificationVersion = notification.message.match(/(\d+\.)(\d+\.)(\d+)/);
if (notification.message.match(ghost20RegEx)) {
notificationVersion = '2.0.0';
} else if (notificationVersion){
notificationVersion = notificationVersion[0];
}
const blogVersion = ghostVersion.full.match(/^(\d+\.)(\d+\.)(\d+)/);
if (notificationVersion && blogVersion && semver.gt(notificationVersion, blogVersion[0])) {
return true;
} else {
return false;
}
} }
return !_private.wasSeen(notification, frame.user);
}); });
return allNotifications;
} }
}, },
@ -81,61 +26,10 @@ module.exports = {
}, },
permissions: true, permissions: true,
query(frame) { query(frame) {
const defaults = { const {allNotifications, notificationsToAdd} = notifications.add({
dismissible: true, notifications: frame.data.notifications
location: 'bottom',
status: 'alert',
id: ObjectId.generate()
};
const overrides = {
seen: false,
addedAt: moment().toDate()
};
let notificationsToCheck = frame.data.notifications;
let notificationsToAdd = [];
const allNotifications = _private.fetchAllNotifications();
notificationsToCheck.forEach((notification) => {
const isDuplicate = allNotifications.find((n) => {
return n.id === notification.id;
});
if (!isDuplicate) {
notificationsToAdd.push(Object.assign({}, defaults, notification, overrides));
}
}); });
const hasReleaseNotification = notificationsToCheck.find((notification) => {
return !notification.custom;
});
// CASE: remove any existing release notifications if a new release notification comes in
if (hasReleaseNotification) {
_.remove(allNotifications, (el) => {
return !el.custom;
});
}
// CASE: nothing to add, skip
if (!notificationsToAdd.length) {
return Promise.resolve();
}
const releaseNotificationsToAdd = notificationsToAdd.filter((notification) => {
return !notification.custom;
});
// CASE: reorder notifications before save
if (releaseNotificationsToAdd.length > 1) {
notificationsToAdd = notificationsToAdd.filter((notification) => {
return notification.custom;
});
notificationsToAdd.push(_.orderBy(releaseNotificationsToAdd, 'created_at', 'desc')[0]);
}
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{
key: 'notifications', key: 'notifications',
@ -160,41 +54,13 @@ module.exports = {
}, },
permissions: true, permissions: true,
query(frame) { query(frame) {
const allNotifications = _private.fetchAllNotifications(); const allNotifications = notifications.destroy({
notificationId: frame.options.notification_id,
const notificationToMarkAsSeen = allNotifications.find((notification) => { user: {
return notification.id === frame.options.notification_id; id: frame.user.id
}
}); });
const notificationToMarkAsSeenIndex = allNotifications.findIndex((notification) => {
return notification.id === frame.options.notification_id;
});
if (notificationToMarkAsSeenIndex > -1 && !notificationToMarkAsSeen.dismissible) {
return Promise.reject(new errors.NoPermissionError({
message: i18n.t('errors.api.notifications.noPermissionToDismissNotif')
}));
}
if (notificationToMarkAsSeenIndex < 0) {
return Promise.reject(new errors.NotFoundError({
message: i18n.t('errors.api.notifications.notificationDoesNotExist')
}));
}
if (_private.wasSeen(notificationToMarkAsSeen, frame.user)) {
return Promise.resolve();
}
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
allNotifications[notificationToMarkAsSeenIndex].seen = true;
if (!allNotifications[notificationToMarkAsSeenIndex].seenBy) {
allNotifications[notificationToMarkAsSeenIndex].seenBy = [];
}
allNotifications[notificationToMarkAsSeenIndex].seenBy.push(frame.user.id);
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{
key: 'notifications', key: 'notifications',
@ -215,12 +81,7 @@ module.exports = {
method: 'destroy' method: 'destroy'
}, },
query() { query() {
const allNotifications = _private.fetchAllNotifications(); const allNotifications = notifications.destroyAll();
allNotifications.forEach((notification) => {
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
notification.seen = true;
});
return api.settings.edit({ return api.settings.edit({
settings: [{ settings: [{

View File

@ -0,0 +1,177 @@
const moment = require('moment-timezone');
const semver = require('semver');
const Promise = require('bluebird');
const _ = require('lodash');
const settingsCache = require('../settings/cache');
const ghostVersion = require('../../lib/ghost-version');
const {i18n} = require('../../lib/common');
const errors = require('@tryghost/errors');
const ObjectId = require('bson-objectid');
const fetchAllNotifications = () => {
let allNotifications = settingsCache.get('notifications');
allNotifications.forEach((notification) => {
notification.addedAt = moment(notification.addedAt).toDate();
});
return allNotifications;
};
const wasSeen = (notification, user) => {
if (notification.seenBy === undefined) {
return notification.seen;
} else {
return notification.seenBy.includes(user.id);
}
};
const browse = ({user}) => {
let allNotifications = fetchAllNotifications();
allNotifications = _.orderBy(allNotifications, 'addedAt', 'desc');
allNotifications = allNotifications.filter((notification) => {
// NOTE: Filtering by version below is just a patch for bigger problem - notifications are not removed
// after Ghost update. Logic below should be removed when Ghost upgrade detection
// is done (https://github.com/TryGhost/Ghost/issues/10236) and notifications are
// be removed permanently on upgrade event.
const ghost20RegEx = /Ghost 2.0 is now available/gi;
// CASE: do not return old release notification
if (notification.message && (!notification.custom || notification.message.match(ghost20RegEx))) {
let notificationVersion = notification.message.match(/(\d+\.)(\d+\.)(\d+)/);
if (notification.message.match(ghost20RegEx)) {
notificationVersion = '2.0.0';
} else if (notificationVersion){
notificationVersion = notificationVersion[0];
}
const blogVersion = ghostVersion.full.match(/^(\d+\.)(\d+\.)(\d+)/);
if (notificationVersion && blogVersion && semver.gt(notificationVersion, blogVersion[0])) {
return true;
} else {
return false;
}
}
return !wasSeen(notification, user);
});
return allNotifications;
};
const add = ({notifications}) => {
const defaults = {
dismissible: true,
location: 'bottom',
status: 'alert',
id: ObjectId.generate()
};
const overrides = {
seen: false,
addedAt: moment().toDate()
};
let notificationsToCheck = notifications;
let notificationsToAdd = [];
const allNotifications = fetchAllNotifications();
notificationsToCheck.forEach((notification) => {
const isDuplicate = allNotifications.find((n) => {
return n.id === notification.id;
});
if (!isDuplicate) {
notificationsToAdd.push(Object.assign({}, defaults, notification, overrides));
}
});
const hasReleaseNotification = notificationsToCheck.find((notification) => {
return !notification.custom;
});
// CASE: remove any existing release notifications if a new release notification comes in
if (hasReleaseNotification) {
_.remove(allNotifications, (el) => {
return !el.custom;
});
}
// CASE: nothing to add, skip
if (!notificationsToAdd.length) {
return Promise.resolve();
}
const releaseNotificationsToAdd = notificationsToAdd.filter((notification) => {
return !notification.custom;
});
// CASE: reorder notifications before save
if (releaseNotificationsToAdd.length > 1) {
notificationsToAdd = notificationsToAdd.filter((notification) => {
return notification.custom;
});
notificationsToAdd.push(_.orderBy(releaseNotificationsToAdd, 'created_at', 'desc')[0]);
}
return {allNotifications, notificationsToAdd};
};
const destroy = ({notificationId, user}) => {
const allNotifications = fetchAllNotifications();
const notificationToMarkAsSeen = allNotifications.find((notification) => {
return notification.id === notificationId;
});
const notificationToMarkAsSeenIndex = allNotifications.findIndex((notification) => {
return notification.id === notificationId;
});
if (notificationToMarkAsSeenIndex > -1 && !notificationToMarkAsSeen.dismissible) {
return Promise.reject(new errors.NoPermissionError({
message: i18n.t('errors.api.notifications.noPermissionToDismissNotif')
}));
}
if (notificationToMarkAsSeenIndex < 0) {
return Promise.reject(new errors.NotFoundError({
message: i18n.t('errors.api.notifications.notificationDoesNotExist')
}));
}
if (wasSeen(notificationToMarkAsSeen, user)) {
return Promise.resolve();
}
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
allNotifications[notificationToMarkAsSeenIndex].seen = true;
if (!allNotifications[notificationToMarkAsSeenIndex].seenBy) {
allNotifications[notificationToMarkAsSeenIndex].seenBy = [];
}
allNotifications[notificationToMarkAsSeenIndex].seenBy.push(user.id);
return allNotifications;
};
const destroyAll = () => {
const allNotifications = fetchAllNotifications();
allNotifications.forEach((notification) => {
// @NOTE: We don't remove the notifications, because otherwise we will receive them again from the service.
notification.seen = true;
});
return allNotifications;
};
module.exports.browse = browse;
module.exports.add = add;
module.exports.destroy = destroy;
module.exports.destroyAll = destroyAll;