Skip to content

Commit

Permalink
Merge pull request #16 from CMU-313/send-notif-upon-resolved
Browse files Browse the repository at this point in the history
[WIP] Feature Send notifications when a question is resolved #8
  • Loading branch information
Ginka3 authored Feb 27, 2025
2 parents 51118dd + 82e5c0c commit dea0265
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 75 deletions.
Binary file modified dump.rdb
Binary file not shown.
80 changes: 77 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"nodebb-plugin-dbsearch": "6.2.5",
"nodebb-plugin-emoji": "5.1.15",
"nodebb-plugin-emoji-android": "4.0.0",
"nodebb-plugin-location-to-map": "^0.1.1",
"nodebb-plugin-markdown": "12.2.6",
"nodebb-plugin-mentions": "4.4.3",
"nodebb-plugin-ntfy": "1.7.4",
Expand Down Expand Up @@ -156,6 +157,7 @@
"@apidevtools/swagger-parser": "10.1.0",
"@commitlint/cli": "19.3.0",
"@commitlint/config-angular": "19.3.0",
"chai": "^5.2.0",
"coveralls": "3.1.1",
"eslint": "8.57.0",
"eslint-config-nodebb": "0.2.1",
Expand All @@ -165,11 +167,12 @@
"husky": "8.0.3",
"jsdom": "24.0.0",
"lint-staged": "15.2.2",
"mocha": "10.4.0",
"mocha": "^10.4.0",
"mocha-lcov-reporter": "1.3.0",
"mockdate": "^3.0.5",
"nyc": "15.1.0",
"sinon": "^19.0.2",
"sinon-chai": "^4.0.0",
"smtp-server": "3.13.4"
},
"optionalDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions public/src/client/topic.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ define('forum/topic', [

handleTopicSearch();

if (ajaxify.data.resolved) {
$('.topic-title').append(' <span class="badge badge-success">Resolved</span>');
}

hooks.fire('action:topic.loaded', ajaxify.data);
};

Expand Down
37 changes: 29 additions & 8 deletions src/controllers/topics.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const helpers = require('./helpers');
const pagination = require('../pagination');
const utils = require('../utils');
const analytics = require('../analytics');
const notifications = require('../notifications');

const topicsController = module.exports;

Expand Down Expand Up @@ -408,32 +409,52 @@ topicsController.pagination = async function (req, res, next) {

const db = require('../database');

const socket = require('../socket.io');

topicsController.setResolved = async function (req, res) {
try {
const { tid } = req.params;
const { resolved } = req.body; // Expected payload: { "resolved": true } or { "resolved": false }
const { resolved } = req.body; // ✅ Use req.body.resolved

if (typeof resolved !== 'boolean') {
return res.status(400).json({ error: "Invalid request. 'resolved' must be a boolean." });
}

// Fetch the topic to ensure it exists
const topic = await topics.getTopicData(tid);
if (!topic) {
// ✅ Instead of checking tags, update the topic with the request's resolved value
await db.setObjectField(`topic:${tid}`, 'resolved', resolved.toString());

// Fetch topic data
const topicData = await topics.getTopicFields(tid, ['uid', 'title']);
if (!topicData || !topicData.uid) {
return res.status(404).json({ error: 'Topic not found' });
}

// Ensure the user has permission to edit the topic
const canEdit = await privileges.topics.canEdit(tid, req.uid);
if (!canEdit) {
return res.status(403).json({ error: '[[error:no-privileges]]' });
}

// Update the `resolved` field in Redis as a boolean
await db.setObjectField(`topic:${tid}`, 'resolved', resolved);
// Fetch watchers
const watchers = await db.getSortedSetRange(`tid:${tid}:watchers`, 0, -1);
const recipients = new Set([...watchers, topicData.uid]);

// Send notification
await notifications.create({
type: 'topic-resolved-status',
bodyShort: resolved ? `The topic **"${topicData.title}"** has been marked as **resolved**.` :
`The topic **"${topicData.title}"** is no longer marked as resolved.`,
bodyLong: `Your watched topic **"${topicData.title}"** has been updated.`,
nid: `topic:resolved-status:${tid}`,
from: topicData.uid,
tid: tid,
path: `/topic/${tid}`,
}, [...recipients]);

// Emit event
socket.emit('event:topicResolvedUpdated', { tid, resolved });

res.json({ message: 'Topic resolved status updated', tid, resolved });
} catch (error) {
res.status(500).json({ error: error.message });
}
};

33 changes: 32 additions & 1 deletion src/topics/tags.js
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,40 @@ module.exports = function (Topics) {

tags = await Topics.filterTags(tags, cid);
await Topics.addTags(tags, [tid]);
plugins.hooks.fire('action:topic.updateTags', { tags, tid });

// Determine resolved status
const resolved = tags.includes('resolved');
await db.setObjectField(`topic:${tid}`, 'resolved', resolved.toString());

// Fetch topic data
const topicData = await Topics.getTopicFields(tid, ['uid', 'title']);

// Fetch watchers
const watchers = await db.getSortedSetRange(`tid:${tid}:watchers`, 0, -1);
const recipients = new Set([...watchers, topicData.uid]);

// Create and send notification to OP and watchers
if (recipients.size > 0) {
const notifObj = await notifications.create({
type: 'topic-resolved-status',
bodyShort: resolved ?
`The topic "${topicData.title}" has been marked as resolved.` :
`The topic "${topicData.title}" is no longer marked as resolved.`,
bodyLong: `Your watched topic "${topicData.title}" has been updated.`,
nid: `topic:resolved-status:${tid}`,
from: topicData.uid,
tid: tid,
path: `/topic/${tid}`,
});

await notifications.push(notifObj, Array.from(recipients));
}

// Emit event to notify frontend
plugins.hooks.fire('action:topic.updateTags', { tags, tid, resolved });
};


Topics.deleteTopicTags = async function (tid) {
const topicData = await Topics.getTopicFields(tid, ['cid', 'tags']);
const { cid } = topicData;
Expand Down
Loading

0 comments on commit dea0265

Please sign in to comment.