From e1c6c3b267945b0109c2675ec2bf3fca6ed9f236 Mon Sep 17 00:00:00 2001 From: psychobunny Date: Tue, 3 Mar 2020 16:33:13 -0500 Subject: [PATCH] refactor: reorganized socket.io admin modules --- src/socket.io/admin.js | 303 ++----------------------------- src/socket.io/admin/analytics.js | 47 +++++ src/socket.io/admin/config.js | 55 ++++++ src/socket.io/admin/digest.js | 24 +++ src/socket.io/admin/email.js | 69 +++++++ src/socket.io/admin/errors.js | 8 + src/socket.io/admin/logs.js | 12 ++ src/socket.io/admin/plugins.js | 43 +++++ src/socket.io/admin/settings.js | 24 +++ src/socket.io/admin/themes.js | 24 +++ src/socket.io/admin/uploads.js | 16 ++ src/socket.io/admin/widgets.js | 13 ++ 12 files changed, 347 insertions(+), 291 deletions(-) create mode 100644 src/socket.io/admin/analytics.js create mode 100644 src/socket.io/admin/config.js create mode 100644 src/socket.io/admin/digest.js create mode 100644 src/socket.io/admin/email.js create mode 100644 src/socket.io/admin/errors.js create mode 100644 src/socket.io/admin/logs.js create mode 100644 src/socket.io/admin/plugins.js create mode 100644 src/socket.io/admin/settings.js create mode 100644 src/socket.io/admin/themes.js create mode 100644 src/socket.io/admin/uploads.js create mode 100644 src/socket.io/admin/widgets.js diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js index 4c6a748be8..05c6c975b8 100644 --- a/src/socket.io/admin.js +++ b/src/socket.io/admin.js @@ -1,48 +1,36 @@ 'use strict'; -const async = require('async'); const winston = require('winston'); -const fs = require('fs'); -const path = require('path'); -const nconf = require('nconf'); const meta = require('../meta'); -const plugins = require('../plugins'); -const widgets = require('../widgets'); const user = require('../user'); -const userDigest = require('../user/digest'); -const userEmail = require('../user/email'); -const logger = require('../logger'); const events = require('../events'); -const notifications = require('../notifications'); -const emailer = require('../emailer'); const db = require('../database'); -const analytics = require('../analytics'); const websockets = require('../socket.io/index'); const index = require('./index'); const getAdminSearchDict = require('../admin/search').getDictionary; -const utils = require('../../public/src/utils'); const SocketAdmin = module.exports; SocketAdmin.user = require('./admin/user'); SocketAdmin.categories = require('./admin/categories'); +SocketAdmin.settings = require('./admin/settings'); SocketAdmin.groups = require('./admin/groups'); SocketAdmin.tags = require('./admin/tags'); SocketAdmin.rewards = require('./admin/rewards'); SocketAdmin.navigation = require('./admin/navigation'); SocketAdmin.rooms = require('./admin/rooms'); SocketAdmin.social = require('./admin/social'); -SocketAdmin.themes = {}; -SocketAdmin.plugins = {}; -SocketAdmin.widgets = {}; -SocketAdmin.config = {}; -SocketAdmin.settings = {}; -SocketAdmin.email = {}; -SocketAdmin.analytics = {}; -SocketAdmin.logs = {}; -SocketAdmin.errors = {}; -SocketAdmin.uploads = {}; -SocketAdmin.digest = {}; +SocketAdmin.themes = require('./admin/themes'); +SocketAdmin.plugins = require('./admin/plugins'); +SocketAdmin.widgets = require('./admin/widgets'); +SocketAdmin.config = require('./admin/config'); +SocketAdmin.settings = require('./admin/settings'); +SocketAdmin.email = require('./admin/email'); +SocketAdmin.analytics = require('./admin/analytics'); +SocketAdmin.logs = require('./admin/logs'); +SocketAdmin.errors = require('./admin/errors'); +SocketAdmin.uploads = require('./admin/uploads'); +SocketAdmin.digest = require('./admin/digest'); SocketAdmin.before = async function (socket, method) { const isAdmin = await user.isAdministrator(socket.uid); @@ -89,246 +77,6 @@ SocketAdmin.fireEvent = function (socket, data, callback) { callback(); }; -SocketAdmin.themes.getInstalled = function (socket, data, callback) { - meta.themes.get(callback); -}; - -SocketAdmin.themes.set = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - if (data.type === 'local') { - await widgets.reset(); - } - - data.ip = socket.ip; - data.uid = socket.uid; - - await meta.themes.set(data); -}; - -SocketAdmin.plugins.toggleActive = async function (socket, plugin_id) { - require('../posts/cache').reset(); - const data = await plugins.toggleActive(plugin_id); - await events.log({ - type: 'plugin-' + (data.active ? 'activate' : 'deactivate'), - text: plugin_id, - uid: socket.uid, - }); - return data; -}; - -SocketAdmin.plugins.toggleInstall = async function (socket, data) { - require('../posts/cache').reset(); - const pluginData = await plugins.toggleInstall(data.id, data.version); - await events.log({ - type: 'plugin-' + (pluginData.installed ? 'install' : 'uninstall'), - text: data.id, - version: data.version, - uid: socket.uid, - }); - return pluginData; -}; - -SocketAdmin.plugins.getActive = function (socket, data, callback) { - plugins.getActive(callback); -}; - -SocketAdmin.plugins.orderActivePlugins = async function (socket, data) { - data = data.filter(plugin => plugin && plugin.name); - await Promise.all(data.map(plugin => db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name))); -}; - -SocketAdmin.plugins.upgrade = function (socket, data, callback) { - plugins.upgrade(data.id, data.version, callback); -}; - -SocketAdmin.widgets.set = function (socket, data, callback) { - if (!Array.isArray(data)) { - return callback(new Error('[[error:invalid-data]]')); - } - - async.eachSeries(data, widgets.setArea, callback); -}; - -SocketAdmin.config.set = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - const _data = {}; - _data[data.key] = data.value; - await SocketAdmin.config.setMultiple(socket, _data); -}; - -SocketAdmin.config.setMultiple = async function (socket, data) { - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - - const changes = {}; - const newData = meta.configs.serialize(data); - const oldData = meta.configs.serialize(meta.config); - Object.keys(newData).forEach(function (key) { - if (newData[key] !== oldData[key]) { - changes[key] = newData[key]; - changes[key + '_old'] = meta.config[key]; - } - }); - await meta.configs.setMultiple(data); - for (const field in data) { - if (data.hasOwnProperty(field)) { - const setting = { - key: field, - value: data[field], - }; - plugins.fireHook('action:config.set', setting); - logger.monitorConfig({ io: index.server }, setting); - } - } - if (Object.keys(changes).length) { - changes.type = 'config-change'; - changes.uid = socket.uid; - changes.ip = socket.ip; - await events.log(changes); - } -}; - -SocketAdmin.config.remove = function (socket, key, callback) { - meta.configs.remove(key, callback); -}; - -SocketAdmin.settings.get = function (socket, data, callback) { - meta.settings.get(data.hash, callback); -}; - -SocketAdmin.settings.set = async function (socket, data) { - await meta.settings.set(data.hash, data.values); - const eventData = data.values; - eventData.type = 'settings-change'; - eventData.uid = socket.uid; - eventData.ip = socket.ip; - eventData.hash = data.hash; - await events.log(eventData); -}; - -SocketAdmin.settings.clearSitemapCache = function (socket, data, callback) { - require('../sitemap').clearCache(); - callback(); -}; - -SocketAdmin.email.test = function (socket, data, callback) { - const payload = { - subject: '[[email:test-email.subject]]', - }; - - switch (data.template) { - case 'digest': - userDigest.execute({ - interval: 'alltime', - subscribers: [socket.uid], - }, callback); - break; - - case 'banned': - Object.assign(payload, { - username: 'test-user', - until: utils.toISOString(Date.now()), - reason: 'Test Reason', - }); - emailer.send(data.template, socket.uid, payload, callback); - break; - - case 'welcome': - userEmail.sendValidationEmail(socket.uid, { - force: 1, - }, callback); - break; - - case 'notification': - async.waterfall([ - function (next) { - notifications.create({ - type: 'test', - bodyShort: '[[email:notif.test.short]]', - bodyLong: '[[email:notif.test.long]]', - nid: 'uid:' + socket.uid + ':test', - path: '/', - from: socket.uid, - }, next); - }, - function (notifObj, next) { - emailer.send('notification', socket.uid, { - path: notifObj.path, - subject: utils.stripHTMLTags(notifObj.subject || '[[notifications:new_notification]]'), - intro: utils.stripHTMLTags(notifObj.bodyShort), - body: notifObj.bodyLong || '', - notification: notifObj, - showUnsubscribe: true, - }, next); - }, - ], callback); - break; - - default: - emailer.send(data.template, socket.uid, payload, callback); - break; - } -}; - -SocketAdmin.analytics.get = function (socket, data, callback) { - if (!data || !data.graph || !data.units) { - return callback(new Error('[[error:invalid-data]]')); - } - - // Default returns views from past 24 hours, by hour - if (!data.amount) { - if (data.units === 'days') { - data.amount = 30; - } else { - data.amount = 24; - } - } - const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; - if (data.graph === 'traffic') { - async.parallel({ - uniqueVisitors: function (next) { - getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); - }, - pageviews: function (next) { - getStats('analytics:pageviews', data.until || Date.now(), data.amount, next); - }, - pageviewsRegistered: function (next) { - getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount, next); - }, - pageviewsGuest: function (next) { - getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount, next); - }, - pageviewsBot: function (next) { - getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount, next); - }, - summary: function (next) { - analytics.getSummary(next); - }, - }, function (err, data) { - data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); }); - data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews(); - callback(err, data); - }); - } -}; - -SocketAdmin.logs.get = function (socket, data, callback) { - meta.logs.get(callback); -}; - -SocketAdmin.logs.clear = function (socket, data, callback) { - meta.logs.clear(callback); -}; - -SocketAdmin.errors.clear = function (socket, data, callback) { - meta.errors.clear(callback); -}; - SocketAdmin.getSearchDict = async function (socket) { const settings = await user.getSettings(socket.uid); const lang = settings.userLang || meta.config.defaultLang || 'en-GB'; @@ -344,31 +92,4 @@ SocketAdmin.reloadAllSessions = function (socket, data, callback) { callback(); }; -SocketAdmin.uploads.delete = function (socket, pathToFile, callback) { - pathToFile = path.join(nconf.get('upload_path'), pathToFile); - if (!pathToFile.startsWith(nconf.get('upload_path'))) { - return callback(new Error('[[error:invalid-path]]')); - } - - fs.unlink(pathToFile, callback); -}; - -SocketAdmin.digest.resend = async (socket, data) => { - const uid = data.uid; - const interval = data.action.startsWith('resend-') ? data.action.slice(7) : await userDigest.getUsersInterval(uid); - - if (!interval && meta.config.dailyDigestFreq === 'off') { - throw new Error('[[error:digest-not-enabled]]'); - } - - if (uid) { - await userDigest.execute({ - interval: interval || meta.config.dailyDigestFreq, - subscribers: [uid], - }); - } else { - await userDigest.execute({ interval: interval }); - } -}; - require('../promisify')(SocketAdmin); diff --git a/src/socket.io/admin/analytics.js b/src/socket.io/admin/analytics.js new file mode 100644 index 0000000000..16cc82ec6d --- /dev/null +++ b/src/socket.io/admin/analytics.js @@ -0,0 +1,47 @@ +'use strict'; + +const async = require('async'); +const analytics = require('../../analytics'); +const Analytics = module.exports; + +Analytics.get = function (socket, data, callback) { + if (!data || !data.graph || !data.units) { + return callback(new Error('[[error:invalid-data]]')); + } + + // Default returns views from past 24 hours, by hour + if (!data.amount) { + if (data.units === 'days') { + data.amount = 30; + } else { + data.amount = 24; + } + } + const getStats = data.units === 'days' ? analytics.getDailyStatsForSet : analytics.getHourlyStatsForSet; + if (data.graph === 'traffic') { + async.parallel({ + uniqueVisitors: function (next) { + getStats('analytics:uniquevisitors', data.until || Date.now(), data.amount, next); + }, + pageviews: function (next) { + getStats('analytics:pageviews', data.until || Date.now(), data.amount, next); + }, + pageviewsRegistered: function (next) { + getStats('analytics:pageviews:registered', data.until || Date.now(), data.amount, next); + }, + pageviewsGuest: function (next) { + getStats('analytics:pageviews:guest', data.until || Date.now(), data.amount, next); + }, + pageviewsBot: function (next) { + getStats('analytics:pageviews:bot', data.until || Date.now(), data.amount, next); + }, + summary: function (next) { + analytics.getSummary(next); + }, + }, function (err, data) { + data.pastDay = data.pageviews.reduce(function (a, b) { return parseInt(a, 10) + parseInt(b, 10); }); + data.pageviews[data.pageviews.length - 1] = parseInt(data.pageviews[data.pageviews.length - 1], 10) + analytics.getUnwrittenPageviews(); + callback(err, data); + }); + } +}; diff --git a/src/socket.io/admin/config.js b/src/socket.io/admin/config.js new file mode 100644 index 0000000000..63ab6d149c --- /dev/null +++ b/src/socket.io/admin/config.js @@ -0,0 +1,55 @@ +'use strict'; + +const meta = require('../../meta'); +const plugins = require('../../plugins'); +const logger = require('../../logger'); +const events = require('../../events'); +const index = require('../index'); + +const Config = module.exports; + +Config.set = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + const _data = {}; + _data[data.key] = data.value; + await Config.setMultiple(socket, _data); +}; + +Config.setMultiple = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + + const changes = {}; + const newData = meta.configs.serialize(data); + const oldData = meta.configs.serialize(meta.config); + Object.keys(newData).forEach(function (key) { + if (newData[key] !== oldData[key]) { + changes[key] = newData[key]; + changes[key + '_old'] = meta.config[key]; + } + }); + await meta.configs.setMultiple(data); + for (const field in data) { + if (data.hasOwnProperty(field)) { + const setting = { + key: field, + value: data[field], + }; + plugins.fireHook('action:config.set', setting); + logger.monitorConfig({ io: index.server }, setting); + } + } + if (Object.keys(changes).length) { + changes.type = 'config-change'; + changes.uid = socket.uid; + changes.ip = socket.ip; + await events.log(changes); + } +}; + +Config.remove = function (socket, key, callback) { + meta.configs.remove(key, callback); +}; diff --git a/src/socket.io/admin/digest.js b/src/socket.io/admin/digest.js new file mode 100644 index 0000000000..b6427aced6 --- /dev/null +++ b/src/socket.io/admin/digest.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const userDigest = require('../../user/digest'); + +const Digest = module.exports; + +Digest.resend = async (socket, data) => { + const uid = data.uid; + const interval = data.action.startsWith('resend-') ? data.action.slice(7) : await userDigest.getUsersInterval(uid); + + if (!interval && meta.config.dailyDigestFreq === 'off') { + throw new Error('[[error:digest-not-enabled]]'); + } + + if (uid) { + await userDigest.execute({ + interval: interval || meta.config.dailyDigestFreq, + subscribers: [uid], + }); + } else { + await userDigest.execute({ interval: interval }); + } +}; diff --git a/src/socket.io/admin/email.js b/src/socket.io/admin/email.js new file mode 100644 index 0000000000..911613ba99 --- /dev/null +++ b/src/socket.io/admin/email.js @@ -0,0 +1,69 @@ +'use strict'; + +const async = require('async'); +const userDigest = require('../../user/digest'); +const userEmail = require('../../user/email'); +const notifications = require('../../notifications'); +const emailer = require('../../emailer'); +const utils = require('../../../public/src/utils'); + +const Email = module.exports; + +Email.test = function (socket, data, callback) { + const payload = { + subject: '[[email:test-email.subject]]', + }; + + switch (data.template) { + case 'digest': + userDigest.execute({ + interval: 'alltime', + subscribers: [socket.uid], + }, callback); + break; + + case 'banned': + Object.assign(payload, { + username: 'test-user', + until: utils.toISOString(Date.now()), + reason: 'Test Reason', + }); + emailer.send(data.template, socket.uid, payload, callback); + break; + + case 'welcome': + userEmail.sendValidationEmail(socket.uid, { + force: 1, + }, callback); + break; + + case 'notification': + async.waterfall([ + function (next) { + notifications.create({ + type: 'test', + bodyShort: '[[email:notif.test.short]]', + bodyLong: '[[email:notif.test.long]]', + nid: 'uid:' + socket.uid + ':test', + path: '/', + from: socket.uid, + }, next); + }, + function (notifObj, next) { + emailer.send('notification', socket.uid, { + path: notifObj.path, + subject: utils.stripHTMLTags(notifObj.subject || '[[notifications:new_notification]]'), + intro: utils.stripHTMLTags(notifObj.bodyShort), + body: notifObj.bodyLong || '', + notification: notifObj, + showUnsubscribe: true, + }, next); + }, + ], callback); + break; + + default: + emailer.send(data.template, socket.uid, payload, callback); + break; + } +}; diff --git a/src/socket.io/admin/errors.js b/src/socket.io/admin/errors.js new file mode 100644 index 0000000000..ba19ae8339 --- /dev/null +++ b/src/socket.io/admin/errors.js @@ -0,0 +1,8 @@ +'use strict'; + +const meta = require('../../meta'); +const Errors = module.exports; + +Errors.clear = function (socket, data, callback) { + meta.errors.clear(callback); +}; diff --git a/src/socket.io/admin/logs.js b/src/socket.io/admin/logs.js new file mode 100644 index 0000000000..22bcbe71a7 --- /dev/null +++ b/src/socket.io/admin/logs.js @@ -0,0 +1,12 @@ +'use strict'; + +const meta = require('../../meta'); +const Logs = module.exports; + +Logs.get = function (socket, data, callback) { + meta.logs.get(callback); +}; + +Logs.clear = function (socket, data, callback) { + meta.logs.clear(callback); +}; diff --git a/src/socket.io/admin/plugins.js b/src/socket.io/admin/plugins.js new file mode 100644 index 0000000000..16f112130d --- /dev/null +++ b/src/socket.io/admin/plugins.js @@ -0,0 +1,43 @@ +'use strict'; + +const plugins = require('../../plugins'); +const events = require('../../events'); +const db = require('../../database'); + +const Plugins = module.exports; + +Plugins.toggleActive = async function (socket, plugin_id) { + require('../../posts/cache').reset(); + const data = await plugins.toggleActive(plugin_id); + await events.log({ + type: 'plugin-' + (data.active ? 'activate' : 'deactivate'), + text: plugin_id, + uid: socket.uid, + }); + return data; +}; + +Plugins.toggleInstall = async function (socket, data) { + require('../../posts/cache').reset(); + const pluginData = await plugins.toggleInstall(data.id, data.version); + await events.log({ + type: 'plugin-' + (pluginData.installed ? 'install' : 'uninstall'), + text: data.id, + version: data.version, + uid: socket.uid, + }); + return pluginData; +}; + +Plugins.getActive = function (socket, data, callback) { + plugins.getActive(callback); +}; + +Plugins.orderActivePlugins = async function (socket, data) { + data = data.filter(plugin => plugin && plugin.name); + await Promise.all(data.map(plugin => db.sortedSetAdd('plugins:active', plugin.order || 0, plugin.name))); +}; + +Plugins.upgrade = function (socket, data, callback) { + plugins.upgrade(data.id, data.version, callback); +}; diff --git a/src/socket.io/admin/settings.js b/src/socket.io/admin/settings.js new file mode 100644 index 0000000000..063c8ca4ee --- /dev/null +++ b/src/socket.io/admin/settings.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const events = require('../../events'); +const Settings = module.exports; + +Settings.get = function (socket, data, callback) { + meta.settings.get(data.hash, callback); +}; + +Settings.set = async function (socket, data) { + await meta.settings.set(data.hash, data.values); + const eventData = data.values; + eventData.type = 'settings-change'; + eventData.uid = socket.uid; + eventData.ip = socket.ip; + eventData.hash = data.hash; + await events.log(eventData); +}; + +Settings.clearSitemapCache = function (socket, data, callback) { + require('../../sitemap').clearCache(); + callback(); +}; diff --git a/src/socket.io/admin/themes.js b/src/socket.io/admin/themes.js new file mode 100644 index 0000000000..1a6b4bd23b --- /dev/null +++ b/src/socket.io/admin/themes.js @@ -0,0 +1,24 @@ +'use strict'; + +const meta = require('../../meta'); +const widgets = require('../../widgets'); + +const Themes = module.exports; + +Themes.getInstalled = function (socket, data, callback) { + meta.themes.get(callback); +}; + +Themes.set = async function (socket, data) { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + if (data.type === 'local') { + await widgets.reset(); + } + + data.ip = socket.ip; + data.uid = socket.uid; + + await meta.themes.set(data); +}; diff --git a/src/socket.io/admin/uploads.js b/src/socket.io/admin/uploads.js new file mode 100644 index 0000000000..d34edba503 --- /dev/null +++ b/src/socket.io/admin/uploads.js @@ -0,0 +1,16 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const nconf = require('nconf'); + +const Uploads = module.exports; + +Uploads.delete = function (socket, pathToFile, callback) { + pathToFile = path.join(nconf.get('upload_path'), pathToFile); + if (!pathToFile.startsWith(nconf.get('upload_path'))) { + return callback(new Error('[[error:invalid-path]]')); + } + + fs.unlink(pathToFile, callback); +}; diff --git a/src/socket.io/admin/widgets.js b/src/socket.io/admin/widgets.js new file mode 100644 index 0000000000..58a2a019d6 --- /dev/null +++ b/src/socket.io/admin/widgets.js @@ -0,0 +1,13 @@ +'use strict'; + +const async = require('async'); +const widgets = require('../../widgets'); +const Widgets = module.exports; + +Widgets.set = function (socket, data, callback) { + if (!Array.isArray(data)) { + return callback(new Error('[[error:invalid-data]]')); + } + + async.eachSeries(data, widgets.setArea, callback); +};