diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000..f418d54023 Binary files /dev/null and b/dump.rdb differ diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index d849187bae..b487f76d41 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -216,6 +216,7 @@ "cant-flag-privileged": "You are not allowed to flag the profiles or content of privileged users (moderators/global moderators/admins)", "cant-locate-flag-report": "Cannot locate flag report", "self-vote": "You cannot vote on your own post", + "self-endorse": "You cannot endorse your own topic", "too-many-upvotes-today": "You can only upvote %1 times a day", "too-many-upvotes-today-user": "You can only upvote a user %1 times a day", "too-many-downvotes-today": "You can only downvote %1 times a day", diff --git a/public/language/en-GB/topic.json b/public/language/en-GB/topic.json index 9027774603..1fac2aaeb8 100644 --- a/public/language/en-GB/topic.json +++ b/public/language/en-GB/topic.json @@ -35,6 +35,7 @@ "share": "Share", "tools": "Tools", "locked": "Locked", + "endorsed": "Endorsed", "pinned": "Pinned", "pinned-with-expiry": "Pinned until %1", "scheduled": "Scheduled", @@ -55,6 +56,10 @@ "user-locked-topic-on": "%1 locked this topic on %2", "user-unlocked-topic-ago": "%1 unlocked this topic %2", "user-unlocked-topic-on": "%1 unlocked this topic on %2", + "user-endorsed-topic-on": "%1 endorsed this topic on %2", + "user-endorsed-topic-ago": "%1 endorsed this topic %2", + "user-unendorsed-topic-on": "%1 unendorsed this topic on %2", + "user-unendorsed-topic-ago": "%1 unendorsed this topic %2", "user-pinned-topic-ago": "%1 pinned this topic %2", "user-pinned-topic-on": "%1 pinned this topic on %2", "user-unpinned-topic-ago": "%1 unpinned this topic %2", @@ -112,6 +117,8 @@ "thread-tools.unpin": "Unpin Topic", "thread-tools.lock": "Lock Topic", "thread-tools.unlock": "Unlock Topic", + "thread-tools.endorse": "Endorse Topic", + "thread-tools.unendorse": "Unendorse Topic", "thread-tools.move": "Move Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", diff --git a/public/language/en-US/error.json b/public/language/en-US/error.json index f12e15c94f..f6a42984d2 100644 --- a/public/language/en-US/error.json +++ b/public/language/en-US/error.json @@ -189,6 +189,7 @@ "cant-flag-privileged": "You are not allowed to flag the profiles or content of privileged users (moderators/global moderators/admins)", "cant-locate-flag-report": "Cannot locate flag report", "self-vote": "You cannot vote on your own post", + "self-endorse": "You cannot endorse your own topic", "too-many-upvotes-today": "You can only upvote %1 times a day", "too-many-upvotes-today-user": "You can only upvote a user %1 times a day", "too-many-downvotes-today": "You can only downvote %1 times a day", @@ -239,4 +240,4 @@ "api.501": "The route you are trying to call is not implemented yet, please try again tomorrow", "api.503": "The route you are trying to call is not currently available due to a server configuration", "api.reauth-required": "The resource you are trying to access requires (re-)authentication." -} \ No newline at end of file +} diff --git a/public/language/en-US/topic.json b/public/language/en-US/topic.json index bf9c68ef85..c43e4196e6 100644 --- a/public/language/en-US/topic.json +++ b/public/language/en-US/topic.json @@ -32,6 +32,7 @@ "share": "Share", "tools": "Tools", "locked": "Locked", + "endorsed": "Endorsed", "pinned": "Pinned", "pinned-with-expiry": "Pinned until %1", "scheduled": "Scheduled", @@ -49,7 +50,10 @@ "user-locked-topic-ago": "%1 locked this topic %2", "user-locked-topic-on": "%1 locked this topic on %2", "user-unlocked-topic-ago": "%1 unlocked this topic %2", - "user-unlocked-topic-on": "%1 unlocked this topic on %2", + "user-endorsed-topic-on": "%1 endorsed this topic on %2", + "user-endorsed-topic-ago": "%1 endorsed this topic %2", + "user-unendorsed-topic-on": "%1 unendorsed this topic on %2", + "user-unendorsed-topic-ago": "%1 unendorsed this topic %2", "user-pinned-topic-ago": "%1 pinned this topic %2", "user-pinned-topic-on": "%1 pinned this topic on %2", "user-unpinned-topic-ago": "%1 unpinned this topic %2", @@ -99,6 +103,8 @@ "thread-tools.unpin": "Unpin Topic", "thread-tools.lock": "Lock Topic", "thread-tools.unlock": "Unlock Topic", + "thread-tools.endorse": "Endorse Topic", + "thread-tools.unendorse": "Unendorse Topic", "thread-tools.move": "Move Topic", "thread-tools.move-posts": "Move Posts", "thread-tools.move-all": "Move All", @@ -217,4 +223,4 @@ "post-tools": "Post tools", "unread-posts-link": "Unread posts link", "thumb-image": "Topic thumbnail image" -} \ No newline at end of file +} diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml index ee34558ffc..306f45b671 100644 --- a/public/openapi/components/schemas/TopicObject.yaml +++ b/public/openapi/components/schemas/TopicObject.yaml @@ -225,6 +225,9 @@ TopicObjectSlim: type: string locked: type: number + endorsed: + type: number + description: Whether or not this particular topic is endorsed by admin pinned: type: number description: Whether or not this particular topic is pinned to the top of the @@ -283,4 +286,4 @@ TopicObjectSlim: type: number description: The number of thumbnails associated with this topic required: - - tid \ No newline at end of file + - tid diff --git a/public/openapi/read/admin/extend/plugins.yaml b/public/openapi/read/admin/extend/plugins.yaml index cd36a779fa..b1ebe79e6d 100644 --- a/public/openapi/read/admin/extend/plugins.yaml +++ b/public/openapi/read/admin/extend/plugins.yaml @@ -205,4 +205,4 @@ get: type: number version: type: string - - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file + - $ref: ../../../components/schemas/CommonProps.yaml#/CommonProps diff --git a/public/openapi/read/tags/tag.yaml b/public/openapi/read/tags/tag.yaml index c9a49c5160..ca263ae537 100644 --- a/public/openapi/read/tags/tag.yaml +++ b/public/openapi/read/tags/tag.yaml @@ -66,6 +66,8 @@ get: type: number locked: type: number + endorsed: + type: number pinned: type: number description: Whether or not this particular topic is pinned to the top of the @@ -263,4 +265,4 @@ get: - categories - $ref: ../../components/schemas/Pagination.yaml#/Pagination - $ref: ../../components/schemas/Breadcrumbs.yaml#/Breadcrumbs - - $ref: ../../components/schemas/CommonProps.yaml#/CommonProps \ No newline at end of file + - $ref: ../../components/schemas/CommonProps.yaml#/CommonProps diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index c59b9bce29..67f629dadc 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -144,6 +144,8 @@ paths: $ref: 'write/topics/tid/state.yaml' /topics/{tid}/lock: $ref: 'write/topics/tid/lock.yaml' + /topics/{tid}/endorse: + $ref: 'write/topics/tid/endorse.yaml' /topics/{tid}/pin: $ref: 'write/topics/tid/pin.yaml' /topics/{tid}/follow: @@ -261,4 +263,4 @@ paths: /files/: $ref: 'write/files.yaml' /files/folder: - $ref: 'write/files/folder.yaml' \ No newline at end of file + $ref: 'write/files/folder.yaml' diff --git a/public/openapi/write/topics/tid/endorse.yaml b/public/openapi/write/topics/tid/endorse.yaml new file mode 100644 index 0000000000..3c751cb393 --- /dev/null +++ b/public/openapi/write/topics/tid/endorse.yaml @@ -0,0 +1,53 @@ +put: + tags: + - topics + summary: endorse a topic + description: This operation endorses 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 endorsed + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} +delete: + tags: + - topics + summary: unendorsed a topic + description: This operation unendorses a topic. + parameters: + - in: path + name: tid + schema: + type: string + required: true + description: a valid topic id + example: 1 + responses: + '200': + description: Topic successfully unendorsed + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} + diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index b372d7a2f6..5358698216 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -134,6 +134,10 @@ define('forum/category/tools', [ socket.on('event:topic_purged', onTopicPurged); socket.on('event:topic_locked', setLockedState); socket.on('event:topic_unlocked', setLockedState); + // Comment @YG + // Listening for the event to be triggered, and setting state of endorsement accordingly. + // socket.on('event:topic_endorsed', setEndorsedState); + // socket.on('event:topic_unendorsed', setEndorsedState); socket.on('event:topic_pinned', setPinnedState); socket.on('event:topic_unpinned', setPinnedState); socket.on('event:topic_moved', onTopicMoved); @@ -180,6 +184,8 @@ define('forum/category/tools', [ socket.removeListener('event:topic_purged', onTopicPurged); socket.removeListener('event:topic_locked', setLockedState); socket.removeListener('event:topic_unlocked', setLockedState); + // socket.removeListener('event:topic_endorsed', setEndorsedState); + // socket.removeListener('event:topic_unendorsed', setEndorsedState); socket.removeListener('event:topic_pinned', setPinnedState); socket.removeListener('event:topic_unpinned', setPinnedState); socket.removeListener('event:topic_moved', onTopicMoved); @@ -211,6 +217,7 @@ define('forum/category/tools', [ const areAllDeleted = areAll(isTopicDeleted, tids); const isAnyPinned = isAny(isTopicPinned, tids); const isAnyLocked = isAny(isTopicLocked, tids); + // const isAnyEndorsed = isAny(isTopicEndorsed, tids); const isAnyScheduled = isAny(isTopicScheduled, tids); const areAllScheduled = areAll(isTopicScheduled, tids); @@ -221,6 +228,9 @@ define('forum/category/tools', [ components.get('topic/lock').toggleClass('hidden', isAnyLocked); components.get('topic/unlock').toggleClass('hidden', !isAnyLocked); + // components.get('topic/endorse').toggleClass('hidden', isAnyEndorsed); + // components.get('topic/unendorse').toggleClass('hidden', !isAnyEndorsed); + components.get('topic/pin').toggleClass('hidden', areAllScheduled || isAnyPinned); components.get('topic/unpin').toggleClass('hidden', areAllScheduled || !isAnyPinned); diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index e091dd69c8..033b707b42 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -26,6 +26,12 @@ define('forum/topic/events', [ 'event:topic_locked': threadTools.setLockedState, 'event:topic_unlocked': threadTools.setLockedState, + // Comment @YG + // Define endorsed event. + // TODO: why do we need to define events? + 'event:topic_endorsed': threadTools.setEndorsedState, + 'event:topic_unendorsed': threadTools.setEndorsedState, + 'event:topic_pinned': threadTools.setPinnedState, 'event:topic_unpinned': threadTools.setPinnedState, diff --git a/public/src/client/topic/threadTools.js b/public/src/client/topic/threadTools.js index a66b293a73..79fcc049f5 100644 --- a/public/src/client/topic/threadTools.js +++ b/public/src/client/topic/threadTools.js @@ -51,6 +51,18 @@ define('forum/topic/threadTools', [ return false; }); + // Comment @YG + // Handles the front-end logic that establishes connection with APIs through topicCommand() + topicContainer.on('click', '[component="topic/endorse"]', function () { + topicCommand('put', '/endorse', 'endorse'); + return false; + }); + + topicContainer.on('click', '[component="topic/unendorse"]', function () { + topicCommand('del', '/endorse', 'unendorse'); + return false; + }); + topicContainer.on('click', '[component="topic/pin"]', function () { topicCommand('put', '/pin', 'pin'); return false; @@ -313,13 +325,36 @@ define('forum/topic/threadTools', [ threadEl.find('[component="post"][data-uid="' + app.user.uid + '"].deleted [component="post/tools"]').toggleClass('hidden', isLocked); + // Comment @YG + // The $ is an alias for the jQuery() function to manipulate HTML element and data. + // This operation looks for element with the component and add hidden if data.isLocked. $('[component="topic/labels"] [component="topic/locked"]').toggleClass('hidden', !data.isLocked); + // Finds dropdown-menu elem inside post/tools elem and set its HTML to be empty. $('[component="post/tools"] .dropdown-menu').html(''); ajaxify.data.locked = data.isLocked; posts.addTopicEvents(data.events); }; + // Comment @YG + // Event handler that responds to API calls triggered by clicking on the topicContainer. + ThreadTools.setEndorsedState = function (data) { + const threadEl = components.get('topic'); + if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) { + return; + } + + components.get('topic/endorse').toggleClass('hidden', data.isEndorsed).parent().attr('hidden', data.isEndorsed ? '' : null); + components.get('topic/unendorse').toggleClass('hidden', !data.isEndorsed).parent().attr('hidden', !data.isEndorsed ? '' : null); + + $('[component="topic/labels"] [component="topic/locked"]').toggleClass('hidden', !data.isLocked); + $('[component="topic/endorsed"]').toggleClass('hidden', !data.isEndorsed); + + ajaxify.data.endorsed = data.isEndorsed; + posts.addTopicEvents(data.events); + }; + + ThreadTools.setDeleteState = function (data) { const threadEl = components.get('topic'); if (parseInt(data.tid, 10) !== parseInt(threadEl.attr('data-tid'), 10)) { diff --git a/src/api/helpers.js b/src/api/helpers.js index ef7c062482..3e8704cb0c 100644 --- a/src/api/helpers.js +++ b/src/api/helpers.js @@ -66,6 +66,7 @@ exports.doTopicAction = async function (action, event, caller, { tids }) { await Promise.all(tids.map(async (tid) => { const title = await topics.getTopicField(tid, 'title'); + // A generic call that handles toggling actions. const data = await topics.tools[action](tid, caller.uid); const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids); socketHelpers.emitToUids(event, data, notifyUids); diff --git a/src/api/topics.js b/src/api/topics.js index 7a6cabf966..f4f344cee7 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -161,6 +161,20 @@ topicsAPI.unlock = async function (caller, data) { }); }; +// Comment @YG +// Handles front-end responses passed in by methods in threadTools.js and triggers backend func calls. +topicsAPI.endorse = async function (caller, data) { + await doTopicAction('endorse', 'event:topic_endorsed', caller, { + tids: data.tids, + }); +}; + +topicsAPI.unendorse = async function (caller, data) { + await doTopicAction('unendorse', 'event:topic_unendorsed', caller, { + tids: data.tids, + }); +}; + topicsAPI.follow = async function (caller, data) { await topics.follow(data.tid, caller.uid); }; diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js index b9691a8da5..e9f75d83fc 100644 --- a/src/controllers/write/topics.js +++ b/src/controllers/write/topics.js @@ -85,6 +85,19 @@ Topics.unlock = async (req, res) => { helpers.formatApiResponse(200, res); }; +// Comment @YG +// Configured http response after clicking the 'endorse' button. +Topics.endorse = async (req, res) => { + await api.topics.endorse(req, { tids: [req.params.tid] }); + helpers.formatApiResponse(200, res); +}; + +Topics.unendorse = async (req, res) => { + await api.topics.unendorse(req, { tids: [req.params.tid] }); + helpers.formatApiResponse(200, res); +}; +// End of implementation + Topics.follow = async (req, res) => { await api.topics.follow(req, req.params); helpers.formatApiResponse(200, res); diff --git a/src/database/redis/hash.js b/src/database/redis/hash.js index 45e80cf532..82ee5ce476 100644 --- a/src/database/redis/hash.js +++ b/src/database/redis/hash.js @@ -59,6 +59,7 @@ module.exports = function (module) { module.setObjectField = async function (key, field, value) { if (!field) { + console.log('No field detected.\n'); return; } if (Array.isArray(key)) { diff --git a/src/posts/tools.js b/src/posts/tools.js index daa5bde189..1bca296cde 100644 --- a/src/posts/tools.js +++ b/src/posts/tools.js @@ -6,6 +6,10 @@ module.exports = function (Posts) { Posts.tools = {}; Posts.tools.delete = async function (uid, pid) { + // Comment @YG + // I actually don't know the difference between this function and + // the one in "delete.js" as both of them seemed to be called when a + // post get deleted. However, this function is called first. return await togglePostDelete(uid, pid, true); }; @@ -14,6 +18,9 @@ module.exports = function (Posts) { }; async function togglePostDelete(uid, pid, isDelete) { + // Comment @YG + // This function will be called whenever you confirm your deletion + // through the "delete" button for each post. const [postData, canDelete] = await Promise.all([ Posts.getPostData(pid), privileges.posts.canDelete(pid, uid), diff --git a/src/privileges/topics.js b/src/privileges/topics.js index 5421876fb2..aba46108ac 100644 --- a/src/privileges/topics.js +++ b/src/privileges/topics.js @@ -35,7 +35,6 @@ privsTopics.get = async function (tid, uid) { const editable = isAdminOrMod; const deletable = (privData['topics:delete'] && (isOwner || isModerator)) || isAdministrator; const mayReply = privsTopics.canViewDeletedScheduled(topicData, {}, false, privData['topics:schedule']); - return await plugins.hooks.fire('filter:privileges.topics.get', { 'topics:reply': (privData['topics:reply'] && ((!topicData.locked && mayReply) || isModerator)) || isAdministrator, 'topics:read': privData['topics:read'] || isAdministrator, diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js index 1a537fd56d..a64cb50dd1 100644 --- a/src/routes/write/topics.js +++ b/src/routes/write/topics.js @@ -27,6 +27,11 @@ module.exports = function () { setupApiRoute(router, 'put', '/:tid/lock', [...middlewares], controllers.write.topics.lock); setupApiRoute(router, 'delete', '/:tid/lock', [...middlewares], controllers.write.topics.unlock); + // Comment @YG + // Configured routes for 'endorse' button on the topic drop-down menu. + setupApiRoute(router, 'put', '/:tid/endorse', [...middlewares], controllers.write.topics.endorse); + setupApiRoute(router, 'delete', '/:tid/endorse', [...middlewares], controllers.write.topics.unendorse); + setupApiRoute(router, 'put', '/:tid/follow', [...middlewares, middleware.assert.topic], controllers.write.topics.follow); setupApiRoute(router, 'delete', '/:tid/follow', [...middlewares, middleware.assert.topic], controllers.write.topics.unfollow); setupApiRoute(router, 'put', '/:tid/ignore', [...middlewares, middleware.assert.topic], controllers.write.topics.ignore); diff --git a/src/topics/create.js b/src/topics/create.js index 0d6ee1bc19..d77bdb69aa 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -123,6 +123,8 @@ module.exports = function (Topics) { postData.tid = tid; postData.ip = data.req ? data.req.ip : null; postData.isMain = true; + // Comment @YG + // I wonder why we create post in this method. postData = await posts.create(postData); postData = await onNewPost(postData, data); @@ -156,7 +158,6 @@ module.exports = function (Topics) { Topics.notifyTagFollowers(postData, uid); categories.notifyCategoryFollowers(postData, uid); } - return { topicData: topicData, postData: postData, diff --git a/src/topics/data.js b/src/topics/data.js index 1260c092e1..e7396aab33 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -12,7 +12,7 @@ const intFields = [ 'tid', 'cid', 'uid', 'mainPid', 'postcount', 'viewcount', 'postercount', 'deleted', 'locked', 'pinned', 'pinExpiry', 'timestamp', 'upvotes', 'downvotes', 'lastposttime', - 'deleterUid', + 'deleterUid', 'endorsed', ]; module.exports = function (Topics) { diff --git a/src/topics/events.js b/src/topics/events.js index f63f4b32a8..56ab37279d 100644 --- a/src/topics/events.js +++ b/src/topics/events.js @@ -52,6 +52,16 @@ Events._types = { icon: 'fa-trash-o', translation: async (event, language) => translateSimple(event, language, 'topic:user-restored-topic'), }, + // Comment @YG + // Added configuration for endorse so that endorsement action gets logged on the screen just like other events. + endorse: { + icon: 'fa-check', + translation: async (event, language) => translateSimple(event, language, 'topic:user-endorsed-topic'), + }, + unendorse: { + icon: 'fa-stop', + translation: async (event, language) => translateSimple(event, language, 'topic:user-unendorsed-topic'), + }, move: { icon: 'fa-arrow-circle-right', translation: async (event, language) => translateEventArgs(event, language, 'topic:user-moved-topic-from', renderUser(event), `${event.fromCategory.name}`, renderTimeago(event)), diff --git a/src/topics/index.js b/src/topics/index.js index 5724d8a276..c27dae6fdf 100644 --- a/src/topics/index.js +++ b/src/topics/index.js @@ -219,6 +219,7 @@ Topics.getTopicWithPosts = async function (topicData, set, uid, start, stop, rev topicData.forkTimestampISO = utils.toISOString(topicData.forkTimestamp); } topicData.related = related || []; + // This means each topic inherently creates a post. topicData.unreplied = topicData.postcount === 1; topicData.icons = []; @@ -297,6 +298,13 @@ Topics.isLocked = async function (tid) { return locked === 1; }; +// Comment @YG +// Adding endorsement check to Topics module +Topics.isEndorsed = async function (tid) { + const endorsed = await Topics.getTopicField(tid, 'endorsed'); + return endorsed === 1; +}; + Topics.search = async function (tid, term) { if (!tid || !term) { throw new Error('[[error:invalid-data]]'); diff --git a/src/topics/tools.js b/src/topics/tools.js index cadeb95563..76ec1a53a9 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -93,6 +93,8 @@ module.exports = function (Topics) { }; async function toggleLock(tid, uid, lock) { + // Comment @YG + // Fetching data from database. const topicData = await Topics.getTopicFields(tid, ['tid', 'uid', 'cid']); if (!topicData || !topicData.cid) { throw new Error('[[error:no-topic]]'); @@ -110,6 +112,43 @@ module.exports = function (Topics) { return topicData; } + + // Comment @YG + // Added endorsement back-end logic. Similar to other topicTools functions. + topicTools.endorse = async function (tid, uid) { + return await toggleEndorse(tid, uid, true); + }; + + topicTools.unendorse = async function (tid, uid) { + return await toggleEndorse(tid, uid, false); + }; + + async function toggleEndorse(tid, uid, endorse) { + // Get the topic data + const topicData = await Topics.getTopicFields(tid, ['tid', 'uid', 'cid']); + if (!topicData || !topicData.cid) { + throw new Error('[[error:no-topic]]'); + } + + + // Only admins can do endorsement + const isAdminOrMod = await privileges.categories.isAdminOrMod(topicData.cid, uid); + if (!isAdminOrMod) { + throw new Error('[[error:no-privileges]]'); + } + + // Set the endorse field according to arg endorse + await Topics.setTopicField(tid, 'endorsed', endorse ? 1 : 0); + topicData.events = await Topics.events.log(tid, { type: endorse ? 'endorse' : 'unendorse', uid }); + topicData.isEndorsed = endorse; + topicData.endorsed = endorse; + + plugins.hooks.fire('action:topic.endorse', { topic: _.clone(topicData), uid: uid }); + + return topicData; + } + + topicTools.pin = async function (tid, uid) { return await togglePin(tid, uid, true); }; diff --git a/test/topics.js b/test/topics.js index 8a32e445f5..9c2a057309 100644 --- a/test/topics.js +++ b/test/topics.js @@ -27,6 +27,7 @@ const request = require('../src/request'); describe('Topic\'s', () => { let topic; + let topicEndorseTest; let categoryObj; let adminUid; let adminJar; @@ -639,6 +640,7 @@ describe('Topic\'s', () => { let newTopic; let followerUid; let moveCid; + let endorseTestTopic; // Only for endorse before(async () => { ({ topicData: newTopic } = await topics.post({ @@ -654,6 +656,22 @@ describe('Topic\'s', () => { name: 'Test Category', description: 'Test category created by testing script', })); + + // Comment @YG + // I created another topic for endorse testing. + topicEndorseTest = { + userId: fooUid, + categoryId: categoryObj.cid, + title: 'Foo is the best', + content: 'Bar is the best', + }; + // A new topic posted by foo. + ({ topicData: endorseTestTopic } = await topics.post({ + uid: topicEndorseTest.userId, + title: topicEndorseTest.title, + content: topicEndorseTest.content, + cid: topicEndorseTest.categoryId, + })); }); it('should load topic tools', (done) => { @@ -688,6 +706,8 @@ describe('Topic\'s', () => { assert(!isLocked); }); + + it('should pin topic', async () => { await apiTopics.pin({ uid: adminUid }, { tids: [newTopic.tid], cid: categoryObj.cid }); const pinned = await topics.getTopicField(newTopic.tid, 'pinned');