moved user deletion into graph

This commit is contained in:
Wyatt Johnson
2018-03-12 15:39:50 -06:00
parent 2f5fc03e9c
commit f5fd162b37
5 changed files with 121 additions and 75 deletions
+20 -75
View File
@@ -7,8 +7,6 @@
const util = require('./util');
const program = require('commander');
const inquirer = require('inquirer');
const { graphql } = require('graphql');
const helpers = require('../services/migration/helpers');
const { stripIndent } = require('common-tags');
const Table = require('cli-table');
@@ -21,31 +19,15 @@ inquirer.registerPrompt(
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');
// Register the shutdown criteria.
util.onshutdown([() => mongoose.disconnect()]);
/**
* transforms a specific action to a removal action on the target model.
*/
const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({
query: { id: item_id },
update: {
$inc: {
[`action_counts.${action_type.toLowerCase()}`]: -1,
[`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1,
},
},
});
/**
* Deletes a user and cleans up their associated verifications.
*/
@@ -76,66 +58,29 @@ async function deleteUser(userID) {
return util.shutdown();
}
const { transformSingleWithCursor } = helpers({
queryBatchSize: 10000,
updateBatchSize: 10000,
});
const ctx = Context.forSystem();
console.warn("Removing user's actions");
// Remove all actions against comments.
await transformSingleWithCursor(
ActionModel.collection.find({ user_id: user.id, item_type: 'COMMENTS' }),
actionDecrTransformer,
CommentModel
const { data, errors } = await ctx.graphql(
`
mutation DeleteUser($user_id: ID!) {
delUser(id: $user_id) {
errors {
translation_key
}
}
}
`,
{ user_id: user.id }
);
if (errors) {
throw errors;
}
// Remove all actions against users.
await transformSingleWithCursor(
ActionModel.collection.find({ user_id: user.id, item_type: 'USERS' }),
actionDecrTransformer,
UserModel
);
if (data.errors) {
throw data.errors;
}
// Remove all the user's actions.
await ActionModel.where({ user_id: user.id })
.setOptions({ multi: true })
.remove();
console.warn("Removing user's comments");
// Removes all the user's reply counts on each of the comments that they
// have commented on.
await transformSingleWithCursor(
CommentModel.collection.aggregate([
{ $match: { author_id: user.id } },
{
$group: {
_id: '$parent_id',
count: { $sum: 1 },
},
},
]),
({ _id: parent_id, count }) => ({
query: { id: parent_id },
update: {
$inc: {
reply_count: -1 * count,
},
},
}),
CommentModel
);
// Remove all the user's comments.
await CommentModel.where({ author_id: user.id })
.setOptions({ multi: true })
.remove();
console.warn('Removing the user');
// Remove the user.
await user.remove();
console.log('User was deleted.');
util.shutdown();
} catch (err) {
@@ -197,7 +142,7 @@ async function searchUsers() {
value = '';
}
const { data, errors } = await graphql(schema, searchQuery, {}, ctx, {
const { data, errors } = await ctx.graphql(searchQuery, {
value,
});
if (errors && errors.length > 0) {
+88
View File
@@ -1,5 +1,6 @@
const errors = require('../../errors');
const UsersService = require('../../services/users');
const migrationHelpers = require('../../services/migration/helpers');
const {
CHANGE_USERNAME,
SET_USERNAME,
@@ -7,6 +8,7 @@ const {
SET_USER_BAN_STATUS,
SET_USER_SUSPENSION_STATUS,
UPDATE_USER_ROLES,
DELETE_USER,
} = require('../../perms/constants');
const setUserUsernameStatus = async (ctx, id, status) => {
@@ -70,6 +72,87 @@ const setRole = (ctx, id, role) => {
return UsersService.setRole(id, role);
};
/**
* transforms a specific action to a removal action on the target model.
*/
const actionDecrTransformer = ({ item_id, action_type, group_id }) => ({
query: { id: item_id },
update: {
$inc: {
[`action_counts.${action_type.toLowerCase()}`]: -1,
[`action_counts.${action_type.toLowerCase()}_${group_id.toLowerCase()}`]: -1,
},
},
});
// delUser will delete a given user with the specified id.
const delUser = async (ctx, id) => {
const { connectors: { models: { User, Action, Comment } } } = ctx;
// Find the user we're removing.
const user = await User.findOne({ id });
if (!user) {
throw errors.ErrNotFound;
}
// Get the query transformer we'll use to help batch process the user
// deletion.
const { transformSingleWithCursor } = migrationHelpers({
queryBatchSize: 10000,
updateBatchSize: 10000,
});
// Remove all actions against comments.
await transformSingleWithCursor(
Action.collection.find({ user_id: user.id, item_type: 'COMMENTS' }),
actionDecrTransformer,
Comment
);
// Remove all actions against users.
await transformSingleWithCursor(
Action.collection.find({ user_id: user.id, item_type: 'USERS' }),
actionDecrTransformer,
User
);
// Remove all the user's actions.
await Action.where({ user_id: user.id })
.setOptions({ multi: true })
.remove();
// Removes all the user's reply counts on each of the comments that they
// have commented on.
await transformSingleWithCursor(
Comment.collection.aggregate([
{ $match: { author_id: user.id } },
{
$group: {
_id: '$parent_id',
count: { $sum: 1 },
},
},
]),
({ _id: parent_id, count }) => ({
query: { id: parent_id },
update: {
$inc: {
reply_count: -1 * count,
},
},
}),
Comment
);
// Remove all the user's comments.
await Comment.where({ author_id: user.id })
.setOptions({ multi: true })
.remove();
// Remove the user.
await user.remove();
};
module.exports = ctx => {
let mutators = {
User: {
@@ -81,6 +164,7 @@ module.exports = ctx => {
setUserUsernameStatus: () => Promise.reject(errors.ErrNotAuthorized),
setUsername: () => Promise.reject(errors.ErrNotAuthorized),
stopIgnoringUser: () => Promise.reject(errors.ErrNotAuthorized),
del: () => Promise.reject(errors.ErrNotAuthorized),
},
};
@@ -116,6 +200,10 @@ module.exports = ctx => {
mutators.User.setUserSuspensionStatus = (id, until, message) =>
setUserSuspensionStatus(ctx, id, until, message);
}
if (ctx.user.can(DELETE_USER)) {
mutators.User.del = id => delUser(ctx, id);
}
}
return mutators;
+3
View File
@@ -136,6 +136,9 @@ const RootMutation = {
forceScrapeAsset: async (_, { id }, { mutators: { Asset } }) => {
await Asset.scrape(id);
},
delUser: async (_, { id }, { mutators: { User } }) => {
await User.del(id);
},
};
module.exports = RootMutation;
+9
View File
@@ -1426,6 +1426,12 @@ type ForceScrapeAssetResponse implements Response {
errors: [UserError!]
}
type DelUserResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
# All mutations for the application are defined on this object.
type RootMutation {
@@ -1523,6 +1529,9 @@ type RootMutation {
# forceScrapeAsset will force scrape the Asset with the given ID.
forceScrapeAsset(id: ID!): ForceScrapeAssetResponse
# delUser will delete the user with the specified id.
delUser(id: ID!): DelUserResponse
}
type UsernameChangedPayload {
+1
View File
@@ -18,4 +18,5 @@ module.exports = {
UPDATE_ASSET_SETTINGS: 'UPDATE_ASSET_SETTINGS',
UPDATE_ASSET_STATUS: 'UPDATE_ASSET_STATUS',
UPDATE_SETTINGS: 'UPDATE_SETTINGS',
DELETE_USER: 'DELETE_USER',
};