mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 09:06:15 +01:00
feat: topic delete/restore/purge/(un)pin/(un)lock
This commit is contained in:
@@ -534,6 +534,135 @@ paths:
|
|||||||
$ref: '#/components/schemas/Status'
|
$ref: '#/components/schemas/Status'
|
||||||
response:
|
response:
|
||||||
$ref: components/schemas/PostsObject.yaml#/PostsObject
|
$ref: components/schemas/PostsObject.yaml#/PostsObject
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Delete a topic
|
||||||
|
description: This operation purges a topic and all of its posts (careful, there is no confirmation!)
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully purged
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
/topics/{tid}/state:
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Delete a topic
|
||||||
|
description: This operation deletes an existing topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully deleted
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Restore a topic
|
||||||
|
description: This operation restores a topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully restored
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
/topics/{tid}/lock:
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Lock a topic
|
||||||
|
description: This operation locks an existing topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully locked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Unlock a topic
|
||||||
|
description: This operation unlocks a topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully unlocked
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
/topics/{tid}/state:
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Pin a topic
|
||||||
|
description: This operation pins an existing topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully pinned
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- topics
|
||||||
|
summary: Unpin a topic
|
||||||
|
description: This operation unpins a topic.
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Topic successfully unpinned
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/Status'
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties: {}
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
Status:
|
Status:
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ define('forum/topic/threadTools', [
|
|||||||
'components',
|
'components',
|
||||||
'translator',
|
'translator',
|
||||||
'handleBack',
|
'handleBack',
|
||||||
], function (components, translator, handleBack) {
|
'api',
|
||||||
|
], function (components, translator, handleBack, api) {
|
||||||
var ThreadTools = {};
|
var ThreadTools = {};
|
||||||
|
|
||||||
ThreadTools.init = function (tid, topicContainer) {
|
ThreadTools.init = function (tid, topicContainer) {
|
||||||
@@ -27,22 +28,22 @@ define('forum/topic/threadTools', [
|
|||||||
});
|
});
|
||||||
|
|
||||||
topicContainer.on('click', '[component="topic/lock"]', function () {
|
topicContainer.on('click', '[component="topic/lock"]', function () {
|
||||||
socket.emit('topics.lock', { tids: [tid], cid: ajaxify.data.cid });
|
api.put(`/topics/${tid}/lock`);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
topicContainer.on('click', '[component="topic/unlock"]', function () {
|
topicContainer.on('click', '[component="topic/unlock"]', function () {
|
||||||
socket.emit('topics.unlock', { tids: [tid], cid: ajaxify.data.cid });
|
api.del(`/topics/${tid}/lock`);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
topicContainer.on('click', '[component="topic/pin"]', function () {
|
topicContainer.on('click', '[component="topic/pin"]', function () {
|
||||||
socket.emit('topics.pin', { tids: [tid], cid: ajaxify.data.cid });
|
api.put(`/topics/${tid}/pin`);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
topicContainer.on('click', '[component="topic/unpin"]', function () {
|
topicContainer.on('click', '[component="topic/unpin"]', function () {
|
||||||
socket.emit('topics.unpin', { tids: [tid], cid: ajaxify.data.cid });
|
api.del(`/topics/${tid}/pin`);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -176,11 +177,9 @@ define('forum/topic/threadTools', [
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit('topics.' + command, { tids: [tid], cid: ajaxify.data.cid }, function (err) {
|
const method = command === 'restore' ? 'put' : 'del';
|
||||||
if (err) {
|
const suffix = command !== 'purge' ? '/state' : '';
|
||||||
app.alertError(err.message);
|
api[method](`/topics/${tid}${suffix}`, undefined, undefined, err => app.alertError(err.status.message));
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ const topics = require('../../topics');
|
|||||||
const posts = require('../../posts');
|
const posts = require('../../posts');
|
||||||
const user = require('../../user');
|
const user = require('../../user');
|
||||||
const meta = require('../../meta');
|
const meta = require('../../meta');
|
||||||
|
const events = require('../../events');
|
||||||
|
const privileges = require('../../privileges');
|
||||||
|
|
||||||
const helpers = require('../helpers');
|
const helpers = require('../helpers');
|
||||||
const socketHelpers = require('../../socket.io/helpers');
|
const socketHelpers = require('../../socket.io/helpers');
|
||||||
@@ -71,3 +73,86 @@ Topics.reply = async (req, res) => {
|
|||||||
user.updateOnlineUsers(req.user.uid);
|
user.updateOnlineUsers(req.user.uid);
|
||||||
socketHelpers.notifyNew(req.user.uid, 'newPost', result);
|
socketHelpers.notifyNew(req.user.uid, 'newPost', result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Topics.delete = async (req, res) => {
|
||||||
|
await doTopicAction('delete', 'event:topic_deleted', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.restore = async (req, res) => {
|
||||||
|
await doTopicAction('restore', 'event:topic_restored', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.purge = async (req, res) => {
|
||||||
|
await doTopicAction('purge', 'event:topic_purged', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.pin = async (req, res) => {
|
||||||
|
await doTopicAction('pin', 'event:topic_pinned', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.unpin = async (req, res) => {
|
||||||
|
await doTopicAction('unpin', 'event:topic_unpinned', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.lock = async (req, res) => {
|
||||||
|
await doTopicAction('lock', 'event:topic_locked', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
Topics.unlock = async (req, res) => {
|
||||||
|
await doTopicAction('unlock', 'event:topic_unlocked', req, {
|
||||||
|
tids: [req.params.tid],
|
||||||
|
});
|
||||||
|
helpers.formatApiResponse(200, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function doTopicAction(action, event, socket, { tids }) {
|
||||||
|
if (!Array.isArray(tids)) {
|
||||||
|
throw new Error('[[error:invalid-tid]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof topics.tools[action] !== 'function') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uids = await user.getUidsFromSet('users:online', 0, -1);
|
||||||
|
|
||||||
|
await Promise.all(tids.map(async function (tid) {
|
||||||
|
const title = await topics.getTopicField(tid, 'title');
|
||||||
|
const data = await topics.tools[action](tid, socket.uid);
|
||||||
|
const notifyUids = await privileges.categories.filterUids('topics:read', data.cid, uids);
|
||||||
|
socketHelpers.emitToTopicAndCategory(event, data, notifyUids);
|
||||||
|
await logTopicAction(action, socket, tid, title);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logTopicAction(action, req, tid, title) {
|
||||||
|
var actionsToLog = ['delete', 'restore', 'purge'];
|
||||||
|
if (!actionsToLog.includes(action)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await events.log({
|
||||||
|
type: 'topic-' + action,
|
||||||
|
uid: req.uid,
|
||||||
|
ip: req.ip,
|
||||||
|
tid: tid,
|
||||||
|
title: String(title),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,50 +12,17 @@ module.exports = function () {
|
|||||||
|
|
||||||
setupApiRoute(router, '/', middleware, [...middlewares, middleware.checkRequired.bind(null, ['cid', 'title', 'content'])], 'post', controllers.write.topics.create);
|
setupApiRoute(router, '/', middleware, [...middlewares, middleware.checkRequired.bind(null, ['cid', 'title', 'content'])], 'post', controllers.write.topics.create);
|
||||||
setupApiRoute(router, '/:tid', middleware, [...middlewares, middleware.checkRequired.bind(null, ['content']), middleware.assertTopic], 'post', controllers.write.topics.reply);
|
setupApiRoute(router, '/:tid', middleware, [...middlewares, middleware.checkRequired.bind(null, ['content']), middleware.assertTopic], 'post', controllers.write.topics.reply);
|
||||||
// setupApiRoute(router, '/:cid', middleware, [...middlewares, middleware.isAdmin], 'put', controllers.write.categories.update);
|
setupApiRoute(router, '/:tid', middleware, [...middlewares, middleware.assertTopic], 'delete', controllers.write.topics.purge);
|
||||||
// setupApiRoute(router, '/:cid', middleware, [...middlewares, middleware.isAdmin], 'delete', controllers.write.categories.delete);
|
|
||||||
|
|
||||||
// app.route('/:tid')
|
setupApiRoute(router, '/:tid/state', middleware, [...middlewares, middleware.assertTopic], 'put', controllers.write.topics.restore);
|
||||||
// .delete(apiMiddleware.requireUser, apiMiddleware.validateTid, function(req, res) {
|
setupApiRoute(router, '/:tid/state', middleware, [...middlewares, middleware.assertTopic], 'delete', controllers.write.topics.delete);
|
||||||
// Topics.purgePostsAndTopic(req.params.tid, req.params._uid, function(err) {
|
|
||||||
// errorHandler.handle(err, res);
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .put(apiMiddleware.requireUser, function(req, res) {
|
|
||||||
// if (!utils.checkRequired(['pid', 'content'], req, res)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var payload = {
|
setupApiRoute(router, '/:tid/pin', middleware, [...middlewares, middleware.assertTopic], 'put', controllers.write.topics.pin);
|
||||||
// uid: req.user.uid,
|
setupApiRoute(router, '/:tid/pin', middleware, [...middlewares, middleware.assertTopic], 'delete', controllers.write.topics.unpin);
|
||||||
// pid: req.body.pid,
|
|
||||||
// content: req.body.content,
|
|
||||||
// options: {}
|
|
||||||
// };
|
|
||||||
// console.log(payload);
|
|
||||||
|
|
||||||
// // Maybe a "set if available" utils method may come in handy
|
setupApiRoute(router, '/:tid/lock', middleware, [...middlewares, middleware.assertTopic], 'put', controllers.write.topics.lock);
|
||||||
// if (req.body.handle) { payload.handle = req.body.handle; }
|
setupApiRoute(router, '/:tid/lock', middleware, [...middlewares, middleware.assertTopic], 'delete', controllers.write.topics.unlock);
|
||||||
// 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, returnData) {
|
|
||||||
// errorHandler.handle(err, res, returnData);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// app.route('/:tid/state')
|
|
||||||
// .put(apiMiddleware.requireUser, apiMiddleware.validateTid, function (req, res) {
|
|
||||||
// Topics.restore(req.params.tid, req.params._uid, function (err) {
|
|
||||||
// errorHandler.handle(err, res);
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .delete(apiMiddleware.requireUser, apiMiddleware.validateTid, function (req, res) {
|
|
||||||
// Topics.delete(req.params.tid, req.params._uid, function (err) {
|
|
||||||
// errorHandler.handle(err, res);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// app.route('/:tid/follow')
|
// app.route('/:tid/follow')
|
||||||
// .put(apiMiddleware.requireUser, apiMiddleware.validateTid, function(req, res) {
|
// .put(apiMiddleware.requireUser, apiMiddleware.validateTid, function(req, res) {
|
||||||
@@ -85,17 +52,5 @@ module.exports = function () {
|
|||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// app.route('/:tid/pin')
|
|
||||||
// .put(apiMiddleware.requireUser, apiMiddleware.validateTid, function(req, res) {
|
|
||||||
// Topics.tools.pin(req.params.tid, req.user.uid, function(err) {
|
|
||||||
// errorHandler.handle(err, res);
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// .delete(apiMiddleware.requireUser, apiMiddleware.validateTid, function(req, res) {
|
|
||||||
// Topics.tools.unpin(req.params.tid, req.user.uid, function(err) {
|
|
||||||
// errorHandler.handle(err, res);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const events = require('../../events');
|
|||||||
const privileges = require('../../privileges');
|
const privileges = require('../../privileges');
|
||||||
const plugins = require('../../plugins');
|
const plugins = require('../../plugins');
|
||||||
const socketHelpers = require('../helpers');
|
const socketHelpers = require('../helpers');
|
||||||
|
const sockets = require('..');
|
||||||
|
|
||||||
module.exports = function (SocketTopics) {
|
module.exports = function (SocketTopics) {
|
||||||
SocketTopics.loadTopicTools = async function (socket, data) {
|
SocketTopics.loadTopicTools = async function (socket, data) {
|
||||||
@@ -34,30 +35,37 @@ module.exports = function (SocketTopics) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.delete = async function (socket, data) {
|
SocketTopics.delete = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'DELETE /api/v1/topics/state');
|
||||||
await SocketTopics.doTopicAction('delete', 'event:topic_deleted', socket, data);
|
await SocketTopics.doTopicAction('delete', 'event:topic_deleted', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.restore = async function (socket, data) {
|
SocketTopics.restore = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'PUT /api/v1/topics/state');
|
||||||
await SocketTopics.doTopicAction('restore', 'event:topic_restored', socket, data);
|
await SocketTopics.doTopicAction('restore', 'event:topic_restored', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.purge = async function (socket, data) {
|
SocketTopics.purge = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'DELETE /api/v1/topics');
|
||||||
await SocketTopics.doTopicAction('purge', 'event:topic_purged', socket, data);
|
await SocketTopics.doTopicAction('purge', 'event:topic_purged', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.lock = async function (socket, data) {
|
SocketTopics.lock = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'PUT /api/v1/topics/lock');
|
||||||
await SocketTopics.doTopicAction('lock', 'event:topic_locked', socket, data);
|
await SocketTopics.doTopicAction('lock', 'event:topic_locked', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.unlock = async function (socket, data) {
|
SocketTopics.unlock = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'DELETE /api/v1/topics/lock');
|
||||||
await SocketTopics.doTopicAction('unlock', 'event:topic_unlocked', socket, data);
|
await SocketTopics.doTopicAction('unlock', 'event:topic_unlocked', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.pin = async function (socket, data) {
|
SocketTopics.pin = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'PUT /api/v1/topics/pin');
|
||||||
await SocketTopics.doTopicAction('pin', 'event:topic_pinned', socket, data);
|
await SocketTopics.doTopicAction('pin', 'event:topic_pinned', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketTopics.unpin = async function (socket, data) {
|
SocketTopics.unpin = async function (socket, data) {
|
||||||
|
sockets.warnDeprecated(socket, 'DELETE /api/v1/topics/pin');
|
||||||
await SocketTopics.doTopicAction('unpin', 'event:topic_unpinned', socket, data);
|
await SocketTopics.doTopicAction('unpin', 'event:topic_unpinned', socket, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user