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/public/openapi/write.yaml b/public/openapi/write.yaml index 6f55dbc..7ff5987 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -128,6 +128,10 @@ paths: $ref: 'write/topics/tid/thumbs/order.yaml' /topics/{tid}/events: $ref: 'write/topics/tid/events.yaml' + # Instructed to add by ChatGPT, code written by copilot autocomplete + # Set up path to resolve topic endpoint + /topics/{tid}/resolve: + $ref: 'write/topics/tid/resolve.yaml' /topics/{tid}/events/{eventId}: $ref: 'write/topics/tid/events/eventId.yaml' /posts/{pid}: diff --git a/public/openapi/write/topics/tid/resolve.yaml b/public/openapi/write/topics/tid/resolve.yaml new file mode 100644 index 0000000..acac2fa --- /dev/null +++ b/public/openapi/write/topics/tid/resolve.yaml @@ -0,0 +1,46 @@ +# File instructed to write by ChatGPT, code written by ChatGPT +# defines an API endpoint for resolving a topic +put: + tags: + - topics + summary: Resolve a topic + description: Marks a topic as resolved. + operationId: resolveTopic + parameters: + - in: path + name: tid + required: true + schema: + type: number + description: The topic identifier that is to be marked as resolved. + requestBody: + content: + application/json: + schema: + type: object + properties: + resolved: + type: boolean + description: A boolean flag to indicate the resolve status. True to mark the topic as resolved, false to mark it as unresolved. + required: + - resolved + responses: + '200': + description: Topic successfully resolved + content: + application/json: + schema: + type: object + properties: + success: + type: boolean + message: + type: string + '400': + description: Bad request. Possible reasons: missing parameters or incorrect values. + '404': + description: Topic not found. + '401': + description: Unauthorized. User is not logged in or lacks the necessary permissions to resolve the topic. + '500': + description: Internal server error. An error occurred while attempting to resolve the topic. diff --git a/public/src/client/topic.js b/public/src/client/topic.js index 969b3da..23e90c1 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -63,6 +63,10 @@ define('forum/topic', [ 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 +76,28 @@ define('forum/topic', [ hooks.fire('action:topic.loaded', ajaxify.data); }; + // instructed to add by ChatGPT, written by ChatGPT + // handle click events for resolving topics + function handleResolveButton() { + // attach event listener to the resolve button + $(document).on('click', '[component="topic/resolve"]', function() { + // Sends a PUT request to the server to mark a topic, identified by 'tid', as resolved. + // The resolve parameter is set to 1, indicating the action to resolve the topic. + api.put('/topics/' + tid + '/resolve', { resolve: 1 }) + .then(function(response) { + // 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..e6e8571 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -31,6 +31,18 @@ Topics.create = 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); +}; + + Topics.reply = async (req, res) => { const id = await lockPosting(req, '[[error:already-posting]]'); try { diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 2a96589..1a7ee80 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -48,7 +48,8 @@ privsTopics.get = async function (tid, uid) { 'posts:view_deleted': privData['posts:view_deleted'] || isAdministrator, read: privData.read || isAdministrator, purge: (privData.purge && (isOwner || isModerator)) || isAdministrator, - + // add privilege for resolve topics, only topic owner, admin, or mod + can_resolve: isOwner || isAdminOrMod, view_thread_tools: editable || deletable, editable: editable, deletable: deletable, diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index 55b9b5a..2ad3647 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -17,7 +17,7 @@ module.exports = function () { setupApiRoute(router, 'get', '/:tid', [], controllers.write.topics.get); setupApiRoute(router, 'post', '/:tid', [middleware.checkRequired.bind(null, ['content']), middleware.assert.topic], controllers.write.topics.reply); setupApiRoute(router, 'delete', '/:tid', [...middlewares], controllers.write.topics.purge); - + setupApiRoute(router, 'put', '/:tid/resolve', [...middlewares], controllers.write.topics.resolve); setupApiRoute(router, 'put', '/:tid/state', [...middlewares], controllers.write.topics.restore); setupApiRoute(router, 'delete', '/:tid/state', [...middlewares], controllers.write.topics.delete); diff --git a/src/topics/create.js b/src/topics/create.js index 56a53e0..a414225 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', 'resolve', 'title', 'slug', 'cid', 'postcount', 'mainPid', 'scheduled']), Topics.addParentPosts([postData]), Topics.syncBacklinks(postData), posts.parsePost(postData), diff --git a/src/topics/data.js b/src/topics/data.js index 3d3051d..b9c4387 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 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) { diff --git a/src/topics/tools.js b/src/topics/tools.js index c2a254b..413bfc5 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); // Assuming tid can be used to determine the forum/category for moderator check + // Check if the user has the 'topics:can_resolve' privilege on the topic, or if they are the owner, an admin, or a 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) { diff --git a/themes/nodebb-theme-persona/templates/partials/post_bar.tpl b/themes/nodebb-theme-persona/templates/partials/post_bar.tpl index 1bb6c77..c4ab9eb 100644 --- a/themes/nodebb-theme-persona/templates/partials/post_bar.tpl +++ b/themes/nodebb-theme-persona/templates/partials/post_bar.tpl @@ -17,4 +17,26 @@ + + + + + + + diff --git a/themes/nodebb-theme-persona/templates/topic.tpl b/themes/nodebb-theme-persona/templates/topic.tpl index 44c66c2..ef3935a 100644 --- a/themes/nodebb-theme-persona/templates/topic.tpl +++ b/themes/nodebb-theme-persona/templates/topic.tpl @@ -16,6 +16,12 @@ {{{each icons}}}{@value}{{{end}}} {title} + + + Resolved + + Unresolved +