diff --git a/src/middleware/admin.js b/src/middleware/admin.js index bb1e83dc30..30c04d6db4 100644 --- a/src/middleware/admin.js +++ b/src/middleware/admin.js @@ -9,6 +9,7 @@ var semver = require('semver'); var user = require('../user'); var meta = require('../meta'); var plugins = require('../plugins'); +var utils = require('../../public/src/utils'); var versions = require('../admin/versions'); var controllers = { @@ -37,93 +38,67 @@ module.exports = function (middleware) { ], next); }; - middleware.admin.renderHeader = function (req, res, data, next) { + middleware.admin.renderHeader = async (req, res, data) => { var custom_header = { plugins: [], authentication: [], }; res.locals.config = res.locals.config || {}; - async.waterfall([ - function (next) { - async.parallel({ - userData: function (next) { - user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed'], next); - }, - scripts: function (next) { - getAdminScripts(next); - }, - custom_header: function (next) { - plugins.fireHook('filter:admin.header.build', custom_header, next); - }, - configs: function (next) { - meta.configs.list(next); - }, - latestVersion: function (next) { - versions.getLatestVersion(function (err, result) { - if (err) { - winston.error('[acp] Failed to fetch latest version', err); - } - - next(null, err ? null : result); - }); - }, - }, next); - }, - function (results, next) { - var userData = results.userData; - userData.uid = req.uid; - userData['email:confirmed'] = userData['email:confirmed'] === 1; - - var acpPath = req.path.slice(1).split('/'); - acpPath.forEach(function (path, i) { - acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); - }); - acpPath = acpPath.join(' > '); - - var version = nconf.get('version'); - - res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang; - var templateValues = { - config: res.locals.config, - configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }), - relative_path: res.locals.config.relative_path, - adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), - user: userData, - userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }), - plugins: results.custom_header.plugins, - authentication: results.custom_header.authentication, - scripts: results.scripts, - 'cache-buster': meta.config['cache-buster'] || '', - env: !!process.env.NODE_ENV, - title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel', - bodyClass: data.bodyClass, - version: version, - latestVersion: results.latestVersion, - upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version), - }; - - templateValues.template = { name: res.locals.template }; - templateValues.template[res.locals.template] = true; - - req.app.render('admin/header', templateValues, next); - }, - ], next); + + const results = await utils.promiseParallel({ + userData: user.getUserFields(req.uid, ['username', 'userslug', 'email', 'picture', 'email:confirmed']), + scripts: getAdminScripts(), + custom_header: plugins.fireHook('filter:admin.header.build', custom_header), + configs: meta.configs.list(), + latestVersion: versions.getLatestVersion(), + }); + + var userData = results.userData; + userData.uid = req.uid; + userData['email:confirmed'] = userData['email:confirmed'] === 1; + + var acpPath = req.path.slice(1).split('/'); + acpPath.forEach(function (path, i) { + acpPath[i] = path.charAt(0).toUpperCase() + path.slice(1); + }); + acpPath = acpPath.join(' > '); + + var version = nconf.get('version'); + + res.locals.config.userLang = res.locals.config.acpLang || res.locals.config.userLang; + var templateValues = { + config: res.locals.config, + configJSON: jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }), + relative_path: res.locals.config.relative_path, + adminConfigJSON: encodeURIComponent(JSON.stringify(results.configs)), + user: userData, + userJSON: jsesc(JSON.stringify(userData), { isScriptContext: true }), + plugins: results.custom_header.plugins, + authentication: results.custom_header.authentication, + scripts: results.scripts, + 'cache-buster': meta.config['cache-buster'] || '', + env: !!process.env.NODE_ENV, + title: (acpPath || 'Dashboard') + ' | NodeBB Admin Control Panel', + bodyClass: data.bodyClass, + version: version, + latestVersion: results.latestVersion, + upgradeAvailable: results.latestVersion && semver.gt(results.latestVersion, version), + }; + + templateValues.template = { name: res.locals.template }; + templateValues.template[res.locals.template] = true; + + return await req.app.renderAsync('admin/header', templateValues); }; - function getAdminScripts(callback) { - async.waterfall([ - function (next) { - plugins.fireHook('filter:admin.scripts.get', [], next); - }, - function (scripts, next) { - next(null, scripts.map(function (script) { - return { src: script }; - })); - }, - ], callback); + async function getAdminScripts() { + const scripts = await plugins.fireHook('filter:admin.scripts.get', []); + return scripts.map(function (script) { + return { src: script }; + }); } - middleware.admin.renderFooter = function (req, res, data, next) { - req.app.render('admin/footer', data, next); + middleware.admin.renderFooter = async function (req, res, data) { + return await req.app.renderAsync('admin/footer', data); }; }; diff --git a/src/middleware/header.js b/src/middleware/header.js index 6963a06565..221b2bfa1e 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -52,7 +52,7 @@ module.exports = function (middleware) { ], next); }; - middleware.generateHeader = function generateHeader(req, res, data, callback) { + async function generateHeader(req, res, data) { var registrationType = meta.config.registrationType || 'normal'; res.locals.config = res.locals.config || {}; var templateValues = { @@ -73,207 +73,162 @@ module.exports = function (middleware) { templateValues.configJSON = jsesc(JSON.stringify(res.locals.config), { isScriptContext: true }); - async.waterfall([ - function (next) { - async.parallel({ - isAdmin: function (next) { - user.isAdministrator(req.uid, next); - }, - isGlobalMod: function (next) { - user.isGlobalModerator(req.uid, next); - }, - isModerator: function (next) { - user.isModeratorOfAnyCategory(req.uid, next); - }, - privileges: function (next) { - privileges.global.get(req.uid, next); - }, - user: function (next) { - user.getUserData(req.uid, next); - }, - isEmailConfirmSent: function (next) { - if (!meta.config.requireEmailConfirmation || req.uid <= 0) { - return next(null, false); - } - db.get('uid:' + req.uid + ':confirm:email:sent', next); - }, - languageDirection: function (next) { - translator.translate('[[language:dir]]', res.locals.config.userLang, function (translated) { - next(null, translated); - }); - }, - browserTitle: function (next) { - translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title)), function (translated) { - next(null, translated); - }); - }, - navigation: async.apply(navigation.get, req.uid), - banned: async.apply(user.bans.isBanned, req.uid), - banReason: async.apply(user.bans.getReason, req.uid), + const results = await utils.promiseParallel({ + isAdmin: user.isAdministrator(req.uid), + isGlobalMod: user.isGlobalModerator(req.uid), + isModerator: user.isModeratorOfAnyCategory(req.uid), + privileges: privileges.global.get(req.uid), + user: user.getUserData(req.uid), + isEmailConfirmSent: (!meta.config.requireEmailConfirmation || req.uid <= 0) ? false : await db.get('uid:' + req.uid + ':confirm:email:sent'), + languageDirection: translator.translate('[[language:dir]]', res.locals.config.userLang), + browserTitle: translator.translate(controllers.helpers.buildTitle(translator.unescape(data.title))), + navigation: navigation.get(req.uid), + banned: user.bans.isBanned(req.uid), + banReason: user.bans.getReason(req.uid), + + unreadData: topics.getUnreadData({ uid: req.uid }), + unreadChatCount: messaging.getUnreadCount(req.uid), + unreadNotificationCount: user.notifications.getUnreadCount(req.uid), + }); - unreadData: async.apply(topics.getUnreadData, { uid: req.uid }), - unreadChatCount: async.apply(messaging.getUnreadCount, req.uid), - unreadNotificationCount: async.apply(user.notifications.getUnreadCount, req.uid), - }, next); - }, - function (results, next) { - if (results.banned) { - req.logout(); - return res.redirect('/'); - } + if (results.banned) { + req.logout(); + return res.redirect('/'); + } - const unreadData = { - '': {}, - new: {}, - watched: {}, - unreplied: {}, - }; - - results.user.unreadData = unreadData; - results.user.isAdmin = results.isAdmin; - results.user.isGlobalMod = results.isGlobalMod; - results.user.isMod = !!results.isModerator; - results.user.privileges = results.privileges; - results.user[results.user.status] = true; - - results.user.email = String(results.user.email); - results.user['email:confirmed'] = results.user['email:confirmed'] === 1; - results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; - - templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; - templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+ - - const unreadCounts = results.unreadData.counts; - var unreadCount = { - topic: unreadCounts[''] || 0, - newTopic: unreadCounts.new || 0, - watchedTopic: unreadCounts.watched || 0, - unrepliedTopic: unreadCounts.unreplied || 0, - chat: results.unreadChatCount || 0, - notification: results.unreadNotificationCount || 0, - }; - - Object.keys(unreadCount).forEach(function (key) { - if (unreadCount[key] > 99) { - unreadCount[key] = '99+'; - } - }); - - const tidsByFilter = results.unreadData.tidsByFilter; - results.navigation = results.navigation.map(function (item) { - function modifyNavItem(item, route, filter, content) { - if (item && item.originalRoute === route) { - unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); - item.content = content; - if (unreadCounts[filter] > 0) { - item.iconClass += ' unread-count'; - } - } + const unreadData = { + '': {}, + new: {}, + watched: {}, + unreplied: {}, + }; + + results.user.unreadData = unreadData; + results.user.isAdmin = results.isAdmin; + results.user.isGlobalMod = results.isGlobalMod; + results.user.isMod = !!results.isModerator; + results.user.privileges = results.privileges; + results.user[results.user.status] = true; + + results.user.email = String(results.user.email); + results.user['email:confirmed'] = results.user['email:confirmed'] === 1; + results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; + + templateValues.bootswatchSkin = (parseInt(meta.config.disableCustomUserSkins, 10) !== 1 ? res.locals.config.bootswatchSkin : '') || meta.config.bootswatchSkin || ''; + templateValues.config.bootswatchSkin = templateValues.bootswatchSkin || 'noskin'; // TODO remove in v1.12.0+ + + const unreadCounts = results.unreadData.counts; + const unreadCount = { + topic: unreadCounts[''] || 0, + newTopic: unreadCounts.new || 0, + watchedTopic: unreadCounts.watched || 0, + unrepliedTopic: unreadCounts.unreplied || 0, + chat: results.unreadChatCount || 0, + notification: results.unreadNotificationCount || 0, + }; + + Object.keys(unreadCount).forEach(function (key) { + if (unreadCount[key] > 99) { + unreadCount[key] = '99+'; + } + }); + + const tidsByFilter = results.unreadData.tidsByFilter; + results.navigation = results.navigation.map(function (item) { + function modifyNavItem(item, route, filter, content) { + if (item && item.originalRoute === route) { + unreadData[filter] = _.zipObject(tidsByFilter[filter], tidsByFilter[filter].map(() => true)); + item.content = content; + if (unreadCounts[filter] > 0) { + item.iconClass += ' unread-count'; } - modifyNavItem(item, '/unread', '', unreadCount.topic); - modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); - modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); - modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); - return item; - }); - - templateValues.browserTitle = results.browserTitle; - templateValues.navigation = results.navigation; - templateValues.unreadCount = unreadCount; - templateValues.isAdmin = results.user.isAdmin; - templateValues.isGlobalMod = results.user.isGlobalMod; - templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; - templateValues.canChat = results.canChat && meta.config.disableChat !== 1; - templateValues.user = results.user; - templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true }); - templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS; - templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : ''; - templateValues.useCustomHTML = meta.config.useCustomHTML; - templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : ''; - templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin; - templateValues.defaultLang = meta.config.defaultLang || 'en-GB'; - templateValues.userLang = res.locals.config.userLang; - templateValues.languageDirection = results.languageDirection; - - templateValues.template = { name: res.locals.template }; - templateValues.template[res.locals.template] = true; - - if (data.hasOwnProperty('_header')) { - templateValues.metaTags = data._header.tags.meta; - templateValues.linkTags = data._header.tags.link; } + } + modifyNavItem(item, '/unread', '', unreadCount.topic); + modifyNavItem(item, '/unread?filter=new', 'new', unreadCount.newTopic); + modifyNavItem(item, '/unread?filter=watched', 'watched', unreadCount.watchedTopic); + modifyNavItem(item, '/unread?filter=unreplied', 'unreplied', unreadCount.unrepliedTopic); + return item; + }); - if (req.route && req.route.path === '/') { - modifyTitle(templateValues); - } + templateValues.browserTitle = results.browserTitle; + templateValues.navigation = results.navigation; + templateValues.unreadCount = unreadCount; + templateValues.isAdmin = results.user.isAdmin; + templateValues.isGlobalMod = results.user.isGlobalMod; + templateValues.showModMenu = results.user.isAdmin || results.user.isGlobalMod || results.user.isMod; + templateValues.canChat = results.canChat && meta.config.disableChat !== 1; + templateValues.user = results.user; + templateValues.userJSON = jsesc(JSON.stringify(results.user), { isScriptContext: true }); + templateValues.useCustomCSS = meta.config.useCustomCSS && meta.config.customCSS; + templateValues.customCSS = templateValues.useCustomCSS ? (meta.config.renderedCustomCSS || '') : ''; + templateValues.useCustomHTML = meta.config.useCustomHTML; + templateValues.customHTML = templateValues.useCustomHTML ? meta.config.customHTML : ''; + templateValues.maintenanceHeader = meta.config.maintenanceMode && !results.isAdmin; + templateValues.defaultLang = meta.config.defaultLang || 'en-GB'; + templateValues.userLang = res.locals.config.userLang; + templateValues.languageDirection = results.languageDirection; + + templateValues.template = { name: res.locals.template }; + templateValues.template[res.locals.template] = true; + + if (data.hasOwnProperty('_header')) { + templateValues.metaTags = data._header.tags.meta; + templateValues.linkTags = data._header.tags.link; + } - plugins.fireHook('filter:middleware.renderHeader', { - req: req, - res: res, - templateValues: templateValues, - data: data, - }, next); - }, - ], function (err, data) { - callback(err, data.templateValues); + if (req.route && req.route.path === '/') { + modifyTitle(templateValues); + } + + const hookReturn = await plugins.fireHook('filter:middleware.renderHeader', { + req: req, + res: res, + templateValues: templateValues, + data: data, }); - }; - middleware.renderHeader = function renderHeader(req, res, data, callback) { - async.waterfall([ - async.apply(middleware.generateHeader, req, res, data), - function (templateValues, next) { - req.app.render('header', templateValues, next); - }, - ], callback); + return hookReturn.templateValues; + } + + middleware.renderHeader = async function renderHeader(req, res, data) { + return await req.app.renderAsync('header', await generateHeader(req, res, data)); }; - middleware.renderFooter = function renderFooter(req, res, data, callback) { - async.waterfall([ - function (next) { - plugins.fireHook('filter:middleware.renderFooter', { - req: req, - res: res, - templateValues: data, - }, next); - }, - function (data, next) { - async.parallel({ - scripts: async.apply(plugins.fireHook, 'filter:scripts.get', []), - timeagoLocale: (next) => { - async.waterfall([ - async.apply(languages.listCodes), - (languageCodes, next) => { - const userLang = res.locals.config.userLang; - const timeagoCode = utils.userLangToTimeagoCode(userLang); - - if (languageCodes.includes(userLang) && languages.timeagoCodes.includes(timeagoCode)) { - const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + timeagoCode + '.js'; - next(null, (nconf.get('relative_path') + '/assets' + pathToLocaleFile)); - } else { - next(null, false); - } - }, - ], next); - }, - }, function (err, results) { - next(err, data, results); - }); - }, - function (data, results, next) { - if (results.timeagoLocale) { - results.scripts.push(results.timeagoLocale); + middleware.renderFooter = async function renderFooter(req, res, templateValues) { + const data = await plugins.fireHook('filter:middleware.renderFooter', { + req: req, + res: res, + templateValues: templateValues, + }); + + const results = await utils.promiseParallel({ + scripts: plugins.fireHook('filter:scripts.get', []), + timeagoLocale: (async () => { + const languageCodes = await languages.listCodes(); + const userLang = res.locals.config.userLang; + const timeagoCode = utils.userLangToTimeagoCode(userLang); + + if (languageCodes.includes(userLang) && languages.timeagoCodes.includes(timeagoCode)) { + const pathToLocaleFile = '/vendor/jquery/timeago/locales/jquery.timeago.' + timeagoCode + '.js'; + return nconf.get('relative_path') + '/assets' + pathToLocaleFile; } - data.templateValues.scripts = results.scripts.map(function (script) { - return { src: script }; - }); - - data.templateValues.useCustomJS = meta.config.useCustomJS; - data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : ''; - data.templateValues.isSpider = req.uid === -1; - req.app.render('footer', data.templateValues, next); - }, - ], callback); + return false; + })(), + }); + + if (results.timeagoLocale) { + results.scripts.push(results.timeagoLocale); + } + data.templateValues.scripts = results.scripts.map(function (script) { + return { src: script }; + }); + + data.templateValues.useCustomJS = meta.config.useCustomJS; + data.templateValues.customJS = data.templateValues.useCustomJS ? meta.config.customJS : ''; + data.templateValues.isSpider = req.uid === -1; + + return await req.app.renderAsync('footer', data.templateValues); }; function modifyTitle(obj) { diff --git a/src/middleware/render.js b/src/middleware/render.js index a6257d04a8..1e9d20b0b0 100644 --- a/src/middleware/render.js +++ b/src/middleware/render.js @@ -12,8 +12,6 @@ const widgets = require('../widgets'); const utils = require('../utils'); module.exports = function (middleware) { - const renderHeaderFooterAsync = util.promisify(renderHeaderFooter); - middleware.processRender = function processRender(req, res, next) { // res.render post-processing, modified from here: https://gist.github.com/mrlannigan/5051687 const render = res.render; @@ -63,9 +61,9 @@ module.exports = function (middleware) { const renderAsync = util.promisify((templateToRender, options, next) => render.call(self, templateToRender, options, next)); const results = await utils.promiseParallel({ - header: renderHeaderFooterAsync('renderHeader', req, res, options), + header: renderHeaderFooter('renderHeader', req, res, options), content: renderAsync(templateToRender, options), - footer: renderHeaderFooterAsync('renderFooter', req, res, options), + footer: renderHeaderFooter('renderFooter', req, res, options), }); const str = results.header + @@ -89,14 +87,13 @@ module.exports = function (middleware) { next(); }; - function renderHeaderFooter(method, req, res, options, next) { + async function renderHeaderFooter(method, req, res, options) { if (res.locals.renderHeader) { - middleware[method](req, res, options, next); + return await middleware[method](req, res, options); } else if (res.locals.renderAdminHeader) { - middleware.admin[method](req, res, options, next); - } else { - next(null, ''); + return await middleware.admin[method](req, res, options); } + return ''; } async function translate(str, req, res) {