Files
talk/plugin-api/beta/server/getReactionConfig.js
T
2018-05-02 09:58:04 -06:00

259 lines
7.2 KiB
JavaScript

const { SEARCH_OTHER_USERS } = require('../../../perms/constants');
const { ErrNotFound, ErrAlreadyExists } = require('../../../errors');
const pluralize = require('pluralize');
const sc = require('snake-case');
const { CREATE_MONGO_INDEXES } = require('../../../config');
const Comment = require('models/comment');
function getReactionConfig(reaction) {
// Ensure that the reaction is a lowercase string.
reaction = reaction.toLowerCase();
if (CREATE_MONGO_INDEXES) {
// Create the index on the comment model based on the reaction config.
Comment.collection.createIndex(
{
created_at: 1,
[`action_counts.${sc(reaction)}`]: 1,
},
{
background: true,
}
);
}
const reactionPlural = pluralize(reaction);
const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1);
const REACTION = reaction.toUpperCase();
const REACTION_PLURAL = reactionPlural.toUpperCase();
const typeDefs = `
enum ACTION_TYPE {
# Represents a ${Reaction}.
${REACTION}
}
enum ASSET_METRICS_SORT {
# Represents a ${Reaction}Action.
${REACTION}
}
input Create${Reaction}ActionInput {
# The item's id for which we are to create a ${reaction}.
item_id: ID!
}
enum SORT_COMMENTS_BY {
# Comments will be sorted by their count of ${reactionPlural}
# on the comment.
${REACTION_PLURAL}
}
input Delete${Reaction}ActionInput {
# The item's id for which we are deleting a ${reaction}.
id: ID!
}
# ${Reaction}Action is used by users who "${reaction}" a specific entity.
type ${Reaction}Action implements Action {
# The ID of the action.
id: ID!
# The author of the action.
user: User
# The time when the Action was updated.
updated_at: Date
# The time when the Action was created.
created_at: Date
# The item's id for which the Action was created.
item_id: ID!
}
type ${Reaction}ActionSummary implements ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: ${Reaction}Action
}
# A summary of counts related to all the ${Reaction}s on an Asset.
type ${Reaction}AssetActionSummary implements AssetActionSummary {
# Number of ${reaction}s associated with actionable types on this this Asset.
actionCount: Int
# Number of unique actionable types that are referenced by the ${reaction}s.
actionableItemCount: Int
}
type Create${Reaction}ActionResponse implements Response {
# The ${reaction} that was created.
${reaction}: ${Reaction}Action
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type Delete${Reaction}ActionResponse implements Response {
# An array of errors relating to the mutation that occurred.
errors: [UserError!]
}
type RootMutation {
# Creates a ${reaction} on an entity.
create${Reaction}Action(input: Create${Reaction}ActionInput!): Create${Reaction}ActionResponse!
delete${Reaction}Action(input: Delete${Reaction}ActionInput!): Delete${Reaction}ActionResponse
}
type Subscription {
# Subscribe to ${reaction}s.
${reaction}ActionCreated(asset_id: ID!): ${Reaction}Action
# Subscribe to ${reaction} removals.
${reaction}ActionDeleted(asset_id: ID!): ${Reaction}Action
}
`;
return {
typeDefs,
context: {
Sort: () => ({
Comments: {
[reactionPlural]: {
startCursor(ctx, nodes, { cursor }) {
// The cursor is the start! This is using numeric pagination.
return cursor != null ? cursor : 0;
},
endCursor(ctx, nodes, { cursor }) {
return nodes.length
? (cursor != null ? cursor : 0) + nodes.length
: null;
},
sort(ctx, query, { cursor, sortOrder }) {
if (cursor) {
query = query.skip(cursor);
}
return query.sort({
[`action_counts.${reaction}`]: sortOrder === 'DESC' ? -1 : 1,
created_at: sortOrder === 'DESC' ? -1 : 1,
});
},
},
},
}),
},
resolvers: {
Subscription: {
[`${reaction}ActionCreated`]: ({ action }) => {
return action;
},
[`${reaction}ActionDeleted`]: ({ action }) => {
return action;
},
},
[`${Reaction}Action`]: {
// This will load the user for the specific action. We'll limit this to the
// admin users only or the current logged in user.
user({ user_id }, _, { loaders: { Users }, user }) {
if (user && (user.can(SEARCH_OTHER_USERS) || user_id === user.id)) {
return Users.getByID.load(user_id);
}
},
},
RootMutation: {
[`create${Reaction}Action`]: async (
_,
{ input: { item_id } },
{ mutators: { Action }, pubsub, loaders: { Comments } }
) => {
const comment = await Comments.get.load(item_id);
if (!comment) {
throw new ErrNotFound();
}
try {
const action = await Action.create({
item_id,
item_type: 'COMMENTS',
action_type: REACTION,
});
if (pubsub) {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionCreated`, { action, comment });
}
return {
[reaction]: action,
};
} catch (err) {
if (err instanceof ErrAlreadyExists) {
return err.metadata.existing;
}
throw err;
}
},
[`delete${Reaction}Action`]: async (
_,
{ input: { id } },
{ mutators: { Action }, pubsub, loaders: { Comments } }
) => {
const action = await Action.delete({ id });
if (!action) {
return null;
}
const comment = await Comments.get.load(action.item_id);
if (pubsub) {
// The comment is needed to allow better filtering e.g. by asset_id.
pubsub.publish(`${reaction}ActionDeleted`, { action, comment });
}
},
},
},
hooks: {
Action: {
__resolveType: {
post: ({ action_type }) =>
action_type === REACTION ? `${Reaction}Action` : undefined,
},
},
ActionSummary: {
__resolveType: {
post: ({ action_type = '' } = {}) =>
action_type === REACTION ? `${Reaction}ActionSummary` : undefined,
},
},
},
setupFunctions: {
[`${reaction}ActionCreated`]: (options, args) => ({
[`${reaction}ActionCreated`]: {
filter: ({ comment }) => comment.asset_id === args.asset_id,
},
}),
[`${reaction}ActionDeleted`]: (options, args) => ({
[`${reaction}ActionDeleted`]: {
filter: ({ comment }) => comment.asset_id === args.asset_id,
},
}),
},
};
}
module.exports = getReactionConfig;