From 7c41d6977dc927e24de41928e35206d86b871654 Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 12:18:57 -0500
Subject: [PATCH 1/7] Set up API endpoint for resolve
---
public/openapi/write.yaml | 4 +++
public/openapi/write/topics/tid/resolve.yaml | 28 ++++++++++++++++++++
2 files changed, 32 insertions(+)
create mode 100644 public/openapi/write/topics/tid/resolve.yaml
diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml
index 6f55dbc..74da874 100644
--- a/public/openapi/write.yaml
+++ b/public/openapi/write.yaml
@@ -110,6 +110,10 @@ paths:
$ref: 'write/topics.yaml'
/topics/{tid}:
$ref: 'write/topics/tid.yaml'
+ # Instructed to add by ChatGPT, code copied from lines above
+ # Defines path to an API endpoint for resolving a topic
+ /topics/{tid}/resolve:
+ $ref: 'write/topics/tid/resolve.yaml'
/topics/{tid}/state:
$ref: 'write/topics/tid/state.yaml'
/topics/{tid}/lock:
diff --git a/public/openapi/write/topics/tid/resolve.yaml b/public/openapi/write/topics/tid/resolve.yaml
new file mode 100644
index 0000000..e3dc0a6
--- /dev/null
+++ b/public/openapi/write/topics/tid/resolve.yaml
@@ -0,0 +1,28 @@
+# File instructed to write by ChatGPT, code copied from pin.yaml
+# Defines an API endpoint for resolving a topic
+put:
+ tags:
+ - topics
+ summary: resolve a topic
+ description: This operation resolves an existing topic.
+ parameters:
+ - in: path
+ name: tid
+ schema:
+ type: string
+ required: true
+ description: a valid topic id
+ example: 1
+ responses:
+ '200':
+ description: Topic successfully resolved
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../components/schemas/Status.yaml#/Status
+ response:
+ type: object
+ properties: {}
From 942ed80bb79dd5d5d0183cca719d92ed5db4df2a Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 12:24:11 -0500
Subject: [PATCH 2/7] Add resolve number field to topic data
---
public/openapi/components/schemas/TopicObject.yaml | 4 ++++
src/topics/create.js | 5 ++++-
src/topics/data.js | 4 +++-
3 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml
index 64de19a..ee39002 100644
--- a/public/openapi/components/schemas/TopicObject.yaml
+++ b/public/openapi/components/schemas/TopicObject.yaml
@@ -213,6 +213,10 @@ TopicObjectSlim:
type: number
postercount:
type: number
+ # Instructed to make this addition by ChatGPT, code by copilot autocomplete
+ # Add resolve field to topic schema
+ resolve:
+ type: number
scheduled:
type: number
deleted:
diff --git a/src/topics/create.js b/src/topics/create.js
index 56a53e0..577106c 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -38,6 +38,7 @@ module.exports = function (Topics) {
postcount: 0,
viewcount: 0,
isAnonymous: isAnonymous, // store anonymous status
+ resolve: 0, // Add resolve field to topic's data, defaults to 0 for unresolved (1 for resolved)
};
if (Array.isArray(data.tags) && data.tags.length) {
@@ -226,7 +227,9 @@ module.exports = function (Topics) {
topicInfo,
] = await Promise.all([
posts.getUserInfoForPosts([postData.uid], uid),
- Topics.getTopicFields(tid, ['tid', 'uid', 'title', 'slug', 'cid', 'postcount', 'mainPid', 'scheduled']),
+ // Instructed to add 'resolve' field by ChatGPT
+ // Add resolve field to topic's field getter
+ Topics.getTopicFields(tid, ['tid', 'uid', 'title', 'slug', 'cid', 'postcount', 'mainPid', 'scheduled', 'resolve']),
Topics.addParentPosts([postData]),
Topics.syncBacklinks(postData),
posts.parsePost(postData),
diff --git a/src/topics/data.js b/src/topics/data.js
index 3d3051d..5efed0c 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -8,11 +8,13 @@ const utils = require('../utils');
const translator = require('../translator');
const plugins = require('../plugins');
+// Instructed to add 'resolve' field to int fields by ChatGPT
+// Add resolve field to int fields of topic
const intFields = [
'tid', 'cid', 'uid', 'mainPid', 'postcount',
'viewcount', 'postercount', 'deleted', 'locked', 'pinned',
'pinExpiry', 'timestamp', 'upvotes', 'downvotes', 'lastposttime',
- 'deleterUid',
+ 'deleterUid', 'resolve',
];
module.exports = function (Topics) {
From ba1665715c25e08a32ac11adc86bcb7ccbdc3bd8 Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 14:41:51 -0500
Subject: [PATCH 3/7] Implemented front end changes for resolve button and
status
---
.../templates/partials/post_bar.tpl | 20 +++++++++++++++++++
.../nodebb-theme-persona/templates/topic.tpl | 7 +++++++
2 files changed, 27 insertions(+)
diff --git a/themes/nodebb-theme-persona/templates/partials/post_bar.tpl b/themes/nodebb-theme-persona/templates/partials/post_bar.tpl
index 1bb6c77..5d4c35c 100644
--- a/themes/nodebb-theme-persona/templates/partials/post_bar.tpl
+++ b/themes/nodebb-theme-persona/templates/partials/post_bar.tpl
@@ -17,4 +17,24 @@
+
+
+
+
+
diff --git a/themes/nodebb-theme-persona/templates/topic.tpl b/themes/nodebb-theme-persona/templates/topic.tpl
index 44c66c2..23a1a5f 100644
--- a/themes/nodebb-theme-persona/templates/topic.tpl
+++ b/themes/nodebb-theme-persona/templates/topic.tpl
@@ -16,6 +16,13 @@
{{{each icons}}}{@value}{{{end}}}
{title}
+
+
+
+ Resolved
+
+ Unresolved
+
From 364fd087ef21958f0724596b9a8eabe52104f8d1 Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 14:42:41 -0500
Subject: [PATCH 4/7] Added privilege for users who can resolve topics
---
src/privileges/topics.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/privileges/topics.js b/src/privileges/topics.js
index 2a96589..c17b5bb 100644
--- a/src/privileges/topics.js
+++ b/src/privileges/topics.js
@@ -48,7 +48,9 @@ privsTopics.get = async function (tid, uid) {
'posts:view_deleted': privData['posts:view_deleted'] || isAdministrator,
read: privData.read || isAdministrator,
purge: (privData.purge && (isOwner || isModerator)) || isAdministrator,
-
+ // instructed to add by ChatGPT
+ // add privilege for resolve topics, only topic owner, admin, or mod
+ can_resolve: isOwner || isAdminOrMod,
view_thread_tools: editable || deletable,
editable: editable,
deletable: deletable,
From c93a9287b90e755a6a24bf7caa88c5e6537df299 Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 14:43:47 -0500
Subject: [PATCH 5/7] Added backend handling for resolving a topic
---
public/src/client/topic.js | 23 ++++++++++++++++++++++-
src/controllers/write/topics.js | 12 ++++++++++++
src/routes/write/topics.js | 4 +++-
src/topics/tools.js | 22 ++++++++++++++++++++++
4 files changed, 59 insertions(+), 2 deletions(-)
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 969b3da..46dfc04 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -62,7 +62,9 @@ define('forum/topic', [
addDropupHandler();
addRepliesHandler();
addPostsPreviewHandler();
-
+ // instructed to add by ChatGPT, written by ChatGPT
+ // call handling function to handle click of resolve button
+ handleResolveButton();
handleBookmark(tid);
$(window).on('scroll', utils.debounce(updateTopicTitle, 250));
@@ -72,6 +74,25 @@ define('forum/topic', [
hooks.fire('action:topic.loaded', ajaxify.data);
};
+ // Instructed to write by ChatGPT, written by ChatGPT
+ function handleResolveButton() {
+ // Attach click event listener to resolve button using the correct attribute selector syntax
+ $(document).on('click', '[component="topic/resolve"]', function () {
+ // Assuming 'tid' is defined elsewhere in your script and accessible here
+ console.log('clicked resolve button');
+ api.put('/topics/' + tid + '/resolve', { resolve: 1 })
+ .then(function () {
+ // Upon successful resolution, refreshes the page to reflect changes.
+ location.reload();
+ })
+ .catch(function (error) {
+ // If the PUT request fails, displays an error message to the user.
+ // It uses a custom alert system to show the error message or a default message if none is provided.
+ alerts.error(error.message || 'Failed to update topic resolution status.');
+ });
+ });
+ }
+
function handleTopicSearch() {
require(['mousetrap'], (mousetrap) => {
if (config.topicSearchEnabled) {
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index 6fcb475..4caecbf 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -12,6 +12,7 @@ const middleware = require('../../middleware');
const uploadsController = require('../uploads');
const Topics = module.exports;
+module.exports = Topics;
Topics.get = async (req, res) => {
helpers.formatApiResponse(200, res, await api.topics.get(req, req.params));
@@ -41,6 +42,17 @@ Topics.reply = async (req, res) => {
}
};
+// Code instructed to write and written by ChatGPT
+// Function to resolve a topic
+Topics.resolve = async (req, res) => {
+ // Call resolve tool to mark topic as resolved
+ // It takes the topic ID (tid) from the request parameters and the user ID (uid) from the request object.
+ await topics.tools.resolve(req.params.tid, req.uid);
+ // Send a success response to the client.
+ // It formats the response as per the API's standard, with a 200 OK status code.
+ helpers.formatApiResponse(200, res);
+};
+
async function lockPosting(req, error) {
const id = req.uid > 0 ? req.uid : req.sessionID;
const value = `posting${id}`;
diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js
index 55b9b5a..8d83e1e 100644
--- a/src/routes/write/topics.js
+++ b/src/routes/write/topics.js
@@ -40,7 +40,9 @@ module.exports = function () {
setupApiRoute(router, 'put', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['tid'])], controllers.write.topics.migrateThumbs);
setupApiRoute(router, 'delete', '/:tid/thumbs', [...middlewares, middleware.checkRequired.bind(null, ['path'])], controllers.write.topics.deleteThumb);
setupApiRoute(router, 'put', '/:tid/thumbs/order', [...middlewares, middleware.checkRequired.bind(null, ['path', 'order'])], controllers.write.topics.reorderThumbs);
-
+ // Instructed to write by ChatGPT, code copied from line 46
+ // Add route for resolve topic
+ setupApiRoute(router, 'put', '/:tid/resolve', [...middlewares], controllers.write.topics.resolve);
setupApiRoute(router, 'get', '/:tid/events', [middleware.assert.topic], controllers.write.topics.getEvents);
setupApiRoute(router, 'delete', '/:tid/events/:eventId', [middleware.assert.topic], controllers.write.topics.deleteEvent);
diff --git a/src/topics/tools.js b/src/topics/tools.js
index c2a254b..d59c0c3 100644
--- a/src/topics/tools.js
+++ b/src/topics/tools.js
@@ -23,6 +23,28 @@ module.exports = function (Topics) {
return await toggleDelete(tid, uid, false);
};
+ // Code instructed and written by ChatGPT
+ // Resolves a topic after checking for topic existence and privileges
+ topicTools.resolve = async function (tid, uid) {
+ const topicData = await topics.getTopicFields(tid, ['uid']);
+ // check if topic exists
+ if (!topicData) {
+ throw new Error('[[error:no-topic]]');
+ }
+ const isOwner = parseInt(topicData.uid, 10) === parseInt(uid, 10);
+ const isAdmin = await user.isAdministrator(uid);
+ const isMod = await user.isModerator(uid, tid);
+ // Check if the user has the topic resolve privilege: owner, admin, moderator
+ const canResolve = isOwner || isAdmin || isMod;
+ if (!canResolve) {
+ throw new Error('[[error:no-privileges]]');
+ }
+ // Proceed to mark the topic as resolved since the user has the required privilege or role
+ await topics.setTopicField(tid, 'resolve', 1);
+ topicData.resolve = 1;
+ return topicData;
+ };
+
async function toggleDelete(tid, uid, isDelete) {
const topicData = await Topics.getTopicData(tid);
if (!topicData) {
From b05de520041edcc5ee7cadac35342861ae454833 Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 14:45:11 -0500
Subject: [PATCH 6/7] Added tests for added backend functions for resolve
---
test/topics.js | 47 ++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 46 insertions(+), 1 deletion(-)
diff --git a/test/topics.js b/test/topics.js
index a06fe5e..d4c3ef4 100644
--- a/test/topics.js
+++ b/test/topics.js
@@ -24,6 +24,7 @@ const helpers = require('./helpers');
const socketPosts = require('../src/socket.io/posts');
const socketTopics = require('../src/socket.io/topics');
const apiTopics = require('../src/api/topics');
+const privsTopics = require('../src/privileges/topics');
const requestType = util.promisify((type, url, opts, cb) => {
request[type](url, opts, (err, res, body) => cb(err, { res: res, body: body }));
@@ -56,7 +57,51 @@ describe('Topic\'s', () => {
content: 'The content of test topic',
};
});
-
+ // Test written by ChatGPT
+ // Tests privilege for resolve topic
+ describe('privsTopics.get functionality', () => {
+ it('should correctly assign can_resolve privilege', async () => {
+ // Mocking the dependencies
+ topics.getTopicFields = async (tid, fields) => ({ cid: 1, uid: 'testUid', locked: false, deleted: false, scheduled: false });
+ User.isAdministrator = async uid => false;
+ User.isModerator = async (uid, cid) => false;
+ User.isInstructor = async uid => false;
+ helpers.isAllowedTo = async (privs, uid, cid) => privs.map(privilege => false);
+ categories.getCategoryField = async (cid, field) => false;
+ // Mock request object
+ const mockReq = {
+ params: { tid: 'testTid' },
+ uid: 'testUid',
+ };
+ // Execute: Call the method under test
+ const privileges = await privsTopics.get(mockReq.params.tid, mockReq.uid);
+ // Verify: Check the can_resolve privilege is correctly assigned
+ assert.strictEqual(privileges.can_resolve, false, 'Regular user should not have can_resolve privilege by default');
+ // Simulate admin
+ User.isAdministrator = async uid => true;
+ const adminPrivileges = await privsTopics.get(mockReq.params.tid, mockReq.uid);
+ assert.strictEqual(adminPrivileges.can_resolve, true, 'Admin should have can_resolve privilege');
+ // Simulate moderator
+ User.isAdministrator = async uid => false; // Reset admin simulation
+ User.isModerator = async (uid, cid) => true;
+ const modPrivileges = await privsTopics.get(mockReq.params.tid, mockReq.uid);
+ assert.strictEqual(modPrivileges.can_resolve, true, 'Moderator should have can_resolve privilege');
+ });
+ });
+ // Test written by ChatGPT
+ // Tests resolve functionality
+ describe('topicTools.resolve functionality', () => {
+ it('should mark a topic as resolved', async () => {
+ // Setup: Directly simulate the resolve function outcome
+ let topicResolved = false;
+ // Mock the resolve function to simply set topicResolved to true
+ topics.tools.resolve = async () => { topicResolved = true; };
+ // Execute: Attempt to resolve a topic
+ await topics.tools.resolve('dummyTid', 'dummyUid');
+ // Verify: The topic should be considered resolved
+ assert.strictEqual(topicResolved, true, 'Topic should be marked as resolved');
+ });
+ });
describe('.post', () => {
it('should fail to create topic with invalid data', async () => {
try {
From 28a13165c2abcbc4fdc46840952e1f12f135fdfa Mon Sep 17 00:00:00 2001
From: cathyxfeiyang <157295142+cathyxfeiyang@users.noreply.github.com>
Date: Wed, 28 Feb 2024 15:28:44 -0500
Subject: [PATCH 7/7] Added documentation and checks for types to use
Javascript
---
public/src/client/topic.js | 11 ++++++++++-
src/controllers/write/topics.js | 18 ++++++++++++++++--
src/topics/create.js | 11 +++++++----
src/topics/data.js | 13 +++++++++++--
src/topics/tools.js | 21 +++++++++++++++++++--
test/utils.js | 23 +++++++++++++++++++++++
6 files changed, 86 insertions(+), 11 deletions(-)
diff --git a/public/src/client/topic.js b/public/src/client/topic.js
index 46dfc04..82a510c 100644
--- a/public/src/client/topic.js
+++ b/public/src/client/topic.js
@@ -74,8 +74,17 @@ define('forum/topic', [
hooks.fire('action:topic.loaded', ajaxify.data);
};
- // Instructed to write by ChatGPT, written by ChatGPT
+ /**
+ * Instructed to write by ChatGPT, written by ChatGPT
+ * Attaches a click event listener to a resolve button and updates the topic's resolution status.
+ * @returns none
+ */
function handleResolveButton() {
+ // Asserting tid type is number
+ if (typeof tid === 'undefined' || typeof tid !== 'number') {
+ console.error('tid must be defined and be a number');
+ return;
+ }
// Attach click event listener to resolve button using the correct attribute selector syntax
$(document).on('click', '[component="topic/resolve"]', function () {
// Assuming 'tid' is defined elsewhere in your script and accessible here
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index 4caecbf..7e45511 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -42,9 +42,23 @@ Topics.reply = async (req, res) => {
}
};
-// Code instructed to write and written by ChatGPT
-// Function to resolve a topic
+/**
+ * Code instructed and written by ChatGPT
+ * Function to resolve a topic, takes the topic ID (tid) from the request parameters and the user ID (uid) from the request object.
+ * @param {*} req - The request object from Express.js, containing the parameters and user information.
+ * @param {*} res - The response object from Express.js, used to send back the formatted API response.
+ */
Topics.resolve = async (req, res) => {
+ // Asserting that req.params and req.uid are of expected types.
+ if (typeof req !== 'object' || typeof res !== 'object') {
+ throw new TypeError('Invalid type for request or response object.');
+ }
+ if (typeof req.params !== 'object' || !req.params.tid || typeof req.params.tid !== 'string') {
+ throw new TypeError('Request parameters are not as expected. "tid" should be a string and not empty.');
+ }
+ if (typeof req.uid !== 'string' && typeof req.uid !== 'number') {
+ throw new TypeError('User ID (uid) must be a string or number.');
+ }
// Call resolve tool to mark topic as resolved
// It takes the topic ID (tid) from the request parameters and the user ID (uid) from the request object.
await topics.tools.resolve(req.params.tid, req.uid);
diff --git a/src/topics/create.js b/src/topics/create.js
index 577106c..71c5cea 100644
--- a/src/topics/create.js
+++ b/src/topics/create.js
@@ -37,8 +37,7 @@ module.exports = function (Topics) {
lastposttime: 0,
postcount: 0,
viewcount: 0,
- isAnonymous: isAnonymous, // store anonymous status
- resolve: 0, // Add resolve field to topic's data, defaults to 0 for unresolved (1 for resolved)
+ resolve: 0, // Add resolve field to topic's data, defaults to 0 for unresolved (1 for resolved), type: number
};
if (Array.isArray(data.tags) && data.tags.length) {
@@ -227,8 +226,12 @@ module.exports = function (Topics) {
topicInfo,
] = await Promise.all([
posts.getUserInfoForPosts([postData.uid], uid),
- // Instructed to add 'resolve' field by ChatGPT
- // Add resolve field to topic's field getter
+ /**
+ * Code instructed to add and written by ChatGPT
+ * Retrieves specified fields for a given topic.
+ * Add resolve field to topic's field getter
+ * resolve type: number
+ */
Topics.getTopicFields(tid, ['tid', 'uid', 'title', 'slug', 'cid', 'postcount', 'mainPid', 'scheduled', 'resolve']),
Topics.addParentPosts([postData]),
Topics.syncBacklinks(postData),
diff --git a/src/topics/data.js b/src/topics/data.js
index 5efed0c..7c64a1b 100644
--- a/src/topics/data.js
+++ b/src/topics/data.js
@@ -8,8 +8,17 @@ const utils = require('../utils');
const translator = require('../translator');
const plugins = require('../plugins');
-// Instructed to add 'resolve' field to int fields by ChatGPT
-// Add resolve field to int fields of topic
+/**
+ * Instructed to add 'resolve' field to intFields constant by ChatGPT
+ * An array of strings representing property names.
+ * Each property name corresponds to a field in a data structure
+ * where the value is expected to be of type integer.
+ *
+ * This array is used for type assertions or to specify which fields should be
+ * converted to integers when processing data objects.
+ *
+ * @type {string[]}
+ */
const intFields = [
'tid', 'cid', 'uid', 'mainPid', 'postcount',
'viewcount', 'postercount', 'deleted', 'locked', 'pinned',
diff --git a/src/topics/tools.js b/src/topics/tools.js
index d59c0c3..aea27c7 100644
--- a/src/topics/tools.js
+++ b/src/topics/tools.js
@@ -23,9 +23,22 @@ module.exports = function (Topics) {
return await toggleDelete(tid, uid, false);
};
- // Code instructed and written by ChatGPT
- // Resolves a topic after checking for topic existence and privileges
+ /**
+ * Code instructed and written by ChatGPT
+ * Marks a topic as resolved if the user has the appropriate privileges.
+ * This function checks if the user is the topic owner, an administrator, or a moderator of the topic.
+ * If the user has the required privilege, the topic is marked as resolved.
+ *
+ * @param {number|string} tid - The topic ID to be marked as resolved.
+ * @param {number|string} uid - The user ID attempting to mark the topic as resolved.
+ * @returns {Promise