'use strict'; const validator = require('validator'); const api = require('../../api'); const topics = require('../../topics'); const privileges = require('../../privileges'); const helpers = require('../helpers'); const middleware = require('../../middleware'); const uploadsController = require('../uploads'); const Topics = module.exports; Topics.get = async (req, res) => { helpers.formatApiResponse(200, res, await api.topics.get(req, req.params)); }; Topics.create = async (req, res) => { const payload = await api.topics.create(req, req.body); if (payload.queued) { helpers.formatApiResponse(202, res, payload); } else { helpers.formatApiResponse(200, res, payload); } }; Topics.reply = async (req, res) => { const payload = await api.topics.reply(req, { ...req.body, tid: req.params.tid }); helpers.formatApiResponse(200, res, payload); }; Topics.delete = async (req, res) => { await api.topics.delete(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.restore = async (req, res) => { await api.topics.restore(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.purge = async (req, res) => { await api.topics.purge(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.pin = async (req, res) => { // Pin expiry was not available w/ sockets hence not included in api lib method if (req.body.expiry) { await topics.tools.setPinExpiry(req.params.tid, req.body.expiry, req.uid); } await api.topics.pin(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.unpin = async (req, res) => { await api.topics.unpin(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.lock = async (req, res) => { await api.topics.lock(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.unlock = async (req, res) => { await api.topics.unlock(req, { tids: [req.params.tid] }); helpers.formatApiResponse(200, res); }; Topics.follow = async (req, res) => { await api.topics.follow(req, req.params); helpers.formatApiResponse(200, res); }; Topics.ignore = async (req, res) => { await api.topics.ignore(req, req.params); helpers.formatApiResponse(200, res); }; Topics.unfollow = async (req, res) => { await api.topics.unfollow(req, req.params); helpers.formatApiResponse(200, res); }; Topics.addTags = async (req, res) => { if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) { return helpers.formatApiResponse(403, res); } const cid = await topics.getTopicField(req.params.tid, 'cid'); await topics.validateTags(req.body.tags, cid, req.user.uid, req.params.tid); const tags = await topics.filterTags(req.body.tags); await topics.addTags(tags, [req.params.tid]); helpers.formatApiResponse(200, res); }; Topics.deleteTags = async (req, res) => { if (!await privileges.topics.canEdit(req.params.tid, req.user.uid)) { return helpers.formatApiResponse(403, res); } await topics.deleteTopicTags(req.params.tid); helpers.formatApiResponse(200, res); }; Topics.getThumbs = async (req, res) => { if (isFinite(req.params.tid)) { // post_uuids can be passed in occasionally, in that case no checks are necessary const [exists, canRead] = await Promise.all([ topics.exists(req.params.tid), privileges.topics.can('topics:read', req.params.tid, req.uid), ]); if (!exists || !canRead) { return helpers.formatApiResponse(403, res); } } helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); }; Topics.addThumb = async (req, res) => { await checkThumbPrivileges({ tid: req.params.tid, uid: req.user.uid, res }); if (res.headersSent) { return; } const files = await uploadsController.uploadThumb(req, res); // response is handled here // Add uploaded files to topic zset if (files && files.length) { await Promise.all(files.map(async (fileObj) => { await topics.thumbs.associate({ id: req.params.tid, path: fileObj.path || fileObj.url, }); })); } }; Topics.migrateThumbs = async (req, res) => { await Promise.all([ checkThumbPrivileges({ tid: req.params.tid, uid: req.user.uid, res }), checkThumbPrivileges({ tid: req.body.tid, uid: req.user.uid, res }), ]); if (res.headersSent) { return; } await topics.thumbs.migrate(req.params.tid, req.body.tid); helpers.formatApiResponse(200, res); }; Topics.deleteThumb = async (req, res) => { if (!req.body.path.startsWith('http')) { await middleware.assert.path(req, res, () => {}); if (res.headersSent) { return; } } await checkThumbPrivileges({ tid: req.params.tid, uid: req.user.uid, res }); if (res.headersSent) { return; } await topics.thumbs.delete(req.params.tid, req.body.path); helpers.formatApiResponse(200, res, await topics.thumbs.get(req.params.tid)); }; Topics.reorderThumbs = async (req, res) => { await checkThumbPrivileges({ tid: req.params.tid, uid: req.user.uid, res }); if (res.headersSent) { return; } const exists = await topics.thumbs.exists(req.params.tid, req.body.path); if (!exists) { return helpers.formatApiResponse(404, res); } await topics.thumbs.associate({ id: req.params.tid, path: req.body.path, score: req.body.order, }); helpers.formatApiResponse(200, res); }; async function checkThumbPrivileges({ tid, uid, res }) { // req.params.tid could be either a tid (pushing a new thumb to an existing topic) // or a post UUID (a new topic being composed) const isUUID = validator.isUUID(tid); // Sanity-check the tid if it's strictly not a uuid if (!isUUID && (isNaN(parseInt(tid, 10)) || !await topics.exists(tid))) { return helpers.formatApiResponse(404, res, new Error('[[error:no-topic]]')); } // While drafts are not protected, tids are if (!isUUID && !await privileges.topics.canEdit(tid, uid)) { return helpers.formatApiResponse(403, res, new Error('[[error:no-privileges]]')); } } Topics.getEvents = async (req, res) => { if (!await privileges.topics.can('topics:read', req.params.tid, req.uid)) { return helpers.formatApiResponse(403, res); } helpers.formatApiResponse(200, res, await topics.events.get(req.params.tid, req.uid)); }; Topics.deleteEvent = async (req, res) => { if (!await privileges.topics.isAdminOrMod(req.params.tid, req.uid)) { return helpers.formatApiResponse(403, res); } await topics.events.purge(req.params.tid, [req.params.eventId]); helpers.formatApiResponse(200, res); };