diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000..8f0fc0d43b Binary files /dev/null and b/dump.rdb differ diff --git a/public/openapi/components/schemas/PostObject.yaml b/public/openapi/components/schemas/PostObject.yaml index ea91579cc6..45b6ecf80d 100644 --- a/public/openapi/components/schemas/PostObject.yaml +++ b/public/openapi/components/schemas/PostObject.yaml @@ -16,6 +16,8 @@ PostObject: type: number deleted: type: boolean + endorse: + type: number upvotes: type: number downvotes: diff --git a/public/openapi/read/topic/topic_id.yaml b/public/openapi/read/topic/topic_id.yaml index f0fde1b6c0..c80e4aa1a7 100644 --- a/public/openapi/read/topic/topic_id.yaml +++ b/public/openapi/read/topic/topic_id.yaml @@ -67,6 +67,8 @@ get: type: number deleted: type: number + endorse: + type: number upvotes: type: number downvotes: @@ -75,6 +77,8 @@ get: type: number deleterUid: type: number + endorseUid: + type: number edited: type: number timestampISO: diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index c59b9bce29..7275f2d9e8 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -186,6 +186,8 @@ paths: $ref: 'write/posts/pid/upvoters.yaml' /posts/{pid}/bookmark: $ref: 'write/posts/pid/bookmark.yaml' + /posts/{pid}/endorse: + $ref: 'write/posts/pid/endorse.yaml' /posts/{pid}/diffs: $ref: 'write/posts/pid/diffs.yaml' /posts/{pid}/diffs/{since}: diff --git a/public/openapi/write/posts/pid.yaml b/public/openapi/write/posts/pid.yaml index 593a7acd01..c76ad76b7b 100644 --- a/public/openapi/write/posts/pid.yaml +++ b/public/openapi/write/posts/pid.yaml @@ -40,12 +40,16 @@ get: type: number deleted: type: number + endorse: + type: number upvotes: type: number downvotes: type: number deleterUid: type: number + endorseUid: + type: number edited: type: number replies: diff --git a/public/openapi/write/posts/pid/endorse.yaml b/public/openapi/write/posts/pid/endorse.yaml new file mode 100644 index 0000000000..992ef65157 --- /dev/null +++ b/public/openapi/write/posts/pid/endorse.yaml @@ -0,0 +1,26 @@ +put: + tags: + - posts + summary: endorse a post + description: This operation endorses a post. + parameters: + - in: path + name: pid + schema: + type: string + required: true + description: a valid post id + example: 2 + responses: + '200': + description: Post successfully endorsed + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object + properties: {} diff --git a/public/openapi/write/posts/pid/replies.yaml b/public/openapi/write/posts/pid/replies.yaml index b021eec14e..108630e7be 100644 --- a/public/openapi/write/posts/pid/replies.yaml +++ b/public/openapi/write/posts/pid/replies.yaml @@ -45,6 +45,8 @@ get: type: number deleted: type: number + endorse: + type: number upvotes: type: number downvotes: diff --git a/public/src/client/topic/events.js b/public/src/client/topic/events.js index e091dd69c8..c2315b6fe2 100644 --- a/public/src/client/topic/events.js +++ b/public/src/client/topic/events.js @@ -40,6 +40,9 @@ define('forum/topic/events', [ 'posts.bookmark': togglePostBookmark, 'posts.unbookmark': togglePostBookmark, + 'posts.endorse': toggleEndorse, + 'posts.unendorse': toggleEndorse, + 'posts.upvote': togglePostVote, 'posts.downvote': togglePostVote, 'posts.unvote': togglePostVote, @@ -222,6 +225,9 @@ define('forum/topic/events', [ el.find('[component="post/bookmark/off"]').toggleClass('hidden', data.isBookmarked); } + function toggleEndorse() { + console.log('inside toggle endorse function'); + } function togglePostVote(data) { const post = $('[data-pid="' + data.post.pid + '"]'); post.find('[component="post/upvote"]').filter(function (index, el) { diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index f8d2ca8933..a3d4aefe83 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -129,6 +129,10 @@ define('forum/topic/postTools', [ return bookmarkPost($(this), getData($(this), 'data-pid')); }); + postContainer.on('click', '[component="post/endorse"]', function () { + return endorsePost($(this), getData($(this), 'data-pid')); + }); + postContainer.on('click', '[component="post/upvote"]', function () { return votes.toggleVote($(this), '.upvoted', 1); }); @@ -404,6 +408,13 @@ define('forum/topic/postTools', [ postAction(action, pid); } + async function endorsePost(button, pid) { + const method = 'put'; + const action = 'endorse'; + console.log('attempting API call'); + api[method](`/posts/${pid}/${action}`).catch(alerts.error); + } + function purgePost(button) { postAction('purge', getData(button, 'data-pid')); } diff --git a/src/api/posts.js b/src/api/posts.js index 4e3917a008..801bd06151 100644 --- a/src/api/posts.js +++ b/src/api/posts.js @@ -156,6 +156,13 @@ postsAPI.edit = async function (caller, data) { return returnData; }; +postsAPI.endorse = async function (caller, data) { + return await apiHelpers.postCommand(caller, 'endorse', 'endorsed', '', data); +}; +postsAPI.unendorse = async function (caller, data) { + return await apiHelpers.postCommand(caller, 'unendorse', 'unendorsed', '', data); +}; + postsAPI.delete = async function (caller, data) { await deleteOrRestore(caller, data, { command: 'delete', diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index 1dc8cf6800..403849ea2b 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -153,6 +153,17 @@ Posts.unbookmark = async (req, res) => { helpers.formatApiResponse(200, res); }; +Posts.endorse = async (req, res) => { + const data = await mock(req); + await api.posts.endorse(req, data); + helpers.formatApiResponse(200, res); +}; +Posts.unendorse = async (req, res) => { + const data = await mock(req); + await api.posts.unendorse(req, data); + helpers.formatApiResponse(200, res); +}; + Posts.getDiffs = async (req, res) => { helpers.formatApiResponse(200, res, await api.posts.getDiffs(req, { ...req.params })); }; diff --git a/src/posts/data.js b/src/posts/data.js index 3a4d303ff5..b2f430ca5d 100644 --- a/src/posts/data.js +++ b/src/posts/data.js @@ -7,7 +7,7 @@ const utils = require('../utils'); const intFields = [ 'uid', 'pid', 'tid', 'deleted', 'timestamp', 'upvotes', 'downvotes', 'deleterUid', 'edited', - 'replies', 'bookmarks', + 'replies', 'bookmarks', 'endorse', 'endorseUid', ]; module.exports = function (Posts) { diff --git a/src/posts/endorse.js b/src/posts/endorse.js new file mode 100644 index 0000000000..b9f6716c77 --- /dev/null +++ b/src/posts/endorse.js @@ -0,0 +1,27 @@ +'use strict'; + +const plugins = require('../plugins'); + +module.exports = function (Posts) { + Posts.endorse = async function (pid, uid) { + return await toggleEndorse('endorse', pid, uid); + }; + + Posts.unendorse = async function (pid, uid) { + return await toggleEndorse('unendorse', pid, uid); + }; + + async function toggleEndorse(type, pid, uid) { + const isEndorsing = type === 'endorse'; + await plugins.hooks.fire(`filter:post.${type}`, { pid: pid, uid: uid }); + + await Posts.setPostFields(pid, { + endorse: isEndorsing ? 1 : 0, + endorseUid: isEndorsing ? uid : 0, + }); + + const postData = await Posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'timestamp']); + + return postData; + } +}; diff --git a/src/posts/index.js b/src/posts/index.js index 9db52c6b27..433b9faa97 100644 --- a/src/posts/index.js +++ b/src/posts/index.js @@ -24,6 +24,7 @@ require('./tools')(Posts); require('./votes')(Posts); require('./bookmarks')(Posts); require('./queue')(Posts); +require('./endorse')(Posts); require('./diffs')(Posts); require('./uploads')(Posts); diff --git a/src/posts/summary.js b/src/posts/summary.js index 364baad1f7..f662e1d5ea 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -20,7 +20,7 @@ module.exports = function (Posts) { options.parse = options.hasOwnProperty('parse') ? options.parse : true; options.extraFields = options.hasOwnProperty('extraFields') ? options.extraFields : []; - const fields = ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes', 'replies', 'handle'].concat(options.extraFields); + const fields = ['pid', 'tid', 'content', 'uid', 'timestamp', 'deleted', 'upvotes', 'downvotes', 'replies', 'handle', 'endorse'].concat(options.extraFields); let posts = await Posts.getPostsFields(pids, fields); posts = posts.filter(Boolean); diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index e573bbb9b0..821650e8e0 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -32,6 +32,9 @@ module.exports = function () { setupApiRoute(router, 'put', '/:pid/bookmark', middlewares, controllers.write.posts.bookmark); setupApiRoute(router, 'delete', '/:pid/bookmark', middlewares, controllers.write.posts.unbookmark); + setupApiRoute(router, 'put', '/:pid/endorse', middlewares, controllers.write.posts.endorse); + setupApiRoute(router, 'put', '/:pid/endorse', middlewares, controllers.write.posts.unendorse); + setupApiRoute(router, 'get', '/:pid/diffs', [middleware.assert.post], controllers.write.posts.getDiffs); setupApiRoute(router, 'get', '/:pid/diffs/:since', [middleware.assert.post], controllers.write.posts.loadDiff); setupApiRoute(router, 'put', '/:pid/diffs/:since', middlewares, controllers.write.posts.restoreDiff); diff --git a/test/posts.js b/test/posts.js index 20403e24cf..d2631fc2f6 100644 --- a/test/posts.js +++ b/test/posts.js @@ -281,6 +281,14 @@ describe('Post\'s', () => { }); }); + describe('endorsing', () => { + it('should endorse a post', async () => { + const data = await apiPosts.endorse({ uid: voterUid }, { pid: postData.pid, room_id: `topic_${postData.tid}` }); + const isEndorsed = await posts.getPostField(postData.pid, 'endorse'); + assert.strictEqual(isEndorsed, 1); + }); + }); + describe('post tools', () => { it('should error if data is invalid', (done) => { socketPosts.loadPostTools({ uid: globalModUid }, null, (err) => {