From ec3b5dd95aa89c41116eea6c5d7fae1761de2b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 16 Sep 2019 23:05:59 -0400 Subject: [PATCH] refactor: async/await routes/feeds --- src/controllers/404.js | 18 +- src/routes/feeds.js | 640 +++++++++++++++++------------------------ 2 files changed, 269 insertions(+), 389 deletions(-) diff --git a/src/controllers/404.js b/src/controllers/404.js index b34926aa5e..c1d6578d68 100644 --- a/src/controllers/404.js +++ b/src/controllers/404.js @@ -1,15 +1,15 @@ 'use strict'; -var nconf = require('nconf'); -var winston = require('winston'); -var validator = require('validator'); +const nconf = require('nconf'); +const winston = require('winston'); +const validator = require('validator'); -var meta = require('../meta'); -var plugins = require('../plugins'); +const meta = require('../meta'); +const plugins = require('../plugins'); exports.handle404 = function handle404(req, res) { - var relativePath = nconf.get('relative_path'); - var isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$'); + const relativePath = nconf.get('relative_path'); + const isClientScript = new RegExp('^' + relativePath + '\\/assets\\/src\\/.+\\.js(\\?v=\\w+)?$'); if (plugins.hasListeners('action:meta.override404')) { return plugins.fireHook('action:meta.override404', { @@ -38,11 +38,11 @@ exports.handle404 = function handle404(req, res) { exports.send404 = function (req, res) { res.status(404); - var path = String(req.path || ''); + const path = String(req.path || ''); if (res.locals.isAPI) { return res.json({ path: validator.escape(path.replace(/^\/api/, '')), title: '[[global:404.title]]' }); } - var middleware = require('../middleware'); + const middleware = require('../middleware'); middleware.buildHeader(req, res, function () { res.render('404', { path: validator.escape(path), title: '[[global:404.title]]' }); }); diff --git a/src/routes/feeds.js b/src/routes/feeds.js index 569db57266..31455580fa 100644 --- a/src/routes/feeds.js +++ b/src/routes/feeds.js @@ -1,22 +1,21 @@ 'use strict'; -var async = require('async'); -var rss = require('rss'); -var nconf = require('nconf'); -var validator = require('validator'); - -var posts = require('../posts'); -var topics = require('../topics'); -var user = require('../user'); -var categories = require('../categories'); -var meta = require('../meta'); -var helpers = require('../controllers/helpers'); -var privileges = require('../privileges'); -var db = require('../database'); -var utils = require('../utils'); -var controllers404 = require('../controllers/404.js'); - -var terms = { +const rss = require('rss'); +const nconf = require('nconf'); +const validator = require('validator'); + +const posts = require('../posts'); +const topics = require('../topics'); +const user = require('../user'); +const categories = require('../categories'); +const meta = require('../meta'); +const helpers = require('../controllers/helpers'); +const privileges = require('../privileges'); +const db = require('../database'); +const utils = require('../utils'); +const controllers404 = require('../controllers/404.js'); + +const terms = { daily: 'day', weekly: 'week', monthly: 'month', @@ -38,327 +37,244 @@ module.exports = function (app, middleware) { app.get('/tags/:tag.rss', middleware.maintenanceMode, generateForTag); }; -function validateTokenIfRequiresLogin(requiresLogin, cid, req, res, callback) { - var uid = parseInt(req.query.uid, 10) || 0; - var token = req.query.token; +async function validateTokenIfRequiresLogin(requiresLogin, cid, req, res) { + const uid = parseInt(req.query.uid, 10) || 0; + const token = req.query.token; if (!requiresLogin) { - return callback(); + return true; } if (uid <= 0 || !token) { return helpers.notAllowed(req, res); } - - async.waterfall([ - function (next) { - db.getObjectField('user:' + uid, 'rss_token', next); - }, - function (_token, next) { - if (token === _token) { - async.waterfall([ - function (next) { - privileges.categories.get(cid, uid, next); - }, - function (privileges, next) { - if (!privileges.read) { - return helpers.notAllowed(req, res); - } - next(); - }, - ], callback); - return; - } - user.auth.logAttempt(uid, req.ip, next); - }, - function () { - helpers.notAllowed(req, res); - }, - ], callback); + const userToken = await db.getObjectField('user:' + uid, 'rss_token'); + if (userToken !== token) { + await user.auth.logAttempt(uid, req.ip); + return helpers.notAllowed(req, res); + } + const userPrivileges = privileges.categories.get(cid, uid); + if (!userPrivileges.read) { + return helpers.notAllowed(req, res); + } + return true; } -function generateForTopic(req, res, callback) { +async function generateForTopic(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var tid = req.params.topic_id; - var userPrivileges; - var topic; - async.waterfall([ - function (next) { - async.parallel({ - privileges: function (next) { - privileges.topics.get(tid, req.uid, next); - }, - topic: function (next) { - topics.getTopicData(tid, next); - }, - }, next); - }, - function (results, next) { - if (!results.topic || (results.topic.deleted && !results.privileges.view_deleted)) { - return controllers404.send404(req, res); - } - userPrivileges = results.privileges; - topic = results.topic; - validateTokenIfRequiresLogin(!results.privileges['topics:read'], results.topic.cid, req, res, next); - }, - function (next) { - topics.getTopicWithPosts(topic, 'tid:' + tid + ':posts', req.uid || req.query.uid || 0, 0, 25, false, next); - }, - function (topicData) { - topics.modifyPostsByPrivilege(topicData, userPrivileges); - - var description = topicData.posts.length ? topicData.posts[0].content : ''; - var image_url = topicData.posts.length ? topicData.posts[0].picture : ''; - var author = topicData.posts.length ? topicData.posts[0].username : ''; - - var feed = new rss({ - title: utils.stripHTMLTags(topicData.title, utils.tags), - description: description, - feed_url: nconf.get('url') + '/topic/' + tid + '.rss', - site_url: nconf.get('url') + '/topic/' + topicData.slug, - image_url: image_url, - author: author, - ttl: 60, - }); - var dateStamp; - - if (topicData.posts.length > 0) { - feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString(); + const tid = req.params.topic_id; + + const [userPrivileges, topic] = await Promise.all([ + privileges.topics.get(tid, req.uid), + topics.getTopicData(tid), + ]); + + if (!topic || (topic.deleted && !userPrivileges.view_deleted)) { + return controllers404.send404(req, res); + } + + if (await validateTokenIfRequiresLogin(!userPrivileges['topics:read'], topic.cid, req, res)) { + const topicData = await topics.getTopicWithPosts(topic, 'tid:' + tid + ':posts', req.uid || req.query.uid || 0, 0, 25, false); + + topics.modifyPostsByPrivilege(topicData, userPrivileges); + + const feed = new rss({ + title: utils.stripHTMLTags(topicData.title, utils.tags), + description: topicData.posts.length ? topicData.posts[0].content : '', + feed_url: nconf.get('url') + '/topic/' + tid + '.rss', + site_url: nconf.get('url') + '/topic/' + topicData.slug, + image_url: topicData.posts.length ? topicData.posts[0].picture : '', + author: topicData.posts.length ? topicData.posts[0].username : '', + ttl: 60, + }); + + if (topicData.posts.length > 0) { + feed.pubDate = new Date(parseInt(topicData.posts[0].timestamp, 10)).toUTCString(); + } + + topicData.posts.forEach(function (postData) { + if (!postData.deleted) { + const dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString(); + + feed.item({ + title: 'Reply to ' + utils.stripHTMLTags(topicData.title, utils.tags) + ' on ' + dateStamp, + description: postData.content, + url: nconf.get('url') + '/post/' + postData.pid, + author: postData.user ? postData.user.username : '', + date: dateStamp, + }); } + }); - topicData.posts.forEach(function (postData) { - if (!postData.deleted) { - dateStamp = new Date(parseInt(parseInt(postData.edited, 10) === 0 ? postData.timestamp : postData.edited, 10)).toUTCString(); - - feed.item({ - title: 'Reply to ' + utils.stripHTMLTags(topicData.title, utils.tags) + ' on ' + dateStamp, - description: postData.content, - url: nconf.get('url') + '/post/' + postData.pid, - author: postData.user ? postData.user.username : '', - date: dateStamp, - }); - } - }); - - sendFeed(feed, res); - }, - ], callback); + sendFeed(feed, res); + } } -function generateForCategory(req, res, callback) { +async function generateForCategory(req, res, next) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var cid = req.params.category_id; - var category; + const cid = req.params.category_id; if (!parseInt(cid, 10)) { - return setImmediate(callback); + return next(); + } + + const [userPrivileges, category] = await Promise.all([ + privileges.categories.get(cid, req.uid), + categories.getCategoryById({ + cid: cid, + set: 'cid:' + cid + ':tids', + reverse: true, + start: 0, + stop: 25, + uid: req.uid || req.query.uid || 0, + }), + ]); + + if (!category) { + return next(); + } + + if (await validateTokenIfRequiresLogin(!userPrivileges.read, cid, req, res)) { + const feed = await generateTopicsFeed({ + uid: req.uid || req.query.uid || 0, + title: category.name, + description: category.description, + feed_url: '/category/' + cid + '.rss', + site_url: '/category/' + category.cid, + }, category.topics); + + sendFeed(feed, res); } - async.waterfall([ - function (next) { - async.parallel({ - privileges: function (next) { - privileges.categories.get(cid, req.uid, next); - }, - category: function (next) { - categories.getCategoryById({ - cid: cid, - set: 'cid:' + cid + ':tids', - reverse: true, - start: 0, - stop: 25, - uid: req.uid || req.query.uid || 0, - }, next); - }, - }, next); - }, - function (results, next) { - category = results.category; - if (!category) { - return callback(); - } - validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next); - }, - function (next) { - generateTopicsFeed({ - uid: req.uid || req.query.uid || 0, - title: category.name, - description: category.description, - feed_url: '/category/' + cid + '.rss', - site_url: '/category/' + category.cid, - }, category.topics, next); - }, - function (feed) { - sendFeed(feed, res); - }, - ], callback); } -function generateForTopics(req, res, next) { +async function generateForTopics(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } + let token = null; + if (req.query.token && req.query.uid) { + token = await db.getObjectField('user:' + req.query.uid, 'rss_token'); + } - async.waterfall([ - function (next) { - if (req.query.token && req.query.uid) { - db.getObjectField('user:' + req.query.uid, 'rss_token', next); - } else { - next(null, null); - } - }, - function (token, next) { - sendTopicsFeed({ - uid: token && token === req.query.token ? req.query.uid : req.uid, - title: 'Most recently created topics', - description: 'A list of topics that have been created recently', - feed_url: '/topics.rss', - useMainPost: true, - }, 'topics:tid', req, res, next); - }, - ], next); + await sendTopicsFeed({ + uid: token && token === req.query.token ? req.query.uid : req.uid, + title: 'Most recently created topics', + description: 'A list of topics that have been created recently', + feed_url: '/topics.rss', + useMainPost: true, + }, 'topics:tid', res); } -function generateForRecent(req, res, next) { +async function generateForRecent(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } + let token = null; + if (req.query.token && req.query.uid) { + token = await db.getObjectField('user:' + req.query.uid, 'rss_token'); + } - async.waterfall([ - function (next) { - if (req.query.token && req.query.uid) { - db.getObjectField('user:' + req.query.uid, 'rss_token', next); - } else { - next(null, null); - } - }, - function (token, next) { - sendTopicsFeed({ - uid: token && token === req.query.token ? req.query.uid : req.uid, - title: 'Recently Active Topics', - description: 'A list of topics that have been active within the past 24 hours', - feed_url: '/recent.rss', - site_url: '/recent', - }, 'topics:recent', req, res, next); - }, - ], next); + await sendTopicsFeed({ + uid: token && token === req.query.token ? req.query.uid : req.uid, + title: 'Recently Active Topics', + description: 'A list of topics that have been active within the past 24 hours', + feed_url: '/recent.rss', + site_url: '/recent', + }, 'topics:recent', res); } -function generateForTop(req, res, next) { +async function generateForTop(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var term = terms[req.params.term] || 'day'; - var uid; - async.waterfall([ - function (next) { - if (req.query.token && req.query.uid) { - db.getObjectField('user:' + req.query.uid, 'rss_token', next); - } else { - next(null, null); - } - }, - function (token, next) { - uid = token && token === req.query.token ? req.query.uid : req.uid; - - topics.getSortedTopics({ - uid: uid, - start: 0, - stop: 19, - term: term, - sort: 'votes', - }, next); - }, - function (result, next) { - generateTopicsFeed({ - uid: uid, - title: 'Top Voted Topics', - description: 'A list of topics that have received the most votes', - feed_url: '/top/' + (req.params.term || 'daily') + '.rss', - site_url: '/top/' + (req.params.term || 'daily'), - }, result.topics, next); - }, - function (feed) { - sendFeed(feed, res); - }, - ], next); + const term = terms[req.params.term] || 'day'; + + let token = null; + if (req.query.token && req.query.uid) { + token = await db.getObjectField('user:' + req.query.uid, 'rss_token'); + } + + const uid = token && token === req.query.token ? req.query.uid : req.uid; + + const result = await topics.getSortedTopics({ + uid: uid, + start: 0, + stop: 19, + term: term, + sort: 'votes', + }); + + const feed = await generateTopicsFeed({ + uid: uid, + title: 'Top Voted Topics', + description: 'A list of topics that have received the most votes', + feed_url: '/top/' + (req.params.term || 'daily') + '.rss', + site_url: '/top/' + (req.params.term || 'daily'), + }, result.topics); + + sendFeed(feed, res); } -function generateForPopular(req, res, next) { +async function generateForPopular(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var term = terms[req.params.term] || 'day'; - var uid; - async.waterfall([ - function (next) { - if (req.query.token && req.query.uid) { - db.getObjectField('user:' + req.query.uid, 'rss_token', next); - } else { - next(null, null); - } - }, - function (token, next) { - uid = token && token === req.query.token ? req.query.uid : req.uid; - - topics.getSortedTopics({ - uid: uid, - start: 0, - stop: 19, - term: term, - sort: 'posts', - }, next); - }, - function (result, next) { - generateTopicsFeed({ - uid: uid, - title: 'Popular Topics', - description: 'A list of topics that are sorted by post count', - feed_url: '/popular/' + (req.params.term || 'daily') + '.rss', - site_url: '/popular/' + (req.params.term || 'daily'), - }, result.topics, next); - }, - function (feed) { - sendFeed(feed, res); - }, - ], next); + const term = terms[req.params.term] || 'day'; + + let token = null; + if (req.query.token && req.query.uid) { + token = await db.getObjectField('user:' + req.query.uid, 'rss_token'); + } + + const uid = token && token === req.query.token ? req.query.uid : req.uid; + + const result = await topics.getSortedTopics({ + uid: uid, + start: 0, + stop: 19, + term: term, + sort: 'posts', + }); + + const feed = await generateTopicsFeed({ + uid: uid, + title: 'Popular Topics', + description: 'A list of topics that are sorted by post count', + feed_url: '/popular/' + (req.params.term || 'daily') + '.rss', + site_url: '/popular/' + (req.params.term || 'daily'), + }, result.topics); + sendFeed(feed, res); } -function sendTopicsFeed(options, set, req, res, next) { - var start = options.hasOwnProperty('start') ? options.start : 0; - var stop = options.hasOwnProperty('stop') ? options.stop : 19; - async.waterfall([ - function (next) { - topics.getTopicsFromSet(set, options.uid, start, stop, next); - }, - function (data, next) { - generateTopicsFeed(options, data.topics, next); - }, - function (feed) { - sendFeed(feed, res); - }, - ], next); +async function sendTopicsFeed(options, set, res) { + const start = options.hasOwnProperty('start') ? options.start : 0; + const stop = options.hasOwnProperty('stop') ? options.stop : 19; + const topicData = await topics.getTopicsFromSet(set, options.uid, start, stop); + const feed = await generateTopicsFeed(options, topicData.topics); + sendFeed(feed, res); } -function generateTopicsFeed(feedOptions, feedTopics, callback) { +async function generateTopicsFeed(feedOptions, feedTopics) { feedOptions.ttl = 60; feedOptions.feed_url = nconf.get('url') + feedOptions.feed_url; feedOptions.site_url = nconf.get('url') + feedOptions.site_url; feedTopics = feedTopics.filter(Boolean); - var feed = new rss(feedOptions); + const feed = new rss(feedOptions); if (feedTopics.length > 0) { feed.pubDate = new Date(feedTopics[0].lastposttime).toUTCString(); } - async.eachSeries(feedTopics, function (topicData, next) { - var feedItem = { + for (const topicData of feedTopics) { + /* eslint-disable no-await-in-loop */ + const feedItem = { title: utils.stripHTMLTags(topicData.title, utils.tags), url: nconf.get('url') + '/topic/' + topicData.slug, date: new Date(topicData.lastposttime).toUTCString(), @@ -368,89 +284,62 @@ function generateTopicsFeed(feedOptions, feedTopics, callback) { feedItem.description = topicData.teaser.content; feedItem.author = topicData.teaser.user.username; feed.item(feedItem); - return next(); + return; } - topics.getMainPost(topicData.tid, feedOptions.uid, function (err, mainPost) { - if (err) { - return next(err); - } - if (!mainPost) { - feed.item(feedItem); - return next(); - } - feedItem.description = mainPost.content; - feedItem.author = mainPost.user.username; + const mainPost = await topics.getMainPost(topicData.tid, feedOptions.uid); + if (!mainPost) { feed.item(feedItem); - next(); - }); - }, function (err) { - callback(err, feed); - }); + return; + } + feedItem.description = mainPost.content; + feedItem.author = mainPost.user && mainPost.user.username; + feed.item(feedItem); + } + return feed; } -function generateForRecentPosts(req, res, next) { +async function generateForRecentPosts(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - - async.waterfall([ - function (next) { - posts.getRecentPosts(req.uid, 0, 19, 'month', next); - }, - function (posts) { - var feed = generateForPostsFeed({ - title: 'Recent Posts', - description: 'A list of recent posts', - feed_url: '/recentposts.rss', - site_url: '/recentposts', - }, posts); - - sendFeed(feed, res); - }, - ], next); + const postData = await posts.getRecentPosts(req.uid, 0, 19, 'month'); + const feed = generateForPostsFeed({ + title: 'Recent Posts', + description: 'A list of recent posts', + feed_url: '/recentposts.rss', + site_url: '/recentposts', + }, postData); + + sendFeed(feed, res); } -function generateForCategoryRecentPosts(req, res, callback) { +async function generateForCategoryRecentPosts(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var cid = req.params.category_id; - var category; - var posts; - async.waterfall([ - function (next) { - async.parallel({ - privileges: function (next) { - privileges.categories.get(cid, req.uid, next); - }, - category: function (next) { - categories.getCategoryData(cid, next); - }, - posts: function (next) { - categories.getRecentReplies(cid, req.uid || req.query.uid || 0, 20, next); - }, - }, next); - }, - function (results, next) { - if (!results.category) { - return controllers404.send404(req, res); - } - category = results.category; - posts = results.posts; - validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next); - }, - function () { - var feed = generateForPostsFeed({ - title: category.name + ' Recent Posts', - description: 'A list of recent posts from ' + category.name, - feed_url: '/category/' + cid + '/recentposts.rss', - site_url: '/category/' + cid + '/recentposts', - }, posts); - - sendFeed(feed, res); - }, - ], callback); + const cid = req.params.category_id; + + const [userPrivileges, category, postData] = await Promise.all([ + privileges.categories.get(cid, req.uid), + categories.getCategoryData(cid), + categories.getRecentReplies(cid, req.uid || req.query.uid || 0, 20), + ]); + + if (!category) { + return controllers404.send404(req, res); + } + + if (await validateTokenIfRequiresLogin(!userPrivileges.read, cid, req, res)) { + const feed = generateForPostsFeed({ + title: category.name + ' Recent Posts', + description: 'A list of recent posts from ' + category.name, + feed_url: '/category/' + cid + '/recentposts.rss', + site_url: '/category/' + cid + '/recentposts', + }, postData); + + sendFeed(feed, res); + } } function generateForPostsFeed(feedOptions, posts) { @@ -477,45 +366,36 @@ function generateForPostsFeed(feedOptions, posts) { return feed; } -function generateForUserTopics(req, res, callback) { +async function generateForUserTopics(req, res, next) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var userslug = req.params.userslug; - - async.waterfall([ - function (next) { - user.getUidByUserslug(userslug, next); - }, - function (uid, next) { - if (!uid) { - return callback(); - } - user.getUserFields(uid, ['uid', 'username'], next); - }, - function (userData, next) { - sendTopicsFeed({ - uid: req.uid, - title: 'Topics by ' + userData.username, - description: 'A list of topics that are posted by ' + userData.username, - feed_url: '/user/' + userslug + '/topics.rss', - site_url: '/user/' + userslug + '/topics', - }, 'uid:' + userData.uid + ':topics', req, res, next); - }, - ], callback); + const userslug = req.params.userslug; + const uid = await user.getUidByUserslug(userslug); + if (!uid) { + return next(); + } + const userData = await user.getUserFields(uid, ['uid', 'username']); + await sendTopicsFeed({ + uid: req.uid, + title: 'Topics by ' + userData.username, + description: 'A list of topics that are posted by ' + userData.username, + feed_url: '/user/' + userslug + '/topics.rss', + site_url: '/user/' + userslug + '/topics', + }, 'uid:' + userData.uid + ':topics', res); } -function generateForTag(req, res, next) { +async function generateForTag(req, res) { if (meta.config['feeds:disableRSS']) { return controllers404.send404(req, res); } - var tag = validator.escape(String(req.params.tag)); - var page = parseInt(req.query.page, 10) || 1; - var topicsPerPage = meta.config.topicsPerPage || 20; - var start = Math.max(0, (page - 1) * topicsPerPage); - var stop = start + topicsPerPage - 1; - sendTopicsFeed({ + const tag = validator.escape(String(req.params.tag)); + const page = parseInt(req.query.page, 10) || 1; + const topicsPerPage = meta.config.topicsPerPage || 20; + const start = Math.max(0, (page - 1) * topicsPerPage); + const stop = start + topicsPerPage - 1; + await sendTopicsFeed({ uid: req.uid, title: 'Topics tagged with ' + tag, description: 'A list of topics that have been tagged with ' + tag, @@ -523,10 +403,10 @@ function generateForTag(req, res, next) { site_url: '/tags/' + tag, start: start, stop: stop, - }, 'tag:' + tag + ':topics', req, res, next); + }, 'tag:' + tag + ':topics', res); } function sendFeed(feed, res) { - var xml = feed.xml(); + const xml = feed.xml(); res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml); }