diff --git a/public/src/client/topic/postTools.js b/public/src/client/topic/postTools.js index f65824d636a0..16a6b18fc479 100644 --- a/public/src/client/topic/postTools.js +++ b/public/src/client/topic/postTools.js @@ -7,7 +7,8 @@ define('forum/topic/postTools', [ 'components', 'translator', 'forum/topic/votes', -], function (share, navigator, components, translator, votes) { + 'api', +], function (share, navigator, components, translator, votes, api) { var PostTools = {}; var staleReplyAnyway = false; @@ -390,13 +391,9 @@ define('forum/topic/postTools', [ return; } - socket.emit('posts.' + action, { - pid: pid, - }, function (err) { - if (err) { - app.alertError(err.message); - } - }); + const route = action === 'purge' ? '' : '/state'; + const method = action === 'restore' ? 'put' : 'del'; + api[method](`/posts/${pid}${route}`, undefined, undefined, err => app.alertError(err.status.message)); }); }); } diff --git a/src/controllers/write/posts.js b/src/controllers/write/posts.js index a1481784d707..fe4e5828791c 100644 --- a/src/controllers/write/posts.js +++ b/src/controllers/write/posts.js @@ -6,11 +6,13 @@ const _ = require('lodash'); const meta = require('../../meta'); const groups = require('../../groups'); const posts = require('../../posts'); +const topics = require('../../topics'); const events = require('../../events'); const utils = require('../../utils'); const helpers = require('../helpers'); const sockets = require('../../socket.io'); +const socketTopics = require('../../socket.io/topics'); // eehhh... const Posts = module.exports; @@ -79,3 +81,106 @@ Posts.edit = async (req, res) => { const uids = _.uniq(_.flatten(memberData).concat(req.user.uid.toString())); uids.forEach(uid => sockets.in('uid_' + uid).emit('event:post_edited', editResult)); }; + +Posts.purge = async (req, res) => { + const results = await isMainAndLastPost(req.params.pid); + if (results.isMain && !results.isLast) { + throw new Error('[[error:cant-purge-main-post]]'); + } + + const isMainAndLast = results.isMain && results.isLast; + const postData = await posts.getPostFields(req.params.pid, ['pid', 'toPid', 'tid']); + + await posts.tools.purge(req.user.uid, req.params.pid); + helpers.formatApiResponse(200, res); + + sockets.in('topic_' + postData.tid).emit('event:post_purged', postData); + const topicData = await topics.getTopicFields(postData.tid, ['title', 'cid']); + + await events.log({ + type: 'post-purge', + uid: req.user.uid, + pid: req.params.pid, + ip: req.ip, + tid: postData.tid, + title: String(topicData.title), + }); + + if (isMainAndLast) { + await socketTopics.doTopicAction('purge', 'event:topic_purged', { + ip: req.ip, + uid: req.user.uid, + }, { tids: [postData.tid], cid: topicData.cid }); + } +}; + +Posts.restore = async (req, res) => { + await deleteOrRestore(req, { + pid: req.params.pid, + }, { + command: 'restore', + event: 'event:post_restored', + type: 'post-restore', + }); + + helpers.formatApiResponse(200, res); +}; + +Posts.delete = async (req, res) => { + await deleteOrRestore(req, { + pid: req.params.pid, + }, { + command: 'delete', + event: 'event:post_deleted', + type: 'post-delete', + }); + + helpers.formatApiResponse(200, res); +}; + +async function isMainAndLastPost(pid) { + const [isMain, topicData] = await Promise.all([ + posts.isMain(pid), + posts.getTopicFields(pid, ['postcount']), + ]); + return { + isMain: isMain, + isLast: topicData && topicData.postcount === 1, + }; +} + +async function deleteOrRestoreTopicOf(command, pid, req) { + const topic = await posts.getTopicFields(pid, ['tid', 'cid', 'deleted']); + if (command === 'delete' && !topic.deleted) { + await socketTopics.doTopicAction('delete', 'event:topic_deleted', { + uid: req.user.uid, + ip: req.ip, + }, { tids: [topic.tid], cid: topic.cid }); + } else if (command === 'restore' && topic.deleted) { + await socketTopics.doTopicAction('restore', 'event:topic_restored', { + uid: req.user.uid, + ip: req.ip, + }, { tids: [topic.tid], cid: topic.cid }); + } +} + +async function deleteOrRestore(req, data, params) { + if (!data || !data.pid) { + throw new Error('[[error:invalid-data]]'); + } + const postData = await posts.tools[params.command](req.user.uid, data.pid); + const results = await isMainAndLastPost(data.pid); + if (results.isMain && results.isLast) { + await deleteOrRestoreTopicOf(params.command, data.pid, req); + } + + sockets.in('topic_' + postData.tid).emit(params.event, postData); + + await events.log({ + type: params.type, + uid: req.user.uid, + pid: data.pid, + tid: postData.tid, + ip: req.ip, + }); +} diff --git a/src/middleware/assert.js b/src/middleware/assert.js index 338392495a5b..ef7d79938678 100644 --- a/src/middleware/assert.js +++ b/src/middleware/assert.js @@ -8,6 +8,7 @@ const user = require('../user'); const groups = require('../groups'); const topics = require('../topics'); +const posts = require('../posts'); const helpers = require('../controllers/helpers'); @@ -36,4 +37,12 @@ module.exports = function (middleware) { next(); }; + + middleware.assertPost = async (req, res, next) => { + if (!await posts.exists(req.params.pid)) { + return helpers.formatApiResponse(404, res, new Error('[[error:no-topic]]')); + } + + next(); + }; }; diff --git a/src/routes/write/posts.js b/src/routes/write/posts.js index 2cbe1a688ce2..9c0655d336f5 100644 --- a/src/routes/write/posts.js +++ b/src/routes/write/posts.js @@ -11,46 +11,10 @@ module.exports = function () { const middlewares = [middleware.authenticate]; setupApiRoute(router, '/:pid', middleware, [...middlewares, middleware.checkRequired.bind(null, ['content'])], 'put', controllers.write.posts.edit); + setupApiRoute(router, '/:pid', middleware, [...middlewares, middleware.assertPost], 'delete', controllers.write.posts.purge); - // app.route('/:pid') - // .put(apiMiddleware.requireUser, function(req, res) { - // if (!utils.checkRequired(['content'], req, res)) { - // return false; - // } - - // var payload = { - // uid: req.user.uid, - // pid: req.params.pid, - // content: req.body.content, - // options: {} - // }; - - // if (req.body.handle) { payload.handle = req.body.handle; } - // if (req.body.title) { payload.title = req.body.title; } - // if (req.body.topic_thumb) { payload.options.topic_thumb = req.body.topic_thumb; } - // if (req.body.tags) { payload.options.tags = req.body.tags; } - - // posts.edit(payload, function(err) { - // errorHandler.handle(err, res); - // }) - // }) - // .delete(apiMiddleware.requireUser, apiMiddleware.validatePid, function(req, res) { - // posts.purge(req.params.pid, req.user.uid, function(err) { - // errorHandler.handle(err, res); - // }); - // }); - - // app.route('/:pid/state') - // .put(apiMiddleware.requireUser, apiMiddleware.validatePid, function (req, res) { - // posts.restore(req.params.pid, req.user.uid, function (err) { - // errorHandler.handle(err, res); - // }); - // }) - // .delete(apiMiddleware.requireUser, apiMiddleware.validatePid, function (req, res) { - // posts.delete(req.params.pid, req.user.uid, function (err) { - // errorHandler.handle(err, res); - // }); - // }); + setupApiRoute(router, '/:pid/state', middleware, [...middlewares, middleware.assertPost], 'put', controllers.write.posts.restore); + setupApiRoute(router, '/:pid/state', middleware, [...middlewares, middleware.assertPost], 'delete', controllers.write.posts.delete); // app.route('/:pid/vote') // .post(apiMiddleware.requireUser, function(req, res) { diff --git a/src/socket.io/posts/tools.js b/src/socket.io/posts/tools.js index 3255722efe12..9d4f6442e991 100644 --- a/src/socket.io/posts/tools.js +++ b/src/socket.io/posts/tools.js @@ -12,6 +12,7 @@ const social = require('../../social'); const user = require('../../user'); const utils = require('../../utils'); +const sockets = require('..'); module.exports = function (SocketPosts) { SocketPosts.loadPostTools = async function (socket, data) { @@ -63,6 +64,8 @@ module.exports = function (SocketPosts) { }; SocketPosts.delete = async function (socket, data) { + sockets.warnDeprecated(socket, 'DELETE /api/v1/posts/:pid/state'); + await deleteOrRestore(socket, data, { command: 'delete', event: 'event:post_deleted', @@ -71,6 +74,8 @@ module.exports = function (SocketPosts) { }; SocketPosts.restore = async function (socket, data) { + sockets.warnDeprecated(socket, 'PUT /api/v1/posts/:pid/state'); + await deleteOrRestore(socket, data, { command: 'restore', event: 'event:post_restored', @@ -118,6 +123,8 @@ module.exports = function (SocketPosts) { } SocketPosts.purge = async function (socket, data) { + sockets.warnDeprecated(socket, 'DELETE /api/v1/posts/:pid'); + if (!data || !parseInt(data.pid, 10)) { throw new Error('[[error:invalid-data]]'); }