#!/usr/bin/env node /** * Module dependencies. */ const util = require('./util'); const program = require('commander'); const inquirer = require('inquirer'); 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')); const schema = require('../graph/schema'); const Context = require('../graph/context'); 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 databaseVerifications = require('./verifications/database'); // Register the shutdown criteria. 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}); if (!user) { throw new Error(`user with id ${userID} not found`); } printUserAsTable(user); console.warn(stripIndent` This will delete the above user. 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({ type: 'confirm', name: 'confirm', message: 'Continue', default: false, }); if (!confirm) { return util.shutdown(); } console.warn('Removing user\'s actions'); // Remove all the user's actions. await ActionModel .where({user_id: user.id}) .setOptions({multi: true}) .remove(); console.warn('Removing user\'s comments'); // Remove all the user's comments. 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}); } console.warn('Removing the user'); // Remove the user. await user.remove(); util.shutdown(); } catch (err) { console.error(err); util.shutdown(1); } } 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}, ); console.log(table.toString()); } /** * Searches for users based on their username and email address. */ async function searchUsers() { const ctx = Context.forSystem(); const searchQuery = ` query SearchUsers($value: String) { users(query: {value: $value}) { nodes { id username role profiles { id provider } } } } `; try { const answers = await inquirer.prompt({ type: 'autocomplete', name: 'userID', message: 'Search for a user', source: async (answers, value) => { if (value === null) { value = ''; } const {data, errors} = await graphql(schema, searchQuery, {}, ctx, { value, }); if (errors && errors.length > 0) { throw errors[0]; } if (data.users === null) { return []; } return data.users.nodes.map((user) => { const emails = user.emails.join(', '); return { name: `${user.username} (${emails}) ${user.id.gray} - ${user.role.gray}`, value: user.id, }; }); } }); const {userID} = answers; const user = await UserModel.findOne({id: userID}); printUserAsTable(user); util.shutdown(0); } 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 setUserRole(userID) { try { const {role} = await inquirer.prompt([ { name: 'role', message: 'User Role', type: 'list', choices: USER_ROLES } ]); 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, otherwise verifies the * first email if there is one, if there are multiple, you get a * prompt. */ async function verifyUserEmail(userID, email) { try { // Get the user. const user = await UserModel.findOne({id: userID}); if (!user) { throw new Error(`user with ID ${userID} cannot be found`); } // Get all the user's email addresses. const emails = user.emails; if (emails.length === 0) { throw new Error('user did not have any email addresses'); } if (!email && emails.length === 1) { // The email wasn't passed, and there is only one option. email = emails[0]; } 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 } ]); email = answers.email; } // Verify the email. 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('delete ') .description('delete a user') .action(deleteUser); program .command('search') .description('searches for a user based on their stored username and email') .action(searchUsers); program .command('set-role ') .description('sets the role on a user') .action(setUserRole); program .command('verify ') .description('verifies the given user\'s email address') .action(verifyUserEmail); program.parse(process.argv); // If there is no command listed, output help. if (!process.argv.slice(2).length) { program.outputHelp(); util.shutdown(); }