Skip to content

Commit

Permalink
[CORL-667] Constraint Directive (#2641)
Browse files Browse the repository at this point in the history
* feat: added constraints

* fix: fixes bug with index creation

- fixes #2612

* fix: added default resolver

* fix: added defaultTo to handle defaulting
  • Loading branch information
wyattjoh authored and kgardnr committed Oct 16, 2019
1 parent ea71230 commit b38a4f1
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 92 deletions.
45 changes: 45 additions & 0 deletions src/core/server/graph/common/directives/constraint.ts
@@ -0,0 +1,45 @@
import {
defaultFieldResolver,
GraphQLArgument,
GraphQLField,
GraphQLInterfaceType,
GraphQLObjectType,
} from "graphql";
import { SchemaDirectiveVisitor } from "graphql-tools";
import { isNumber } from "lodash";

export default class extends SchemaDirectiveVisitor {
public visitArgumentDefinition(
argument: GraphQLArgument,
details: {
field: GraphQLField<any, any>;
objectType: GraphQLObjectType | GraphQLInterfaceType;
}
) {
const originalResolver = details.field.resolve || defaultFieldResolver;
details.field.resolve = async (...resolveArgs) => {
const argName = argument.name;
const args = resolveArgs[1]; // (parent, args, context, info)
const valueToValidate = args[argName];

// Apply number validations.
if (isNumber(valueToValidate)) {
// Apply min validation.
if (isNumber(this.args.min)) {
if (valueToValidate < this.args.min) {
throw new Error("too short");
}
}

// Apply max validation.
if (isNumber(this.args.max)) {
if (valueToValidate > this.args.max) {
throw new Error("too long");
}
}
}

return originalResolver.apply(this, resolveArgs);
};
}
}
6 changes: 4 additions & 2 deletions src/core/server/graph/tenant/loaders/CommentActions.ts
@@ -1,13 +1,15 @@
import { defaultTo } from "lodash";

import Context from "coral-server/graph/tenant/context";
import {
CommentActionConnectionInput,
retrieveCommentActionConnection,
} from "coral-server/models/action/comment";

export default (ctx: Context) => ({
connection: ({ first = 10, after, filter }: CommentActionConnectionInput) =>
connection: ({ first, after, filter }: CommentActionConnectionInput) =>
retrieveCommentActionConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
filter,
}),
Expand Down
10 changes: 6 additions & 4 deletions src/core/server/graph/tenant/loaders/CommentModerationActions.ts
@@ -1,3 +1,5 @@
import { defaultTo } from "lodash";

import TenantContext from "coral-server/graph/tenant/context";
import {
CommentToStatusHistoryArgs,
Expand All @@ -7,22 +9,22 @@ import { retrieveCommentModerationActionConnection } from "coral-server/models/a

export default (ctx: TenantContext) => ({
forModerator: (
{ first = 10, after }: UserToCommentModerationActionHistoryArgs,
{ first, after }: UserToCommentModerationActionHistoryArgs,
moderatorID: string
) =>
retrieveCommentModerationActionConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
filter: {
moderatorID,
},
}),
forComment: (
{ first = 10, after }: CommentToStatusHistoryArgs,
{ first, after }: CommentToStatusHistoryArgs,
commentID: string
) =>
retrieveCommentModerationActionConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
filter: {
commentID,
Expand Down
73 changes: 21 additions & 52 deletions src/core/server/graph/tenant/loaders/Comments.ts
@@ -1,5 +1,5 @@
import DataLoader from "dataloader";
import { isNil, omitBy } from "lodash";
import { defaultTo, isNil, omitBy } from "lodash";
import { DateTime } from "luxon";

import Context from "coral-server/graph/tenant/context";
Expand Down Expand Up @@ -126,15 +126,15 @@ export default (ctx: Context) => ({
}
),
forFilter: ({
first = 10,
first,
after,
storyID,
status,
tag,
query,
}: QueryToCommentsArgs) =>
retrieveCommentConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
orderBy: GQLCOMMENT_SORT.CREATED_AT_DESC,
filter: omitBy(
Expand Down Expand Up @@ -164,71 +164,45 @@ export default (ctx: Context) => ({
);
}
),
forUser: (
userID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: UserToCommentsArgs
) =>
forUser: (userID: string, { first, orderBy, after }: UserToCommentsArgs) =>
retrieveCommentUserConnection(ctx.mongo, ctx.tenant.id, userID, {
first,
orderBy,
first: defaultTo(first, 10),
orderBy: defaultTo(orderBy, GQLCOMMENT_SORT.CREATED_AT_DESC),
after,
}).then(primeCommentsFromConnection(ctx)),
forUserAll: (
userID: string,
// Apply the graph schema defaults at the loader.
{ first = 10, after }: UserToAllCommentsArgs
) =>
forUserAll: (userID: string, { first, after }: UserToAllCommentsArgs) =>
retrieveAllCommentsUserConnection(ctx.mongo, ctx.tenant.id, userID, {
first,
first: defaultTo(first, 10),
orderBy: GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}).then(primeCommentsFromConnection(ctx)),
forUserRejected: (
userID: string,
// Apply the graph schema defaults at the loader.
{ first = 10, after }: UserToRejectedCommentsArgs
{ first, after }: UserToRejectedCommentsArgs
) =>
retrieveRejectedCommentUserConnection(ctx.mongo, ctx.tenant.id, userID, {
first,
first: defaultTo(first, 10),
orderBy: GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}).then(primeCommentsFromConnection(ctx)),
taggedForStory: (
storyID: string,
tag: GQLTAG,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: StoryToCommentsArgs
{ first, orderBy, after }: StoryToCommentsArgs
) =>
retrieveCommentStoryConnection(ctx.mongo, ctx.tenant.id, storyID, {
first,
orderBy,
first: defaultTo(first, 10),
orderBy: defaultTo(orderBy, GQLCOMMENT_SORT.CREATED_AT_DESC),
after,
filter: {
// Filter optionally for comments with a specific tag.
"tags.type": tag,
},
}).then(primeCommentsFromConnection(ctx)),
forStory: (
storyID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: StoryToCommentsArgs
) =>
forStory: (storyID: string, { first, orderBy, after }: StoryToCommentsArgs) =>
retrieveCommentStoryConnection(ctx.mongo, ctx.tenant.id, storyID, {
first,
orderBy,
first: defaultTo(first, 10),
orderBy: defaultTo(orderBy, GQLCOMMENT_SORT.CREATED_AT_DESC),
after,
filter: {
// Only get Comments that are top level. If the client wants to load
Expand All @@ -239,27 +213,22 @@ export default (ctx: Context) => ({
forParent: (
storyID: string,
parentID: string,
// Apply the graph schema defaults at the loader.
{
first = 10,
orderBy = GQLCOMMENT_SORT.CREATED_AT_DESC,
after,
}: CommentToRepliesArgs
{ first, orderBy, after }: CommentToRepliesArgs
) =>
retrieveCommentRepliesConnection(
ctx.mongo,
ctx.tenant.id,
storyID,
parentID,
{
first,
orderBy,
first: defaultTo(first, 10),
orderBy: defaultTo(orderBy, GQLCOMMENT_SORT.CREATED_AT_DESC),
after,
}
).then(primeCommentsFromConnection(ctx)),
parents: (comment: Comment, { last = 1, before }: CommentToParentsArgs) =>
parents: (comment: Comment, { last, before }: CommentToParentsArgs) =>
retrieveCommentParentsConnection(ctx.mongo, ctx.tenant.id, comment, {
last,
last: defaultTo(last, 1),
// The cursor passed here is always going to be a number.
before: before as number,
}).then(primeCommentsFromConnection(ctx)),
Expand Down
5 changes: 3 additions & 2 deletions src/core/server/graph/tenant/loaders/Stories.ts
@@ -1,4 +1,5 @@
import DataLoader from "dataloader";
import { defaultTo } from "lodash";

import TenantContext from "coral-server/graph/tenant/context";
import {
Expand Down Expand Up @@ -99,9 +100,9 @@ export default (ctx: TenantContext) => ({
cache: !ctx.disableCaching,
}
),
connection: ({ first = 10, after, status, query }: QueryToStoriesArgs) =>
connection: ({ first, after, status, query }: QueryToStoriesArgs) =>
retrieveStoryConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
filter: {
// Merge the status filter into the connection filter.
Expand Down
11 changes: 3 additions & 8 deletions src/core/server/graph/tenant/loaders/Users.ts
@@ -1,4 +1,5 @@
import DataLoader from "dataloader";
import { defaultTo } from "lodash";

import Context from "coral-server/graph/tenant/context";
import {
Expand Down Expand Up @@ -112,15 +113,9 @@ export default (ctx: Context) => {

return {
user,
connection: ({
first = 10,
after,
role,
query,
status,
}: QueryToUsersArgs) =>
connection: ({ first, after, role, query, status }: QueryToUsersArgs) =>
retrieveUserConnection(ctx.mongo, ctx.tenant.id, {
first,
first: defaultTo(first, 10),
after,
filter: {
// Merge the role filters into the query.
Expand Down
5 changes: 3 additions & 2 deletions src/core/server/graph/tenant/resolvers/Comment.ts
@@ -1,4 +1,5 @@
import { GraphQLResolveInfo } from "graphql";
import { defaultTo } from "lodash";

import { StoryNotFoundError } from "coral-server/errors";
import { getRequestedFields } from "coral-server/graph/tenant/resolvers/util";
Expand Down Expand Up @@ -83,9 +84,9 @@ export const Comment: GQLCommentTypeResolver<comment.Comment> = {
},
// Action Counts are encoded, decode them for use with the GraphQL system.
actionCounts: c => decodeActionCounts(c.actionCounts),
flags: ({ id }, { first = 10, after }, ctx) =>
flags: ({ id }, { first, after }, ctx) =>
ctx.loaders.CommentActions.connection({
first,
first: defaultTo(first, 10),
after,
filter: {
actionType: ACTION_TYPE.FLAG,
Expand Down
11 changes: 7 additions & 4 deletions src/core/server/graph/tenant/resolvers/ModerationQueue.ts
@@ -1,3 +1,5 @@
import { defaultTo } from "lodash";

import {
CommentConnectionInput,
retrieveCommentConnection,
Expand All @@ -24,11 +26,12 @@ export const ModerationQueue: GQLModerationQueueTypeResolver<

return selector;
},
comments: ({ connection }, { first = 10, after }, { mongo, tenant }) =>
retrieveCommentConnection(mongo, tenant.id, {
comments: ({ connection }, { first, after }, { mongo, tenant }) => {
return retrieveCommentConnection(mongo, tenant.id, {
...connection,
first,
first: defaultTo(first, 10),
after,
orderBy: GQLCOMMENT_SORT.CREATED_AT_DESC,
}),
});
},
};
12 changes: 11 additions & 1 deletion src/core/server/graph/tenant/schema/index.ts
@@ -1,7 +1,12 @@
import { attachDirectiveResolvers, IResolvers } from "graphql-tools";
import {
attachDirectiveResolvers,
IResolvers,
SchemaDirectiveVisitor,
} from "graphql-tools";

import { loadSchema } from "coral-common/graphql";
import auth from "coral-server/graph/common/directives/auth";
import constraint from "coral-server/graph/common/directives/constraint";
import resolvers from "coral-server/graph/tenant/resolvers";

export default function getTenantSchema() {
Expand All @@ -10,5 +15,10 @@ export default function getTenantSchema() {
// Attach the directive resolvers.
attachDirectiveResolvers(schema, { auth });

// Attach the constraint directive.
SchemaDirectiveVisitor.visitSchemaDirectives(schema, {
constraint,
});

return schema;
}

0 comments on commit b38a4f1

Please sign in to comment.