diff --git a/dump.rdb b/dump.rdb
index 80eb17f6e2..16da9a5145 100644
Binary files a/dump.rdb and b/dump.rdb differ
diff --git a/install/data/defaults.json b/install/data/defaults.json
index a5921ab940..329f612938 100644
--- a/install/data/defaults.json
+++ b/install/data/defaults.json
@@ -119,6 +119,7 @@
"notificationType_post-queue": "notification",
"notificationType_new-post-flag": "notification",
"notificationType_new-user-flag": "notification",
+ "notificationType_faculty-reply": "notification",
"topicStaleDays": 60,
"maxTopicsPerPage": 20,
"maxPostsPerPage": 20,
diff --git a/public/language/ar/notifications.json b/public/language/ar/notifications.json
index 06ddfa34c7..d2f6e335df 100644
--- a/public/language/ar/notifications.json
+++ b/public/language/ar/notifications.json
@@ -46,7 +46,6 @@
"user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)",
"user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)",
"user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)",
- "admin-posted-to": "[COURSE FACULTY] %1 أضاف ردًا إلى: %2",
"user-posted-to": "%1 أضاف ردا إلى: %2",
"user-posted-to-dual": "%1 and %2 have posted replies to: %3",
"user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4",
@@ -62,6 +61,11 @@
"user-started-following-you-dual": "%1 and %2 started following you.",
"user-started-following-you-triple": "%1, %2 and %3 started following you.",
"user-started-following-you-multiple": "%1, %2 and %3 others started following you.",
+
+ "faculty-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
+ "faculty-posted-to-dual" : "[COURSE FACULTY] %1 and [COURSE FACULTY] %2 have posted replies to: %3",
+ "faculty-posted-to-multiple" : "[COURSE FACULTY] %1, [COURSE FACULTY] %2 and %3 other faculty members have posted replies to: %4",
+
"new-register": "%1 sent a registration request.",
"new-register-multiple": "There are %1 registration requests awaiting review.",
"flag-assigned-to-you": "تم تخصيص العلامة 1% لك",
@@ -98,5 +102,7 @@
"notificationType-post-queue": "When a new post is queued",
"notificationType-new-post-flag": "When a post is flagged",
"notificationType-new-user-flag": "When a user is flagged",
- "notificationType-new-reward": "When you earn a new reward"
+ "notificationType-new-reward": "When you earn a new reward",
+ "notificationType-faculty-reply": "When a course faculty replies to your post"
+
}
\ No newline at end of file
diff --git a/public/language/en-GB/notifications.json b/public/language/en-GB/notifications.json
index eb5b4a3382..8683fe74c5 100644
--- a/public/language/en-GB/notifications.json
+++ b/public/language/en-GB/notifications.json
@@ -50,7 +50,6 @@
"user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)",
"user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)",
"user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)",
- "admin-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
"user-posted-to" : "%1 has posted a reply to: %2",
"user-posted-to-dual" : "%1 and %2 have posted replies to: %3",
"user-posted-to-triple" : "%1, %2 and %3 have posted replies to: %4",
@@ -58,6 +57,11 @@
"user-posted-topic": "%1 has posted a new topic: %2",
"user-edited-post" : "%1 has edited a post in %2",
+ "faculty-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
+ "faculty-posted-to-dual" : "[COURSE FACULTY] %1 and [COURSE FACULTY] %2 have posted replies to: %3",
+ "faculty-posted-to-multiple" : "[COURSE FACULTY] %1, [COURSE FACULTY] %2 and %3 other faculty members have posted replies to: %4",
+
+
"user-posted-topic-with-tag": "%1 has posted a new topic with tag %2",
"user-posted-topic-with-tag-dual": "%1 has posted a new topic with tags %2 and %3",
"user-posted-topic-with-tag-triple": "%1 has posted a new topic with tags %2, %3 and %4",
@@ -90,6 +94,7 @@
"notification-only": "Notification Only",
"email-only": "Email Only",
"notification-and-email": "Notification & Email",
+
"notificationType-upvote": "When someone upvotes your post",
"notificationType-new-topic": "When someone you follow posts a topic",
"notificationType-new-topic-with-tag": "When a topic is posted with a tag you follow",
@@ -107,5 +112,7 @@
"notificationType-post-queue": "When a new post is queued",
"notificationType-new-post-flag": "When a post is flagged",
"notificationType-new-user-flag": "When a user is flagged",
- "notificationType-new-reward": "When you earn a new reward"
+ "notificationType-new-reward": "When you earn a new reward",
+ "notificationType-faculty-reply": "When a course faculty replies to your post"
+
}
diff --git a/public/language/en-US/notifications.json b/public/language/en-US/notifications.json
index c6d470da84..155d37153c 100644
--- a/public/language/en-US/notifications.json
+++ b/public/language/en-US/notifications.json
@@ -46,7 +46,6 @@
"user-flagged-user-dual": "%1 and %2 flagged a user profile (%3)",
"user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)",
"user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)",
- "admin-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
"user-posted-to": "%1 has posted a reply to: %2",
"user-posted-to-dual": "%1 and %2 have posted replies to: %3",
"user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4",
@@ -62,6 +61,12 @@
"user-started-following-you-dual": "%1 and %2 started following you.",
"user-started-following-you-triple": "%1, %2 and %3 started following you.",
"user-started-following-you-multiple": "%1, %2 and %3 others started following you.",
+
+ "faculty-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
+ "faculty-posted-to-dual" : "[COURSE FACULTY] %1 and [COURSE FACULTY] %2 have posted replies to: %3",
+ "faculty-posted-to-multiple" : "[COURSE FACULTY] %1, [COURSE FACULTY] %2 and %3 other faculty members have posted replies to: %4",
+ "notificationType-faculty-reply": "When a course faculty replies to your post",
+
"new-register": "%1 sent a registration request.",
"new-register-multiple": "There are %1 registration requests awaiting review.",
"flag-assigned-to-you": "Flag %1 has been assigned to you",
diff --git a/public/language/es/notifications.json b/public/language/es/notifications.json
index cfb6dba8de..966f93ac24 100644
--- a/public/language/es/notifications.json
+++ b/public/language/es/notifications.json
@@ -46,7 +46,6 @@
"user-flagged-user-dual": "%1 y %2 reportaron el perfil (%3)",
"user-flagged-user-triple": "%1, %2 and %3 flagged a user profile (%4)",
"user-flagged-user-multiple": "%1, %2 and %3 others flagged a user profile (%4)",
- "admin-posted-to" : "[COURSE FACULTY] %1 ha respondido a: %2",
"user-posted-to": "%1 ha respondido a: %2",
"user-posted-to-dual": "%1 y %2 han respondido a %3",
"user-posted-to-triple": "%1, %2 and %3 have posted replies to: %4",
@@ -62,6 +61,12 @@
"user-started-following-you-dual": "%1 y %2 comenzaron a seguirte.",
"user-started-following-you-triple": "%1, %2 and %3 started following you.",
"user-started-following-you-multiple": "%1, %2 and %3 others started following you.",
+
+ "faculty-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
+ "faculty-posted-to-dual" : "[COURSE FACULTY] %1 and [COURSE FACULTY] %2 have posted replies to: %3",
+ "faculty-posted-to-multiple" : "[COURSE FACULTY] %1, [COURSE FACULTY] %2 and %3 other faculty members have posted replies to: %4",
+ "notificationType-faculty-reply": "When a course faculty replies to your post",
+
"new-register": "%1 envió una solicitud de registro.",
"new-register-multiple": "Hay %1 peticiones de registros pendientes de revisión",
"flag-assigned-to-you": "Reporte %1 te ha sido asignado.",
diff --git a/public/language/fr/notifications.json b/public/language/fr/notifications.json
index f136229a75..93623f96ca 100644
--- a/public/language/fr/notifications.json
+++ b/public/language/fr/notifications.json
@@ -46,7 +46,6 @@
"user-flagged-user-dual": "%1 et %2 ont signalé un profil utilisateur (%3)",
"user-flagged-user-triple": "%1, %2 et %3 ont signalé un profil utilisateur (%4)",
"user-flagged-user-multiple": "%1, %2 et %3 autres ont signalé un profil utilisateur (%4)",
- "admin-posted-to": "[COURSE FACULTY] %1 a répondu à : %2",
"user-posted-to": "%1 a répondu à : %2",
"user-posted-to-dual": "%1 et %2 ont posté une réponse à : %3",
"user-posted-to-triple": "%1, %2 et %3 ont publié des réponses à : %4",
@@ -62,6 +61,12 @@
"user-started-following-you-dual": "%1 et %2 se sont abonnés à votre compte.",
"user-started-following-you-triple": "%1, %2 et %3 ont commencé à vous suivre.",
"user-started-following-you-multiple": "%1, %2 et %3 autres ont commencé à vous suivre.",
+
+ "faculty-posted-to" : "[COURSE FACULTY] %1 has posted a reply to: %2",
+ "faculty-posted-to-dual" : "[COURSE FACULTY] %1 and [COURSE FACULTY] %2 have posted replies to: %3",
+ "faculty-posted-to-multiple" : "[COURSE FACULTY] %1, [COURSE FACULTY] %2 and %3 other faculty members have posted replies to: %4",
+ "notificationType-faculty-reply": "When a course faculty replies to your post",
+
"new-register": "%1 a envoyé une demande d'incription.",
"new-register-multiple": "%1 inscription(s) est en attente de validation.",
"flag-assigned-to-you": "Signalement %1 vous a été assigné",
diff --git a/public/openapi/components/schemas/SettingsObj.yaml b/public/openapi/components/schemas/SettingsObj.yaml
index 2ccc8e161c..97337ebf9e 100644
--- a/public/openapi/components/schemas/SettingsObj.yaml
+++ b/public/openapi/components/schemas/SettingsObj.yaml
@@ -1,6 +1,11 @@
Settings:
type: object
properties:
+ # Adding faculty reply to the api schema
+ notificationType_faculty-reply:
+ type: string
+ description: Notification type for when a course faculty replies to your post
+ enum: [none, notification, email, notificationemail]
showemail:
type: boolean
description: Show user email in profile page
@@ -161,4 +166,5 @@ Settings:
- notificationType_new-user-flag
- categoryWatchState
- notificationType_group-request-membership
- - uid
\ No newline at end of file
+ - uid
+ - notificationType_faculty-reply
\ No newline at end of file
diff --git a/src/notifications.js b/src/notifications.js
index fdb9998248..18db094a2a 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -42,6 +42,8 @@ Notifications.baseTypes = [
'notificationType_group-leave',
'notificationType_group-request-membership',
'notificationType_new-reward',
+ // Adding the new notification type
+ 'notificationType_faculty-reply',
];
Notifications.privilegedTypes = [
@@ -155,6 +157,10 @@ Notifications.create = async function (data) {
if (!result.data) {
return null;
}
+ // Handle faculty reply notification
+ if (result.data.type === 'faculty-reply') {
+ result.data.importance = result.data.importance || 6; // Setting a higher importance for faculty replies
+ }
await Promise.all([
db.sortedSetAdd('notifications', now, data.nid),
db.setObject(`notifications:${data.nid}`, data),
@@ -241,7 +247,7 @@ async function pushToUids(uids, notification) {
results = await getUidsBySettings(data.uids);
}
await sendNotification(results.uidsToNotify);
- const delayNotificationTypes = ['new-chat', 'new-group-chat', 'new-public-chat'];
+ const delayNotificationTypes = ['new-chat', 'new-group-chat', 'new-public-chat', 'faculty-reply'];
if (delayNotificationTypes.includes(notification.type)) {
const cacheKey = `${notification.mergeId}|${results.uidsToEmail.join(',')}`;
if (notificationCache.has(cacheKey)) {
diff --git a/src/topics/create.js b/src/topics/create.js
index f874f8d64f..dbad977afe 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -209,38 +209,34 @@ module.exports = function (Topics) {
if (parseInt(uid, 10) || meta.config.allowGuestReplyNotifications) {
const { displayname } = postData.user;
- // Checks if the user is an admin
- const isAdmin = await privileges.users.isAdministrator(uid);
+ let notificationType = 'new-reply';
let notificationMessage;
+ let nid;
+ let mergeId;
if (isAdmin) {
- // Handles admin/instructor notification separately so it doesn't get bundled with
- // notifications of non-admin replies.
- // It does this by using a different notification ID and merge ID for admin replies.
- notificationMessage = translator.compile('notifications:admin-posted-to', displayname, postData.topic.title);
- Topics.notifyFollowers(postData, uid, {
- type: 'new-reply',
- bodyShort: notificationMessage,
- nid: `admin_post:tid:${postData.topic.tid}:pid:${postData.pid}:uid:${uid}`,
- mergeId: `notifications:admin-posted-to|${postData.topic.tid}`,
- });
+ notificationType = 'faculty-reply';
+ notificationMessage = translator.compile('notifications:faculty-posted-to', displayname, postData.topic.title);
+ nid = `faculty_post:tid:${postData.topic.tid}:pid:${postData.pid}:uid:${uid}`;
+ mergeId = `notifications:faculty-posted-to|${postData.topic.tid}`;
} else {
- // This code handles non-admin replies, allowing bundling.
notificationMessage = translator.compile('notifications:user-posted-to', displayname, postData.topic.title);
-
- Topics.notifyFollowers(postData, uid, {
- type: 'new-reply',
- bodyShort: notificationMessage,
- nid: `new_post:tid:${postData.topic.tid}:pid:${postData.pid}:uid:${uid}`,
- mergeId: `notifications:user-posted-to|${postData.topic.tid}`,
- });
+ nid = `new_post:tid:${postData.topic.tid}:pid:${postData.pid}:uid:${uid}`;
+ mergeId = `notifications:user-posted-to|${postData.topic.tid}`;
}
+ await Topics.notifyFollowers(postData, uid, {
+ type: notificationType,
+ bodyShort: notificationMessage,
+ nid: nid,
+ mergeId: mergeId,
+ });
+
analytics.increment(['posts', `posts:byCid:${data.cid}`]);
plugins.hooks.fire('action:topic.reply', { post: _.clone(postData), data: data });
-
- return postData;
}
+
+ return postData;
};
async function onNewPost(postData, data) {
diff --git a/src/upgrades/1.7.6/notification_types.js b/src/upgrades/1.7.6/notification_types.js
index 42d59cdd38..b90fce87da 100644
--- a/src/upgrades/1.7.6/notification_types.js
+++ b/src/upgrades/1.7.6/notification_types.js
@@ -13,6 +13,7 @@ module.exports = {
notificationType_upvote: config.notificationType_upvote || 'notification',
'notificationType_new-topic': config['notificationType_new-topic'] || 'notification',
'notificationType_new-reply': config['notificationType_new-reply'] || postNotifications,
+ 'notificationType_faculty-reply': config['notificationType_faculty-reply'] || 'notification',
notificationType_follow: config.notificationType_follow || 'notification',
'notificationType_new-chat': config['notificationType_new-chat'] || chatNotifications,
'notificationType_group-invite': config['notificationType_group-invite'] || 'notification',
diff --git a/src/upgrades/3.8.4/add_faculty_reply_notification_setting.js b/src/upgrades/3.8.4/add_faculty_reply_notification_setting.js
new file mode 100644
index 0000000000..608e40509f
--- /dev/null
+++ b/src/upgrades/3.8.4/add_faculty_reply_notification_setting.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const db = require('../../database');
+const batch = require('../../batch');
+
+module.exports = {
+ name: 'Add_faculty_reply_notification_setting_for_all_users',
+ timestamp: Date.UTC(2024, 10, 8),
+ method: async function () {
+ const { progress } = this;
+
+ await batch.processSortedSet('users:joindate', async (uids) => {
+ await Promise.all(uids.map(async (uid) => {
+ await db.setObjectField(`user:${uid}:settings`, 'notificationType_faculty-reply', 'notification');
+ }));
+
+ if (progress) {
+ progress.incr(uids.length);
+ }
+ }, {
+ batch: 500,
+ });
+ },
+};
diff --git a/src/user/settings.js b/src/user/settings.js
index d85a712ba6..b6a5e19752 100644
--- a/src/user/settings.js
+++ b/src/user/settings.js
@@ -84,6 +84,9 @@ module.exports = function (User) {
settings[notificationType] = getSetting(settings, notificationType, 'notification');
});
+ // Load the faculty reply notification setting
+ settings['notificationType_faculty-reply'] = getSetting(settings, 'notificationType_faculty-reply', 'notification');
+
return settings;
}
@@ -155,6 +158,12 @@ module.exports = function (User) {
settings[notificationType] = data[notificationType];
}
});
+
+ // Handling for faculty-reply notification
+ if (data['notificationType_faculty-reply']) {
+ settings['notificationType_faculty-reply'] = data['notificationType_faculty-reply'];
+ }
+
const result = await plugins.hooks.fire('filter:user.saveSettings', { uid: uid, settings: settings, data: data });
await db.setObject(`user:${uid}:settings`, result.settings);
await User.updateDigestSetting(uid, data.dailyDigestFreq);
diff --git a/test/notifications.js b/test/notifications.js
index 0fdcc7f117..9f962b9192 100644
--- a/test/notifications.js
+++ b/test/notifications.js
@@ -12,6 +12,7 @@ const topics = require('../src/topics');
const categories = require('../src/categories');
const notifications = require('../src/notifications');
const socketNotifications = require('../src/socket.io/notifications');
+const groups = require('../src/groups');
const sleep = util.promisify(setTimeout);
@@ -388,6 +389,119 @@ describe('Notifications', () => {
assert(data);
});
+ // New tests for faculty reply notifications
+ // Asked help from ChatGPT to generate these tests.
+ describe('Faculty Reply Notifications', () => {
+ let adminUid;
+ let regularUid;
+ let cid;
+ let tid;
+
+ before(async () => {
+ // Create test users
+ adminUid = await user.create({ username: 'admin' });
+ regularUid = await user.create({ username: 'regular' });
+
+ // Make the admin user a part of the administrators group
+ await groups.join('administrators', adminUid);
+
+ // Create test category and topic
+ cid = await categories.create({
+ name: 'Test Category',
+ description: 'Test category created by testing script',
+ }).then(category => category.cid);
+
+ tid = await topics.post({
+ uid: regularUid,
+ cid: cid,
+ title: 'Test Topic',
+ content: 'This is a test topic',
+ }).then(result => result.topicData.tid);
+ });
+
+ it('should create a faculty-reply notification when an admin (faculty) replies', async () => {
+ const postData = await topics.reply({
+ uid: adminUid,
+ tid: tid,
+ content: 'This is a faculty reply',
+ });
+
+ // Wait for notification to be created
+ await sleep(3000);
+
+ const notifs = await db.getSortedSetRange(`uid:${regularUid}:notifications:unread`, 0, -1);
+ console.log('Notifications:', notifs);
+
+ const notifData = await db.getObjects(notifs.map(nid => `notifications:${nid}`));
+ console.log('Notification Data:', notifData);
+
+ const facultyReplyNotif = notifData.find(n => n.type === 'faculty-reply');
+
+ if (!facultyReplyNotif) {
+ console.log('All notification types:', notifData.map(n => n.type));
+ }
+
+ assert(facultyReplyNotif, 'Faculty reply notification should exist');
+ assert.strictEqual(facultyReplyNotif.bodyShort, `[[notifications:faculty-posted-to, ${postData.user.displayname}, ${postData.topic.title}]]`);
+ });
+
+ it('should not create a faculty-reply notification when a regular user replies', async () => {
+ // Clear existing notifications
+ await db.delete(`uid:${regularUid}:notifications:unread`);
+
+ const beforeNotifs = await db.getSortedSetRange(`uid:${regularUid}:notifications:unread`, 0, -1);
+ console.log('Notifications before reply:', beforeNotifs);
+
+ const replyData = await topics.reply({
+ uid: regularUid,
+ tid: tid,
+ content: 'This is a regular user reply',
+ });
+ console.log('Reply data:', replyData);
+
+ // Wait for potential notification to be created
+ await sleep(3000);
+
+ const afterNotifs = await db.getSortedSetRange(`uid:${regularUid}:notifications:unread`, 0, -1);
+ console.log('Notifications after reply:', afterNotifs);
+
+ const notifData = await db.getObjects(afterNotifs.map(nid => `notifications:${nid}`));
+ console.log('Notification Data:', notifData);
+
+ // Log all notification types
+ console.log('All notification types:', notifData.map(n => n.type));
+
+ // Check that no new notifications were created
+ assert.strictEqual(afterNotifs.length, 0, 'No new notifications should be created for a regular user reply');
+
+ // Logging existing notifications
+ if (notifData.length > 0) {
+ console.log('Unexpected notifications:', notifData);
+ }
+ });
+
+ it('should allow users to configure faculty-reply notification preferences', async () => {
+ await user.setSetting(regularUid, 'notificationType_faculty-reply', 'notification');
+
+ const settings = await user.getSettings(regularUid);
+ assert.strictEqual(settings['notificationType_faculty-reply'], 'notification', 'Faculty reply notification preference should be set');
+ });
+
+ after(async () => {
+ // Clean up users, category, and topic
+ await Promise.all([
+ user.delete(adminUid),
+ user.delete(regularUid),
+ ].map(p => p.catch(() => { /* ignore errors */ })));
+ await db.delete(`category:${cid}`);
+ await topics.purge(tid);
+
+ // Remove admin from administrators group
+ await groups.leave('administrators', adminUid);
+ });
+ });
+
+
it('should send welcome notification', (done) => {
meta.config.welcomeNotification = 'welcome to the forums';
user.notifications.sendWelcomeNotification(uid, (err) => {