0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00
ghost/core/server/services/notifications/notifications.js
Naz 4dc413d6a1 🐛 Fixed upgrade notification showing post-upgrade
closes https://github.com/TryGhost/Team/issues/564
refs https://github.com/TryGhost/Ghost/issues/10236

- The notification to upgrade to new 4.0 Ghost version was still visible to users after upgrading the instance to 4.0. This was caused by notification filtering not taking into account 3.x or 4.x versions.
- The fix filters out notifications that detect a major version notification using `x.0 is now available` pattern and compares current version to that major. This should future proof the issue from happening in Ghost 5.0 (but a proper holistic fix is preferable!)
2021-03-23 16:42:46 +13:00

188 lines
6.6 KiB
JavaScript

const moment = require('moment-timezone');
const semver = require('semver');
const Promise = require('bluebird');
const _ = require('lodash');
const errors = require('@tryghost/errors');
const ObjectId = require('bson-objectid');
class Notifications {
/**
*
* @param {Object} options
* @param {Object} options.settingsCache - settings cache instance
* @param {Object} options.i18n - i18n instance
* @param {Object} options.ghostVersion
* @param {String} options.ghostVersion.full - Ghost instance version in "full" format - major.minor.patch
*/
constructor({settingsCache, i18n, ghostVersion}) {
this.settingsCache = settingsCache;
this.i18n = i18n;
this.ghostVersion = ghostVersion;
}
fetchAllNotifications() {
let allNotifications = this.settingsCache.get('notifications');
allNotifications.forEach((notification) => {
notification.addedAt = moment(notification.addedAt).toDate();
});
return allNotifications;
}
wasSeen(notification, user) {
if (notification.seenBy === undefined) {
return notification.seen;
} else {
return notification.seenBy.includes(user.id);
}
}
browse({user}) {
let allNotifications = this.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 ghostMajorRegEx = /Ghost (?<major>\d).0 is now available/gi;
// CASE: do not return old release notification
if (notification.message && (!notification.custom || notification.message.match(ghostMajorRegEx))) {
let notificationVersion = notification.message.match(/(\d+\.)(\d+\.)(\d+)/);
const ghostMajorMatch = ghostMajorRegEx.exec(notification.message);
if (ghostMajorMatch.groups.major) {
notificationVersion = `${ghostMajorMatch.groups.major}.0.0`;
} else if (notificationVersion){
notificationVersion = notificationVersion[0];
}
const blogVersion = this.ghostVersion.full.match(/^(\d+\.)(\d+\.)(\d+)/);
if (notificationVersion && blogVersion && semver.gt(notificationVersion, blogVersion[0])) {
return true;
} else {
return false;
}
}
return !this.wasSeen(notification, user);
});
return allNotifications;
}
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 = this.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 {allNotifications, notificationsToAdd};
}
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};
}
destroy({notificationId, user}) {
const allNotifications = this.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: this.i18n.t('errors.api.notifications.noPermissionToDismissNotif')
}));
}
if (notificationToMarkAsSeenIndex < 0) {
return Promise.reject(new errors.NotFoundError({
message: this.i18n.t('errors.api.notifications.notificationDoesNotExist')
}));
}
if (this.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;
}
destroyAll() {
const allNotifications = this.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 = Notifications;