#!/usr/bin/env node /** * Module dependencies. */ const program = require('commander'); const inquirer = require('inquirer'); const UsersService = require('../services/users'); const UserModel = require('../models/user'); const CommentModel = require('../models/comment'); const ActionModel = require('../models/action'); const USER_ROLES = require('../models/enum/user_roles'); const mongoose = require('../services/mongoose'); const util = require('./util'); const Table = require('cli-table'); const databaseVerifications = require('./verifications/database'); const validateRequired = (msg = 'Field is required', len = 1) => (input) => { if (input && input.length >= len) { return true; } return msg; }; // Regeister the shutdown criteria. util.onshutdown([ () => mongoose.disconnect() ]); function getUserCreateAnswers(options) { if (options.flag_mode) { let user = { email: options.email, password: options.password, confirmPassword: options.password, username: options.name, role: 'COMMENTER' }; if (options.role && USER_ROLES.indexOf(options.role) > -1) { user.roles = options.role; } return Promise.resolve(user); } return inquirer.prompt([ { name: 'email', message: 'Email', format: 'email', validate: validateRequired('Email is required') }, { name: 'password', message: 'Password', type: 'password', filter: (password) => { return UsersService .isValidPassword(password) .catch((err) => { throw err.message; }); } }, { name: 'confirmPassword', message: 'Confirm Password', type: 'password', filter: (confirmPassword) => { return UsersService .isValidPassword(confirmPassword) .catch((err) => { throw err.message; }); } }, { name: 'username', message: 'Username', filter: (username) => { return UsersService .isValidUsername(username) .catch((err) => { throw err.message; }); } }, { name: 'role', message: 'User Role', type: 'list', choices: USER_ROLES } ]); } /** * Prompts for input and registers a user based on those. */ async function createUser(options) { try { const answers = await getUserCreateAnswers(options); if (answers.password !== answers.confirmPassword) { throw new Error('Passwords do not match'); } const user = await UsersService.createLocalUser(answers.email.trim(), answers.password.trim(), answers.username.trim()); await UsersService.setRole(user.id, answers.role); await UsersService.sendEmailConfirmation(user, answers.email.trim()); console.log(`Created User ${user.id}.`); util.shutdown(); } catch (err) { console.error(err); util.shutdown(1); } } /** * Deletes a user. */ async function deleteUser(userID) { try { // Find the user we're removing. const user = await UserModel.findOne({id: userID}); if (!user) { throw new Error(`user with id ${userID} not found`); } // Remove all the user's actions. await ActionModel .where({user_id: user.id}) .setOptions({multi: true}) .remove(); // Remove all the user's comments. await CommentModel .where({author_id: user.id}) .setOptions({multi: true}) .remove(); // Update the counts that might have changed. for (const verification of databaseVerifications) { await verification({fix: true, limit: Infinity, batch: 1000}); } // Remove the user. await user.remove(); util.shutdown(); } catch (err) { console.error(err); util.shutdown(1); } } /** * Changes the password for a user. */ function passwd(userID) { inquirer.prompt([ { name: 'password', message: 'Password', type: 'password', validate: validateRequired('Password is required') }, { name: 'confirmPassword', message: 'Confirm Password', type: 'password', validate: validateRequired('Confirm Password is required') } ]) .then((answers) => { if (answers.password !== answers.confirmPassword) { throw new Error('Password mismatch'); } return UsersService.changePassword(userID, answers.password); }) .then(() => { console.log('Password changed.'); util.shutdown(); }) .catch((err) => { console.error(err); util.shutdown(1); }); } /** * Updates the user from the options array. */ function updateUser(userID, options) { const updates = []; if (options.email && typeof options.email === 'string' && options.email.length > 0) { let q = UserModel.update({ 'id': userID, 'profiles.provider': 'local' }, { $set: { 'profiles.$.id': options.email } }); updates.push(q); } if (options.name && typeof options.name === 'string' && options.name.length > 0) { let q = UserModel.update({ 'id': userID }, { $set: { username: options.name } }); updates.push(q); } Promise .all(updates.map((q) => q.exec())) .then(() => { console.log(`User ${userID} updated.`); util.shutdown(); }) .catch((err) => { console.error(err); util.shutdown(1); }); } /** * Lists all the users registered in the database. */ function listUsers() { UsersService .all() .then((users) => { let table = new Table({ head: [ 'ID', 'Username', 'Profiles', 'Roles', 'Status', 'State' ] }); users.forEach((user) => { const profile = user.profiles.find(({provider}) => provider === 'local'); let state; if (profile && profile.metadata && profile.metadata.confirmed_at) { state = 'Verified'; } else { state = 'Unverified'; } table.push([ user.id, user.username, user.profiles.map((p) => p.provider).join(', '), user.role, user.status, state ]); }); console.log(table.toString()); util.shutdown(); }) .catch((err) => { console.error(err); util.shutdown(1); }); } /** * Merges two users using the specified ID's. * @param {String} dstUserID id of the user to which is the target of the merge * @param {String} srcUserID id of the user to which is the source of the merge */ function mergeUsers(dstUserID, srcUserID) { UsersService .mergeUsers(dstUserID, srcUserID) .then(() => { console.log(`User ${srcUserID} was merged into user ${dstUserID}.`); util.shutdown(); }) .catch((err) => { console.error(err); util.shutdown(1); }); } /** * Adds a role to a user * @param {String} userUD id of the user to add the role to * @param {String} role the role to add */ async function setRole(userID, role, options) { try { if (options.interactive || !role) { const answers = await inquirer.prompt([ { name: 'role', message: 'User Role', type: 'list', choices: USER_ROLES } ]); role = answers.role; } await UsersService.setRole(userID, role); console.log(`Set User ${userID} to the ${role} role.`); util.shutdown(); } catch (err) { console.error(err); util.shutdown(1); } } /** * Verifies an email address for a user. * * @param userID the user's id * @param email the user's email address to be verified */ async function verify(userID, email) { try { await UsersService.confirmEmail(userID, email); console.log(`User ${userID} had their email ${email} verified.`); util.shutdown(); } catch (err) { console.error(err); util.shutdown(1); } } //============================================================================== // Setting up the program command line arguments. //============================================================================== program .command('create') .option('--email [email]', 'Email to use') .option('--password [password]', 'Password to use') .option('--name [name]', 'Name to use') .option('--role [role]', 'Role to add') .option('-f, --flag_mode', 'Source from flags instead of prompting') .description('create a new user') .action(createUser); program .command('delete ') .description('delete a user') .action(deleteUser); program .command('passwd ') .description('change a password for a user') .action(passwd); program .command('update ') .option('--email [email]', 'Email to use') .option('--name [name]', 'Name to use') .description('update a user') .action(updateUser); program .command('list') .description('list all the users in the database') .action(listUsers); program .command('merge ') .description('merge srcUser into the dstUser') .action(mergeUsers); program .command('setrole [role]') .option('-i, --interactive', 'Enable interactive mode') .description('sets the role on a user') .action(setRole); program .command('verify ') .description('verifies the given user\'s email address') .action(verify); program.parse(process.argv); // If there is no command listed, output help. if (!process.argv.slice(2).length) { program.outputHelp(); util.shutdown(); }