replaced eslint:recommended with prettier

This commit is contained in:
Wyatt Johnson
2018-01-11 20:00:34 -07:00
parent d56c19016a
commit 0abc2ca243
649 changed files with 16235 additions and 13008 deletions
+9 -4
View File
@@ -6,7 +6,7 @@
require('./util');
const program = require('commander');
const {head, map} = require('lodash');
const { head, map } = require('lodash');
const Matcher = require('did-you-mean');
program
@@ -19,7 +19,10 @@ program
.command('users', 'work with the application auth')
.command('migration', 'provides utilities for migrating the database')
.command('verify', 'provides utilities for performing data verification')
.command('plugins', 'provides utilities for interacting with the plugin system')
.command(
'plugins',
'provides utilities for interacting with the plugin system'
)
.parse(process.argv);
// If the command wasn't found, output help.
@@ -29,9 +32,11 @@ if (!commands.includes(command)) {
const m = new Matcher(commands);
const similarCommands = m.list(command);
console.error(`cli '${command}' is not a talk cli command. See 'cli --help'.`);
console.error(
`cli '${command}' is not a talk cli command. See 'cli --help'.`
);
if (similarCommands.length > 0) {
const sc = similarCommands.map(({value}) => `\t${value}\n`).join('');
const sc = similarCommands.map(({ value }) => `\t${value}\n`).join('');
console.error(`\nThe most similar commands are\n${sc}`);
}
process.exit(1);
+20 -24
View File
@@ -16,30 +16,24 @@ const scraper = require('../services/scraper');
const inquirer = require('inquirer');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
/**
* Lists all the assets registered in the database.
*/
async function listAssets() {
try {
let assets = await AssetModel.find({}).sort({'created_at': 1});
let assets = await AssetModel.find({}).sort({ created_at: 1 });
let table = new Table({
head: [
'ID',
'Title',
'URL'
]
head: ['ID', 'Title', 'URL'],
});
assets.forEach((asset) => {
assets.forEach(asset => {
table.push([
asset.id,
asset.title ? asset.title : '',
asset.url ? asset.url : ''
asset.url ? asset.url : '',
]);
});
@@ -61,13 +55,13 @@ async function refreshAssets(ageString) {
$or: [
{
scraped: {
$lte: age
}
$lte: age,
},
},
{
scraped: null
}
]
scraped: null,
},
],
});
// Queue all the assets for scraping.
@@ -95,7 +89,6 @@ async function updateURL(assetID, assetURL) {
async function merge(srcID, dstID) {
try {
// Grab the assets...
let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]);
if (!srcAsset || !dstAsset) {
@@ -103,21 +96,22 @@ async function merge(srcID, dstID) {
}
// Count the affected resources...
let srcCommentCount = await CommentModel.find({asset_id: srcID}).count();
let srcCommentCount = await CommentModel.find({ asset_id: srcID }).count();
console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`);
console.log(
`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`
);
let {confirm} = await inquirer.prompt([
let { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Proceed with merge',
default: false
}
default: false,
},
]);
if (confirm) {
// Perform the merge!
await AssetsService.merge(srcID, dstID);
} else {
@@ -152,7 +146,9 @@ program
program
.command('merge <srcID> <dstID>')
.description('merges two assets together by moving comments from src to dst and deleting the src asset')
.description(
'merges two assets together by moving comments from src to dst and deleting the src asset'
)
.action(merge);
program.parse(process.argv);
+12 -20
View File
@@ -11,20 +11,15 @@ const mailer = require('../services/mailer');
const mongoose = require('../services/mongoose');
const kue = require('../services/kue');
util.onshutdown([
() => mongoose.disconnect(),
]);
util.onshutdown([() => mongoose.disconnect()]);
/**
* Starts the job processor.
*/
function processJobs() {
// The scraper only needs to shutdown when the scraper has actually been
// started.
util.onshutdown([
() => kue.Task.shutdown()
]);
util.onshutdown([() => kue.Task.shutdown()]);
// Start the scraper processor.
scraper.process();
@@ -39,13 +34,15 @@ function processJobs() {
* @return {Promise}
*/
function removeJob(job) {
return new Promise((resolve, reject) => job.remove((err) => {
if (err) {
return reject(err);
}
return new Promise((resolve, reject) =>
job.remove(err => {
if (err) {
return reject(err);
}
return resolve(job);
}));
return resolve(job);
})
);
}
/**
@@ -82,17 +79,13 @@ async function getJobBatch(n, includeStuck) {
* Cleans up the jobs that are in the queue.
*/
async function cleanupJobs(options) {
// The scraper only needs to shutdown when the scraper has actually been
// started.
util.onshutdown([
() => kue.Task.shutdown()
]);
util.onshutdown([() => kue.Task.shutdown()]);
const n = 100;
try {
// Connect to redis by establishing a queue.
kue.Task.connect();
@@ -100,9 +93,8 @@ async function cleanupJobs(options) {
let jobs = await getJobBatch(n, options.stuck);
while (jobs.length > 0) {
// Remove all the jobs.
await Promise.all(jobs.map((job) => removeJob(job)));
await Promise.all(jobs.map(job => removeJob(job)));
jobCount += jobs.length;
+11 -15
View File
@@ -11,13 +11,10 @@ const mongoose = require('../services/mongoose');
const MigrationService = require('../services/migration');
// Register shutdown hooks.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
async function createMigration(name) {
try {
// Create the migration.
await MigrationService.create(name);
@@ -29,20 +26,20 @@ async function createMigration(name) {
}
async function runMigrations() {
try {
let {backedUp} = await inquirer.prompt([
let { backedUp } = await inquirer.prompt([
{
type: 'confirm',
name: 'backedUp',
message: 'Did you perform a database backup',
default: false
}
default: false,
},
]);
if (!backedUp) {
throw new Error('Please backup your databases prior to migrations occuring');
throw new Error(
'Please backup your databases prior to migrations occuring'
);
}
// Get the migrations to run.
@@ -50,21 +47,20 @@ async function runMigrations() {
console.log('Now going to run the following migrations:\n');
for (let {filename} of migrations) {
for (let { filename } of migrations) {
console.log(`\tmigrations/${filename}`);
}
let {confirm} = await inquirer.prompt([
let { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Proceed with migrations',
default: false
}
default: false,
},
]);
if (confirm) {
// Run the migrations.
await MigrationService.run(migrations);
} else {
+117 -77
View File
@@ -21,11 +21,11 @@ const path = require('path');
const spawn = require('cross-spawn');
const semver = require('semver');
const resolve = require('resolve');
const {plugins, iteratePlugins, isInternal} = require('../plugins');
const { plugins, iteratePlugins, isInternal } = require('../plugins');
function existsInNodeModules(name) {
try {
resolve.sync(name, {basedir: dir});
resolve.sync(name, { basedir: dir });
return true;
} catch (e) {
@@ -39,13 +39,13 @@ function versionMatch(name, version) {
resolve.sync(name, {
basedir: dir,
packageFilter: (pkg) => {
packageFilter: pkg => {
if (pkg && pkg.version && semver.satisfies(pkg.version, version)) {
matched = true;
}
return pkg;
}
},
});
return matched;
@@ -56,7 +56,7 @@ function versionMatch(name, version) {
const EXTERNAL = /^\w[a-z\-0-9.]+$/; // Match "react", "path", "fs", "lodash.random", etc.
function reconcilePackages({quiet = false, upgradeRemote = false}) {
function reconcilePackages({ quiet = false, upgradeRemote = false }) {
const fetchable = [];
const local = [];
const upgradable = [];
@@ -74,10 +74,11 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) {
let section = iteratePlugins(plugins[i]);
for (let j in section) {
let {name, version} = section[j];
let { name, version } = section[j];
let namespaced = name.charAt(0) === '@';
let dep = name.split('/')
let dep = name
.split('/')
.slice(0, namespaced ? 2 : 1)
.join('/');
@@ -91,7 +92,7 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) {
console.log(` l ${name}`);
}
local.push({name, version});
local.push({ name, version });
continue;
}
@@ -99,9 +100,8 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) {
if (!quiet) {
console.log(` m ${name}`);
}
fetchable.push({name, version});
fetchable.push({ name, version });
} else if (!versionMatch(dep, version)) {
// A plugin was found, yet the current version does not match the
// current version installed. We should warn if upgradeRemote is
// not enabled that it is currently not supported.
@@ -115,14 +115,14 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) {
console.log(` oe ${name} (package upgrade may be required)`);
upgradable.push({name, version});
upgradable.push({ name, version });
} else {
if (!quiet) {
console.log(` e ${name}`);
}
if (upgradeRemote) {
upgradable.push({name, version});
upgradable.push({ name, version });
}
}
}
@@ -132,33 +132,44 @@ function reconcilePackages({quiet = false, upgradeRemote = false}) {
console.log();
}
return {local, fetchable, upgradable};
return { local, fetchable, upgradable };
}
async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) {
console.log(`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get('mag')} Reconciling plugins...`.yellow);
const {fetchable, upgradable} = reconcilePackages({upgradeRemote});
async function reconcileRemotePlugins({ skipLocal, dryRun, upgradeRemote }) {
console.log(
`\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get(
'mag'
)} Reconciling plugins...`.yellow
);
const { fetchable, upgradable } = reconcilePackages({ upgradeRemote });
console.log(`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`.yellow);
console.log(
`[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n`
.yellow
);
if (fetchable.length > 0) {
console.log(`$ yarn add --ignore-scripts ${fetchable.map(({name, version}) => `${name}@${version}`.cyan)}`);
console.log(
`$ yarn add --ignore-scripts ${fetchable.map(
({ name, version }) => `${name}@${version}`.cyan
)}`
);
if (!dryRun) {
let args = [
'add',
'--ignore-scripts',
...fetchable.map(({name, version}) => `${name}@${version}`)
...fetchable.map(({ name, version }) => `${name}@${version}`),
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
stdio: ['ignore', 'pipe', 'inherit'],
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
throw new Error(
'Could not install external plugins, errors occured during install'
);
}
console.log(output.stdout.toString());
@@ -166,36 +177,45 @@ async function reconcileRemotePlugins({skipLocal, dryRun, upgradeRemote}) {
}
if (upgradable.length > 0) {
console.log(`$ yarn upgrade ${upgradable.map(({name, version}) => `${name}@${version}`.cyan)}`);
console.log(
`$ yarn upgrade ${upgradable.map(
({ name, version }) => `${name}@${version}`.cyan
)}`
);
if (!dryRun) {
let args = [
'upgrade',
...upgradable.map(({name, version}) => `${name}@${version}`)
...upgradable.map(({ name, version }) => `${name}@${version}`),
];
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit']
stdio: ['ignore', 'pipe', 'inherit'],
});
if (output.status) {
throw new Error('Could not install external plugins, errors occured during install');
throw new Error(
'Could not install external plugins, errors occured during install'
);
}
console.log(output.stdout.toString());
}
}
return {upgradable, fetchable};
return { upgradable, fetchable };
}
async function reconcileLocalPlugins({skipRemote, dryRun}) {
console.log(`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get('pick')} Installing local plugin dependencies...\n`.yellow);
const {local} = reconcilePackages({quiet: true});
async function reconcileLocalPlugins({ skipRemote, dryRun }) {
console.log(
`\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get(
'pick'
)} Installing local plugin dependencies...\n`.yellow
);
const { local } = reconcilePackages({ quiet: true });
for (let i in local) {
let {name} = local[i];
let { name } = local[i];
if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) {
continue;
@@ -210,11 +230,13 @@ async function reconcileLocalPlugins({skipRemote, dryRun}) {
let output = spawn.sync('yarn', args, {
stdio: ['ignore', 'pipe', 'inherit'],
cwd: wd
cwd: wd,
});
if (output.status) {
throw new Error('Could not install local plugin dependencies, errors occured during install');
throw new Error(
'Could not install local plugin dependencies, errors occured during install'
);
}
console.log(output.stdout.toString());
@@ -225,7 +247,12 @@ async function reconcileLocalPlugins({skipRemote, dryRun}) {
// This traverses the local plugins and installs any dependencies listed there,
// this only is really needed for plugins that are installed via docker because
// core plugins will have their dependencies already included in core.
async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote}) {
async function reconcilePluginDeps({
skipLocal,
skipRemote,
dryRun,
upgradeRemote,
}) {
let startTime = new Date();
// We don't need to do anything if we skip everything....
@@ -235,14 +262,19 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote
// Traverse local plugins and install dependencies if enabled.
if (!skipLocal) {
await reconcileLocalPlugins({skipRemote, dryRun});
await reconcileLocalPlugins({ skipRemote, dryRun });
}
// Locate any external plugins and install them.
if (!skipRemote) {
let results = [];
try {
results = await reconcileRemotePlugins({skipLocal, skipRemote, dryRun, upgradeRemote});
results = await reconcileRemotePlugins({
skipLocal,
skipRemote,
dryRun,
upgradeRemote,
});
} catch (e) {
throw e;
}
@@ -262,7 +294,9 @@ async function reconcilePluginDeps({skipLocal, skipRemote, dryRun, upgradeRemote
} else if (results.fetchable.length === 0) {
message = `Upgraded ${results.upgradable.length} new plugins.`;
} else {
message = `Fetched ${results.fetchable.length} new plugins, upgraded ${results.upgradable.length} plugins.`;
message = `Fetched ${results.fetchable.length} new plugins, upgraded ${
results.upgradable.length
} plugins.`;
}
console.log(`\n${status} ${message}`);
@@ -279,8 +313,7 @@ async function createSeedPlugin() {
function pluginNameExists(pluginName) {
const pluginNames = fs.readdirSync(pluginsDir);
return !!pluginNames
.filter((pn) => pn === pluginName).length;
return !!pluginNames.filter(pn => pn === pluginName).length;
}
let answers = await inquirer.prompt([
@@ -288,8 +321,7 @@ async function createSeedPlugin() {
type: 'input',
name: 'pluginName',
message: 'Plugin Name:',
validate: (input) => {
validate: input => {
if (pluginNameExists(input)) {
return 'Please, choose another name. That name already exists';
}
@@ -299,23 +331,23 @@ async function createSeedPlugin() {
}
return 'Plugin Name is required.';
}
},
},
{
type: 'confirm',
name: 'server',
message: 'Is this plugin extending the server capabilities?'
message: 'Is this plugin extending the server capabilities?',
},
{
type: 'confirm',
name: 'client',
message: 'Is this plugin extending the client capabilities?'
message: 'Is this plugin extending the client capabilities?',
},
{
type: 'confirm',
name: 'addPluginsJson',
message: 'Should we add it to the plugins.json?'
}
message: 'Should we add it to the plugins.json?',
},
]);
//==============================================================================
@@ -326,41 +358,41 @@ async function createSeedPlugin() {
const newPluginPath = path.join(pluginsDir, answers.pluginName);
if (fs.existsSync(seedPlugin)) {
if (answers.server && answers.client) {
// This is a server-side and client-side plugin!, let's copy the template
fs.copySync(seedPlugin, newPluginPath);
} else {
} else {
fs.copySync(seedPlugin, newPluginPath, {
filter: p => {
// Allowing plugin folder and files with no subfolders
const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/gim;
if (
rootRx.test(p) &&
(fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())
) {
return true;
}
fs.copySync(seedPlugin, newPluginPath, {filter: (p) => {
// If it's a client-side plugin, copying client folder
if (answers.client) {
return /client/.test(p);
}
// Allowing plugin folder and files with no subfolders
const rootRx = /plugin$|plugin\/[^/]*(\.).{2,3}/igm;
if (rootRx.test(p) && (fs.lstatSync(p).isDirectory() || fs.lstatSync(p).isFile())) {
return true;
}
// If it's a client-side plugin, copying client folder
if (answers.client) {
return /client/.test(p);
}
// If it's a server-side plugin, copying server folder
if (answers.server) {
return /server/.test(p);
}
}});
// If it's a server-side plugin, copying server folder
if (answers.server) {
return /server/.test(p);
}
},
});
}
// Let's add this to the plugins.json
if (answers.addPluginsJson) {
const pluginsJson = path.resolve(__dirname, '..', 'plugins.json');
fs.readJson(pluginsJson)
.then((j) => {
fs
.readJson(pluginsJson)
.then(j => {
// This is a client-side plugin, let's push this.
if (answers.client) {
j.client.push(answers.pluginName);
@@ -377,14 +409,17 @@ async function createSeedPlugin() {
fs.writeFileSync(pluginsJson, output);
}
})
.catch((err) => {
.catch(err => {
console.error(err);
});
}
console.log(`✨ Yay! Plugin created! Find your plugin: ${answers.pluginName} in the ./plugins folder`);
console.log(
`✨ Yay! Plugin created! Find your plugin: ${
answers.pluginName
} in the ./plugins folder`
);
}
}
//==============================================================================
@@ -403,9 +438,14 @@ program
program
.command('reconcile')
.description('reconciles local plugin dependencies and downloads external plugins')
.description(
'reconciles local plugin dependencies and downloads external plugins'
)
.option('-u, --upgrade-remote', 'upgrades remote dependencies')
.option('-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation')
.option(
'-d, --dry-run',
'does not actually change anything on the filesystem acts only as a simulation'
)
.option('--skip-local', 'skips the local dependancy reconciliation')
.option('--skip-remote', 'skips the remote plugin reconciliation')
.action(reconcilePluginDeps);
+5 -3
View File
@@ -10,12 +10,14 @@ const serve = require('../serve');
program
.option('-j, --jobs', 'enable job processing on this thread')
.option('-w, --websockets', 'enable the websocket (subscriptions) handler on this thread')
.option(
'-w, --websockets',
'enable the websocket (subscriptions) handler on this thread'
)
.parse(process.argv);
// Start serving.
serve({jobs: program.jobs, websockets: program.websockets}).catch((err) => {
serve({ jobs: program.jobs, websockets: program.websockets }).catch(err => {
console.error(err);
util.shutdown(1);
});
+3 -3
View File
@@ -16,12 +16,12 @@ async function changeOrgName() {
try {
let settings = await SettingsService.retrieve();
let {organizationName} = await inquirer.prompt([
let { organizationName } = await inquirer.prompt([
{
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName
}
default: settings.organizationName,
},
]);
if (settings.organizationName !== organizationName) {
+39 -47
View File
@@ -16,9 +16,7 @@ const UsersService = require('../services/users');
const errors = require('../errors');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
//==============================================================================
// Setting up the program command line arguments.
@@ -34,19 +32,15 @@ program
//==============================================================================
const performSetup = async () => {
// Get the current settings, we are expecing an error here.
try {
// Try to get the settings.
await SettingsService.retrieve();
// We should NOT have gotten a settings object, this means that the
// application is already setup. Error out here.
throw errors.ErrSettingsInit;
} catch (e) {
// If the error is `not init`, then we're good, otherwise, it's something
// else.
if (e !== errors.ErrSettingsNotInit) {
@@ -66,7 +60,9 @@ const performSetup = async () => {
// Create the base settings model.
let settings = new SettingModel();
console.log('\nWe\'ll ask you some questions in order to setup your installation of Talk.\n');
console.log(
"\nWe'll ask you some questions in order to setup your installation of Talk.\n"
);
let answers = await inquirer.prompt([
{
@@ -74,31 +70,31 @@ const performSetup = async () => {
name: 'organizationName',
message: 'Organization Name',
default: settings.organizationName,
validate: (input) => {
validate: input => {
if (input && input.length > 0) {
return true;
}
return 'Organization Name is required.';
}
},
},
{
type: 'list',
choices: MODERATION_OPTIONS,
name: 'moderation',
default: settings.moderation,
message: 'Select a moderation mode'
message: 'Select a moderation mode',
},
{
type: 'confirm',
name: 'requireEmailConfirmation',
default: settings.requireEmailConfirmation,
message: 'Should emails always be confirmed'
message: 'Should emails always be confirmed',
},
]);
// Update the settings that were changed.
Object.keys(answers).forEach((key) => {
Object.keys(answers).forEach(key => {
if (answers[key] !== undefined) {
settings[key] = answers[key];
}
@@ -109,97 +105,93 @@ const performSetup = async () => {
type: 'confirm',
name: 'inputWhitelistedDomains',
default: true,
message: 'Would you like to specify a whitelisted domain'
message: 'Would you like to specify a whitelisted domain',
},
{
type: 'input',
name: 'whitelistedDomain',
message: 'Whitelisted Domain',
when: ({inputWhitelistedDomains}) => inputWhitelistedDomains,
validate: (input) => {
when: ({ inputWhitelistedDomains }) => inputWhitelistedDomains,
validate: input => {
if (input && input.length > 0) {
return true;
}
return 'Whitelisted Domain cannot be empty.';
}
}
},
},
]);
if (answers.inputWhitelistedDomains) {
settings.domains.whitelist = [answers.whitelistedDomain];
}
console.log('\nWe\'ll ask you some questions about your first admin user.\n');
console.log("\nWe'll ask you some questions about your first admin user.\n");
let user = await inquirer.prompt([
{
type: 'input',
name: 'username',
message: 'Username',
filter: (username) => {
return UsersService
.isValidUsername(username, false)
.catch((err) => {
throw err.message;
});
}
filter: username => {
return UsersService.isValidUsername(username, false).catch(err => {
throw err.message;
});
},
},
{
name: 'email',
message: 'Email',
format: 'email',
validate: (value) => {
validate: value => {
if (value && value.length >= 3) {
return true;
}
return 'Email is required';
}
},
},
{
name: 'password',
message: 'Password',
type: 'password',
filter: (password) => {
return UsersService
.isValidPassword(password)
.catch((err) => {
throw err.message;
});
}
filter: password => {
return UsersService.isValidPassword(password).catch(err => {
throw err.message;
});
},
},
{
name: 'confirmPassword',
message: 'Confirm Password',
type: 'password',
filter: (confirmPassword, {password}) => {
filter: (confirmPassword, { password }) => {
if (password !== confirmPassword) {
return Promise.reject(new Error('Passwords do not match'));
}
return UsersService
.isValidPassword(confirmPassword)
.catch((err) => {
throw err.message;
});
}
return UsersService.isValidPassword(confirmPassword).catch(err => {
throw err.message;
});
},
},
]);
let {user: newUser} = await SetupService.setup({
let { user: newUser } = await SetupService.setup({
settings: settings.toObject(),
user: {
email: user.email,
username: user.username,
password: user.password
}
password: user.password,
},
});
console.log('Settings created.');
console.log(`User ${newUser.id} created.`);
console.log('\nTalk is now installed!');
console.log('\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.');
console.log(
'\nWe recommend adding TALK_INSTALL_LOCK=TRUE to your environment to turn off the dynamic setup.'
);
};
// Start tthe setup process.
@@ -207,7 +199,7 @@ performSetup()
.then(() => {
util.shutdown();
})
.catch((e) => {
.catch(e => {
console.error(e);
util.shutdown(1);
});
+5 -17
View File
@@ -11,28 +11,18 @@ const TokensService = require('../services/tokens');
const Table = require('cli-table');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
async function listTokens(userID) {
try {
let tokens = await TokensService.list(userID);
let table = new Table({
head: [
'ID',
'Name',
'Status'
]
head: ['ID', 'Name', 'Status'],
});
tokens.forEach((token) => {
table.push([
token.id,
token.name,
token.active ? 'Active' : 'Revoked'
]);
tokens.forEach(token => {
table.push([token.id, token.name, token.active ? 'Active' : 'Revoked']);
});
console.log(table.toString());
@@ -46,7 +36,6 @@ async function listTokens(userID) {
async function revokeToken(tokenID) {
try {
await TokensService.revoke(null, tokenID);
console.log(`Revoked Token[${tokenID}]`);
@@ -60,8 +49,7 @@ async function revokeToken(tokenID) {
async function createToken(userID, tokenName) {
try {
let {pat: {id}, jwt} = await TokensService.create(userID, tokenName);
let { pat: { id }, jwt } = await TokensService.create(userID, tokenName);
console.log(`Created Token[${id}] for User[${userID}] = ${jwt}`);
+45 -47
View File
@@ -7,15 +7,18 @@
const util = require('./util');
const program = require('commander');
const inquirer = require('inquirer');
const {graphql} = require('graphql');
const {stripIndent} = require('common-tags');
const { graphql } = require('graphql');
const { stripIndent } = require('common-tags');
const Table = require('cli-table');
// Make things colorful!
require('colors');
// Register the autocomplete plugin.
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'));
inquirer.registerPrompt(
'autocomplete',
require('inquirer-autocomplete-prompt')
);
const schema = require('../graph/schema');
const Context = require('../graph/context');
@@ -28,19 +31,15 @@ const mongoose = require('../services/mongoose');
const databaseVerifications = require('./verifications/database');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
/**
* Deletes a user and cleans up their associated verifications.
*/
async function deleteUser(userID) {
try {
// Find the user we're removing.
const user = await UserModel.findOne({id: userID});
const user = await UserModel.findOne({ id: userID });
if (!user) {
throw new Error(`user with id ${userID} not found`);
}
@@ -54,7 +53,7 @@ async function deleteUser(userID) {
This might take a long time if there is a lot of data, please confirm that
you want to continue.
`);
const {confirm} = await inquirer.prompt({
const { confirm } = await inquirer.prompt({
type: 'confirm',
name: 'confirm',
message: 'Continue',
@@ -64,27 +63,25 @@ async function deleteUser(userID) {
return util.shutdown();
}
console.warn('Removing user\'s actions');
console.warn("Removing user's actions");
// Remove all the user's actions.
await ActionModel
.where({user_id: user.id})
.setOptions({multi: true})
await ActionModel.where({ user_id: user.id })
.setOptions({ multi: true })
.remove();
console.warn('Removing user\'s comments');
console.warn("Removing user's comments");
// Remove all the user's comments.
await CommentModel
.where({author_id: user.id})
.setOptions({multi: true})
await CommentModel.where({ author_id: user.id })
.setOptions({ multi: true })
.remove();
console.warn('Updating the database indexes');
// Update the counts that might have changed.
for (const verification of databaseVerifications) {
await verification({fix: true, limit: Infinity, batch: 1000});
await verification({ fix: true, limit: Infinity, batch: 1000 });
}
console.warn('Removing the user');
@@ -103,15 +100,19 @@ function printUserAsTable(user) {
let table = new Table({});
table.push(
{'ID': user.id.gray},
{'Username': user.username},
{'Emails': user.emails},
{'Tags': user.tags ? user.tags.map(({tag: {name}}) => name) : ''},
{'Role': user.role},
{'Verified': user.hasVerifiedEmail},
{'Username': user.status.username.status},
{'Banned': user.banned},
{'Suspension': user.suspended ? `Until ${user.status.suspension.until}` : false},
{ ID: user.id.gray },
{ Username: user.username },
{ Emails: user.emails },
{ Tags: user.tags ? user.tags.map(({ tag: { name } }) => name) : '' },
{ Role: user.role },
{ Verified: user.hasVerifiedEmail },
{ Username: user.status.username.status },
{ Banned: user.banned },
{
Suspension: user.suspended
? `Until ${user.status.suspension.until}`
: false,
}
);
console.log(table.toString());
@@ -148,7 +149,7 @@ async function searchUsers() {
value = '';
}
const {data, errors} = await graphql(schema, searchQuery, {}, ctx, {
const { data, errors } = await graphql(schema, searchQuery, {}, ctx, {
value,
});
if (errors && errors.length > 0) {
@@ -159,18 +160,20 @@ async function searchUsers() {
return [];
}
return data.users.nodes.map((user) => {
return data.users.nodes.map(user => {
const emails = user.emails.join(', ');
return {
name: `${user.username} (${emails}) ${user.id.gray} - ${user.role.gray}`,
name: `${user.username} (${emails}) ${user.id.gray} - ${
user.role.gray
}`,
value: user.id,
};
});
}
},
});
const {userID} = answers;
const user = await UserModel.findOne({id: userID});
const { userID } = answers;
const user = await UserModel.findOne({ id: userID });
printUserAsTable(user);
util.shutdown(0);
@@ -187,13 +190,13 @@ async function searchUsers() {
*/
async function setUserRole(userID) {
try {
const {role} = await inquirer.prompt([
const { role } = await inquirer.prompt([
{
name: 'role',
message: 'User Role',
type: 'list',
choices: USER_ROLES
}
choices: USER_ROLES,
},
]);
await UsersService.setRole(userID, role);
@@ -204,7 +207,6 @@ async function setUserRole(userID) {
console.error(err);
util.shutdown(1);
}
}
/**
@@ -217,9 +219,8 @@ async function setUserRole(userID) {
*/
async function verifyUserEmail(userID, email) {
try {
// Get the user.
const user = await UserModel.findOne({id: userID});
const user = await UserModel.findOne({ id: userID });
if (!user) {
throw new Error(`user with ID ${userID} cannot be found`);
}
@@ -231,23 +232,20 @@ async function verifyUserEmail(userID, email) {
}
if (!email && emails.length === 1) {
// The email wasn't passed, and there is only one option.
email = emails[0];
} else if (!emails.includes(email)){
} else if (!emails.includes(email)) {
// The email passed doesn't belong to this user.
throw new Error(`user does not have the email ${email}`);
} else if (emails.length > 1) {
// The email wasn't passed, and there is more than one choice.
const answers = await inquirer.prompt([
{
name: 'email',
message: 'Select Email to Verify',
type: 'list',
choices: emails
}
choices: emails,
},
]);
email = answers.email;
@@ -284,7 +282,7 @@ program
program
.command('verify <userID> <email>')
.description('verifies the given user\'s email address')
.description("verifies the given user's email address")
.action(verifyUserEmail);
program.parse(process.argv);
+17 -8
View File
@@ -10,17 +10,18 @@ const mongoose = require('../services/mongoose');
const databaseVerifications = require('./verifications/database');
// Register the shutdown criteria.
util.onshutdown([
() => mongoose.disconnect()
]);
util.onshutdown([() => mongoose.disconnect()]);
async function database({fix = false, limit = Infinity, batch = 1000}) {
async function database({ fix = false, limit = Infinity, batch = 1000 }) {
try {
for (const verification of databaseVerifications) {
await verification({fix, limit, batch});
await verification({ fix, limit, batch });
}
} catch (err) {
console.error(`Failed to process all the ${databaseVerifications.length} verifications`, err);
console.error(
`Failed to process all the ${databaseVerifications.length} verifications`,
err
);
util.shutdown(1);
return;
}
@@ -36,8 +37,16 @@ program
.command('db')
.description('verifies the database integrity')
.option('-f, --fix', 'fix the problems found with database inconsistencies')
.option('-l, --limit [size]', 'limit the amount of documents to process in a single pass, this will ensure only a maximum number of batch operations are issued [default: inf]', parseInt)
.option('-b, --batch [size]', 'batch size to process verifications and repairs of documents [default: 1000]', parseInt)
.option(
'-l, --limit [size]',
'limit the amount of documents to process in a single pass, this will ensure only a maximum number of batch operations are issued [default: inf]',
parseInt
)
.option(
'-b, --batch [size]',
'batch size to process verifications and repairs of documents [default: 1000]',
parseInt
)
.action(database);
program.parse(process.argv);
@@ -1,12 +1,12 @@
import React from 'react';
import {CoralLogo} from 'plugin-api/beta/client/components/ui';
import { CoralLogo } from 'plugin-api/beta/client/components/ui';
import styles from './MyPluginComponent.css';
class MyPluginComponent extends React.Component {
render() {
return (
<div className={styles.myPluginContainer}>
<CoralLogo className={styles.logo}/>
<CoralLogo className={styles.logo} />
<div className={styles.description}>
<h3>Plugin created by Talk CLI</h3>
+2 -3
View File
@@ -1,4 +1,3 @@
/**
This is a client index example file and it could look like this:
@@ -21,6 +20,6 @@ import MyPluginComponent from './components/MyPluginComponent';
export default {
slots: {
stream: [MyPluginComponent]
}
stream: [MyPluginComponent],
},
};
+11 -11
View File
@@ -1,10 +1,9 @@
// Setup the environment.
require('../services/env');
const debug = require('debug')('talk:util');
const util = module.exports = {};
const util = (module.exports = {});
/**
* Stores an array of functions that should be executed in the event that the
@@ -18,20 +17,22 @@ util.toshutdown = [];
* @param {Number} [defaultCode=0] default return code upon sucesfull shutdown.
*/
util.shutdown = (defaultCode = 0, signal = null) => {
if (signal) {
debug(`Reached ${signal} signal`);
}
debug(`${util.toshutdown.length} jobs now being called`);
Promise
.all(util.toshutdown.map((func) => func ? func(signal) : null).filter((func) => func))
Promise.all(
util.toshutdown
.map(func => (func ? func(signal) : null))
.filter(func => func)
)
.then(() => {
debug('Shutdown complete, now exiting');
process.exit(defaultCode);
})
.catch((err) => {
.catch(err => {
console.error(err);
process.exit(1);
@@ -44,8 +45,7 @@ util.shutdown = (defaultCode = 0, signal = null) => {
* @param {Array} jobs Array of promise capable shutdown functions that are
* executed.
*/
util.onshutdown = (jobs) => {
util.onshutdown = jobs => {
debug(`${jobs.length} jobs registered to be called during shutdown`);
// Add the new jobs to shutdown to the object reference.
@@ -55,13 +55,13 @@ util.onshutdown = (jobs) => {
// Attach to the SIGTERM + SIGINT handles to ensure a clean shutdown in the
// event that we have an external event. SIGUSR2 is called when the app is asked
// to be 'killed', same procedure here.
process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM'));
process.on('SIGINT', () => util.shutdown(0, 'SIGINT'));
process.on('SIGTERM', () => util.shutdown(0, 'SIGTERM'));
process.on('SIGINT', () => util.shutdown(0, 'SIGINT'));
process.once('SIGUSR2', () => util.shutdown(0, 'SIGUSR2'));
// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', (err) => {
process.on('unhandledRejection', err => {
throw err;
});
+55 -41
View File
@@ -1,28 +1,24 @@
const UserModel = require('../../../models/user');
const CommentModel = require('../../../models/comment');
const ActionsService = require('../../../services/actions');
const {arrayJoinBy} = require('../../../graph/loaders/util');
const {get} = require('lodash');
const { arrayJoinBy } = require('../../../graph/loaders/util');
const { get } = require('lodash');
const debug = require('debug')('talk:cli:verify');
const MODELS = [
UserModel,
CommentModel,
];
const MODELS = [UserModel, CommentModel];
async function processBatch(Model, documents) {
// Get an array of all the document id's.
const documentIDs = documents.map(({id}) => id);
const documentIDs = documents.map(({ id }) => id);
// Store all the operations on this batch in this array that we'll return
// later.
const operations = [];
// Get the action summaries for this batch.
const totalActionSummaries = await ActionsService
.getActionSummaries(documentIDs)
.then(arrayJoinBy(documentIDs, 'item_id'));
const totalActionSummaries = await ActionsService.getActionSummaries(
documentIDs
).then(arrayJoinBy(documentIDs, 'item_id'));
// Iterate over the documents.
for (let i = 0; i < documents.length; i++) {
@@ -49,8 +45,10 @@ async function processBatch(Model, documents) {
const ACTION_COUNT_FIELD = `${ACTION_TYPE}_${GROUP_ID}`;
// Check that the action summaries match the cached counts.
if (get(document, ['action_counts', ACTION_COUNT_FIELD]) !== actionSummary.count) {
if (
get(document, ['action_counts', ACTION_COUNT_FIELD]) !==
actionSummary.count
) {
// Batch updates for those changes.
ops.push({
[`action_counts.${ACTION_COUNT_FIELD}`]: actionSummary.count,
@@ -60,27 +58,28 @@ async function processBatch(Model, documents) {
// Group all the action summaries together from all the different group
// ids.
const groupedActionSummaries = actionSummaries.reduce((acc, actionSummary) => {
const groupedActionSummaries = actionSummaries.reduce(
(acc, actionSummary) => {
// action_type is already snake cased (as it would have had to be when it
// was inserted in the database).
const ACTION_TYPE = actionSummary.action_type.toLowerCase();
// action_type is already snake cased (as it would have had to be when it
// was inserted in the database).
const ACTION_TYPE = actionSummary.action_type.toLowerCase();
if (!(ACTION_TYPE in acc)) {
acc[ACTION_TYPE] = 0;
}
if (!(ACTION_TYPE in acc)) {
acc[ACTION_TYPE] = 0;
}
acc[ACTION_TYPE] += actionSummary.count;
acc[ACTION_TYPE] += actionSummary.count;
return acc;
}, {});
return acc;
},
{}
);
for (const ACTION_COUNT_FIELD of Object.keys(groupedActionSummaries)) {
const count = groupedActionSummaries[ACTION_COUNT_FIELD];
// Check that the action summaries match the cached counts.
if (get(document, ['action_counts', ACTION_COUNT_FIELD]) !== count) {
// Batch updates for those changes.
ops.push({
[`action_counts.${ACTION_COUNT_FIELD}`]: count,
@@ -94,7 +93,7 @@ async function processBatch(Model, documents) {
operations.push({
updateOne: {
filter: {
id: document.id
id: document.id,
},
update: {
$set: Object.assign({}, ...ops),
@@ -107,23 +106,21 @@ async function processBatch(Model, documents) {
return operations;
}
module.exports = async ({fix, batch}) => {
module.exports = async ({ fix, batch }) => {
for (const Model of MODELS) {
const cursor = Model
.collection
const cursor = Model.collection
.find({})
.project({
id: 1,
action_counts: 1
action_counts: 1,
})
.sort({created_at: 1});
.sort({ created_at: 1 });
let operations = [];
let documents = [];
// While there are documents to process.
while (await cursor.hasNext()) {
// Load the document.
const document = await cursor.next();
@@ -133,7 +130,6 @@ module.exports = async ({fix, batch}) => {
// Check to see if the length of the documents array requires us to
// process it.
if (documents.length > batch) {
// Process this batch.
let batchOperations = await processBatch(Model, documents);
@@ -147,7 +143,6 @@ module.exports = async ({fix, batch}) => {
// Check to see if there are any documents left over.
if (documents.length > 0) {
// Process this batch.
let batchOperations = await processBatch(Model, documents);
@@ -157,24 +152,43 @@ module.exports = async ({fix, batch}) => {
const OPERATIONS_LENGTH = operations.length;
console.log(`action_counts.js: ${OPERATIONS_LENGTH} ${Model.collection.name} need their action counts fixed.`);
console.log(
`action_counts.js: ${OPERATIONS_LENGTH} ${
Model.collection.name
} need their action counts fixed.`
);
// If fix was enabled, execute the batch writes.
if (OPERATIONS_LENGTH > 0) {
if (fix) {
debug(`action_counts.js: fixing ${OPERATIONS_LENGTH} ${Model.collection.name}...`);
debug(
`action_counts.js: fixing ${OPERATIONS_LENGTH} ${
Model.collection.name
}...`
);
while (operations.length) {
let result = await Model.collection.bulkWrite(operations.splice(0, batch));
let result = await Model.collection.bulkWrite(
operations.splice(0, batch)
);
debug(`action_counts.js: fixed batch of ${result.modifiedCount} ${Model.collection.name}.`);
debug(
`action_counts.js: fixed batch of ${result.modifiedCount} ${
Model.collection.name
}.`
);
}
console.log(`action_counts.js: applied all ${OPERATIONS_LENGTH} fixes to ${Model.collection.name}.`);
console.log(
`action_counts.js: applied all ${OPERATIONS_LENGTH} fixes to ${
Model.collection.name
}.`
);
} else {
console.warn('Skipping fixing, --fix was not enabled, pass --fix to fix these errors');
console.warn(
'Skipping fixing, --fix was not enabled, pass --fix to fix these errors'
);
}
}
}
};
+46 -36
View File
@@ -1,15 +1,15 @@
const CommentModel = require('../../../models/comment');
const {singleJoinBy} = require('../../../graph/loaders/util');
const { singleJoinBy } = require('../../../graph/loaders/util');
const debug = require('debug')('talk:cli:verify');
const getBatch = async (limit, offset) => CommentModel
.find({})
.select({'id': 1, 'action_counts': 1, 'reply_count': 1})
.limit(limit)
.skip(offset)
.sort('created_at');
const getBatch = async (limit, offset) =>
CommentModel.find({})
.select({ id: 1, action_counts: 1, reply_count: 1 })
.limit(limit)
.skip(offset)
.sort('created_at');
module.exports = async ({fix, limit, batch}) => {
module.exports = async ({ fix, limit, batch }) => {
let operations = [];
// Count how many comments there are to process.
@@ -23,35 +23,33 @@ module.exports = async ({fix, limit, batch}) => {
// Keep processing documents until there are is none left.
while (offset < totalCount) {
// Get a batch of comments.
comments = await getBatch(batch, offset);
commentIDs = comments.map(({id}) => id);
commentIDs = comments.map(({ id }) => id);
// Get their reply counts.
let allReplyCounts = await CommentModel
.aggregate([
{
$match: {
parent_id: {
$in: commentIDs,
},
status: {
$in: ['NONE', 'ACCEPTED']
}
}
let allReplyCounts = await CommentModel.aggregate([
{
$match: {
parent_id: {
$in: commentIDs,
},
status: {
$in: ['NONE', 'ACCEPTED'],
},
},
{
$group: {
_id: '$parent_id',
count: {
$sum: 1
}
}
}
])
},
{
$group: {
_id: '$parent_id',
count: {
$sum: 1,
},
},
},
])
.then(singleJoinBy(commentIDs, '_id'))
.then((results) => results.map((result) => result ? result.count : 0));
.then(results => results.map(result => (result ? result.count : 0)));
// Loop over the comments, with their action summaries.
for (let i = 0; i < comments.length; i++) {
@@ -75,7 +73,7 @@ module.exports = async ({fix, limit, batch}) => {
operations.push({
updateOne: {
filter: {
id: comment.id
id: comment.id,
},
update: {
$set: Object.assign({}, ...commentOperations),
@@ -88,10 +86,17 @@ module.exports = async ({fix, limit, batch}) => {
debug(`Processed batch of ${comments.length} comments.`);
if (operations.length >= limit) {
debug(`Queued operations are ${operations.length}, reached limit of ${limit}, not processing any more.`);
debug(
`Queued operations are ${
operations.length
}, reached limit of ${limit}, not processing any more.`
);
if (operations.length > limit) {
debug(`${operations.length - limit} operations have been truncated to enforce the limit`);
debug(
`${operations.length -
limit} operations have been truncated to enforce the limit`
);
}
break;
@@ -103,7 +108,10 @@ module.exports = async ({fix, limit, batch}) => {
const OPERATIONS_LENGTH = operations.length;
if (limit < Infinity && offset + comments.length < totalCount) {
console.log(`Processed ${offset + comments.length}/${totalCount} comments because we reached the update limit of ${limit}.`);
console.log(
`Processed ${offset +
comments.length}/${totalCount} comments because we reached the update limit of ${limit}.`
);
} else {
console.log(`Processed all ${totalCount} comments.`);
}
@@ -124,7 +132,9 @@ module.exports = async ({fix, limit, batch}) => {
console.log(`Applied all ${OPERATIONS_LENGTH} fixes.`);
} else {
console.warn('Skipping fixing, --fix was not enabled, pass --fix to fix these errors');
console.warn(
'Skipping fixing, --fix was not enabled, pass --fix to fix these errors'
);
}
}
};
+1 -4
View File
@@ -7,7 +7,4 @@
// async ({fix = false, batch = 1000}) => {}
//
// where their options are derived.
module.exports = [
require('./comment_replies'),
require('./action_counts'),
];
module.exports = [require('./comment_replies'), require('./action_counts')];