First draft of toxic-comments

This commit is contained in:
Chi Vinh Le
2017-09-06 22:11:48 +07:00
parent fea5f75aa6
commit 6f82fd76f5
32 changed files with 298 additions and 491 deletions
+70
View File
@@ -0,0 +1,70 @@
const {
GraphQLObjectType,
GraphQLInterfaceType
} = require('graphql');
const {maskErrors} = require('graphql-errors');
const errors = require('../errors');
const {Error: {ValidationError}} = require('mongoose');
// This function is pretty much copied verbatim from the graphql-tools repo:
// https://github.com/apollographql/graphql-tools/blob/b12973c86e00be209d04af0184780998056051c4/src/schemaGenerator.ts#L180-L194
const forEachField = (schema, fn) => {
const typeMap = schema.getTypeMap();
Object.keys(typeMap).forEach((typeName) => {
const type = typeMap[typeName];
if (type instanceof GraphQLObjectType || type instanceof GraphQLInterfaceType) {
const fields = type.getFields();
Object.keys(fields).forEach((fieldName) => {
const field = fields[fieldName];
fn(field, typeName, fieldName);
});
}
});
};
// If an APIError happens in a mutation, then respond with `{errors: Array}`
// according to the schema.
const decorateWithMutationErrorHandler = (field) => {
const fieldResolver = field.resolve;
field.resolve = async (obj, args, ctx, info) => {
try {
return await fieldResolver(obj, args, ctx, info);
}
catch(err) {
if (err instanceof errors.APIError) {
return {
errors: [err]
};
} else if (err instanceof ValidationError) {
// TODO: wrap this with one of our internal errors.
throw err;
}
throw err;
}
};
};
// Masks errors during production and handle mutation errors inside the schema.
const decorateWithErrorHandler = (schema) => {
forEachField(schema, (field, typeName) => {
// Handle mutation errors.
if (typeName === 'RootMutation') {
decorateWithMutationErrorHandler(field);
}
// If we are in production mode, don't show server errors to the front end.
if (process.env.NODE_ENV === 'production') {
// Mask errors that are thrown if we are in a production environment.
maskErrors(field);
}
});
};
module.exports = {
decorateWithErrorHandler,
};
+3 -2
View File
@@ -154,7 +154,7 @@ const adjustKarma = (Comments, id, status) => async () => {
* @param {String} [status='NONE'] the status of the new comment
* @return {Promise} resolves to the created comment
*/
const createComment = async (context, {tags = [], body, asset_id, parent_id = null}, status = 'NONE') => {
const createComment = async (context, {tags = [], body, asset_id, parent_id = null, metadata = {}}, status = 'NONE') => {
const {user, loaders: {Comments}, pubsub} = context;
// Resolve the tags for the comment.
@@ -166,7 +166,8 @@ const createComment = async (context, {tags = [], body, asset_id, parent_id = nu
parent_id,
status,
tags,
author_id: user.id
author_id: user.id,
metadata,
});
// If the loaders are present, clear the caches for these values because we
+38 -31
View File
@@ -1,35 +1,41 @@
const wrapResponse = require('../helpers/response');
const RootMutation = {
createComment(_, {comment}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.create(comment));
async createComment(_, {input}, {mutators: {Comment}}) {
return {
comment: await Comment.create(input),
};
},
editComment(_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.edit({id, asset_id, edit: {body}}));
async editComment(_, {id, asset_id, edit: {body}}, {mutators: {Comment}}) {
return {
comment: await Comment.edit({id, asset_id, edit: {body}}),
};
},
createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) {
return wrapResponse('flag')(Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}));
async createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) {
return {
flag: Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}),
};
},
createDontAgree(_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) {
return wrapResponse('dontagree')(Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}));
async createDontAgree(_, {dontagree: {item_id, item_type, reason, message}}, {mutators: {Action}}) {
return {
dontagree: await Action.create({item_id, item_type, action_type: 'DONTAGREE', group_id: reason, metadata: {message}}),
};
},
deleteAction(_, {id}, {mutators: {Action}}) {
return wrapResponse(null)(Action.delete({id}));
async deleteAction(_, {id}, {mutators: {Action}}) {
await Action.delete({id});
},
setUserStatus(_, {id, status}, {mutators: {User}}) {
return wrapResponse(null)(User.setUserStatus({id, status}));
async setUserStatus(_, {id, status}, {mutators: {User}}) {
await User.setUserStatus({id, status});
},
suspendUser(_, {input: {id, message, until}}, {mutators: {User}}) {
return wrapResponse(null)(User.suspendUser({id, message, until}));
async suspendUser(_, {input: {id, message, until}}, {mutators: {User}}) {
await User.suspendUser({id, message, until});
},
rejectUsername(_, {input: {id, message}}, {mutators: {User}}) {
return wrapResponse(null)(User.rejectUsername({id, message}));
async rejectUsername(_, {input: {id, message}}, {mutators: {User}}) {
await User.rejectUsername({id, message});
},
ignoreUser(_, {id}, {mutators: {User}}) {
return wrapResponse(null)(User.ignoreUser({id}));
async ignoreUser(_, {id}, {mutators: {User}}) {
await User.ignoreUser({id});
},
stopIgnoringUser(_, {id}, {mutators: {User}}) {
return wrapResponse(null)(User.stopIgnoringUser({id}));
async stopIgnoringUser(_, {id}, {mutators: {User}}) {
await User.stopIgnoringUser({id});
},
async setCommentStatus(_, {id, status}, {mutators: {Comment}, pubsub}) {
const comment = await Comment.setStatus({id, status});
@@ -42,19 +48,20 @@ const RootMutation = {
// Publish the comment status change via the subscription.
pubsub.publish('commentRejected', comment);
}
return wrapResponse(null)(comment);
},
addTag(_, {tag}, {mutators: {Tag}}) {
return wrapResponse(null)(Tag.add(tag));
async addTag(_, {tag}, {mutators: {Tag}}) {
await Tag.add(tag);
},
removeTag(_, {tag}, {mutators: {Tag}}) {
return wrapResponse(null)(Tag.remove(tag));
async removeTag(_, {tag}, {mutators: {Tag}}) {
await Tag.remove(tag);
},
createToken(_, {input}, {mutators: {Token}}) {
return wrapResponse('token')(Token.create(input));
async createToken(_, {input}, {mutators: {Token}}) {
return {
token: await Token.create(input),
};
},
revokeToken(_, {input}, {mutators: {Token}}) {
return wrapResponse(null)(Token.revoke(input));
async revokeToken(_, {input}, {mutators: {Token}}) {
await Token.revoke(input);
}
};
+3 -7
View File
@@ -1,6 +1,6 @@
const {makeExecutableSchema} = require('graphql-tools');
const {maskErrors} = require('graphql-errors');
const {decorateWithHooks} = require('./hooks');
const {decorateWithErrorHandler} = require('./errorHandler');
const plugins = require('../services/plugins');
const resolvers = require('./resolvers');
@@ -11,11 +11,7 @@ const schema = makeExecutableSchema({typeDefs, resolvers});
// Plugin to the schema level resolvers to provide an before/after hook.
decorateWithHooks(schema, plugins.get('server', 'hooks'));
// If we are in production mode, don't show server errors to the front end.
if (process.env.NODE_ENV === 'production') {
// Mask errors that are thrown if we are in a production environment.
maskErrors(schema);
}
// Handle errors like masking in production and mutation errors.
decorateWithErrorHandler(schema);
module.exports = schema;
+4 -4
View File
@@ -1033,7 +1033,7 @@ type RevokeTokenResponse implements Response {
type RootMutation {
# Creates a comment on the asset.
createComment(comment: CreateCommentInput!): CreateCommentResponse
createComment(input: CreateCommentInput!): CreateCommentResponse
# Creates a flag on an entity.
createFlag(flag: CreateFlagInput!): CreateFlagResponse
@@ -1060,10 +1060,10 @@ type RootMutation {
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
# Add a tag.
addTag(tag: ModifyTagInput!): ModifyTagResponse!
addTag(tag: ModifyTagInput!): ModifyTagResponse
# Removes a tag.
removeTag(tag: ModifyTagInput!): ModifyTagResponse!
removeTag(tag: ModifyTagInput!): ModifyTagResponse
# Ignore comments by another user
ignoreUser(id: ID!): IgnoreUserResponse
@@ -1072,7 +1072,7 @@ type RootMutation {
createToken(input: CreateTokenInput!): CreateTokenResponse!
# RevokeToken will revoke an existing token.
revokeToken(input: RevokeTokenInput!): RevokeTokenResponse!
revokeToken(input: RevokeTokenInput!): RevokeTokenResponse
# Stop Ignoring comments by another user
stopIgnoringUser(id: ID!): StopIgnoringUserResponse