diff --git a/package-lock.json b/package-lock.json index efc1a3a5c..78177d1c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1342,9 +1342,9 @@ } }, "@coralproject/rte": { - "version": "0.10.11", - "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.11.tgz", - "integrity": "sha512-Tq8NznCOYx84QpHhZbUldxcYrztEQnnNjDC2aW5HhzltO9nBG2Hu78MzTwliML3MmaTFbLehdIb/BKGKj4eMSw==", + "version": "0.10.12", + "resolved": "https://registry.npmjs.org/@coralproject/rte/-/rte-0.10.12.tgz", + "integrity": "sha512-w7UWe6u+TNoPtFcWvyjYkV7eaAE4ccTOYrhdWPsDfM34ZDRq+bM5eiiAMcUVHizvHgsUzFWoN2qjOo+jSfIjCw==", "dev": true, "requires": { "bowser": "^1.0.0", @@ -11379,6 +11379,11 @@ "source-map-support": "^0.5.1" } }, + "graphql-fields": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/graphql-fields/-/graphql-fields-1.1.0.tgz", + "integrity": "sha1-okZtHEFFVNq4DA93d9a6mg4PdbQ=" + }, "graphql-import": { "version": "0.4.5", "resolved": "https://registry.npmjs.org/graphql-import/-/graphql-import-0.4.5.tgz", @@ -23373,6 +23378,11 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, + "striptags": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.1.1.tgz", + "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0=" + }, "style-loader": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", diff --git a/package.json b/package.json index 9a804811f..d95ef611d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "fs-extra": "^6.0.1", "graphql": "^0.13.2", "graphql-config": "^2.0.1", + "graphql-fields": "^1.1.0", "graphql-playground-middleware-express": "^1.7.2", "graphql-redis-subscriptions": "^1.5.0", "graphql-tools": "^3.0.5", @@ -75,6 +76,7 @@ "passport-strategy": "^1.0.0", "performance-now": "^2.1.0", "permit": "^0.2.4", + "striptags": "^3.1.1", "subscriptions-transport-ws": "^0.9.12", "tlds": "^1.203.1", "uuid": "^3.3.2" @@ -86,7 +88,7 @@ "@babel/polyfill": "7.0.0-beta.49", "@babel/preset-env": "7.0.0-beta.49", "@babel/preset-react": "7.0.0-beta.49", - "@coralproject/rte": "^0.10.11", + "@coralproject/rte": "^0.10.12", "@types/bcryptjs": "^2.4.1", "@types/bull": "^3.3.16", "@types/bunyan": "^1.8.4", diff --git a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx index d383a4e7e..dd9ac740a 100644 --- a/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx +++ b/src/core/client/stream/tabs/comments/containers/ReplyListContainer.spec.tsx @@ -35,14 +35,14 @@ it("renders correctly", () => { expect(wrapper).toMatchSnapshot(); }); -it("renders correctly when replies are null", () => { +it("renders correctly when replies are empty", () => { const props: PropTypesOf = { asset: { id: "asset-id", }, comment: { id: "comment-id", - replies: null, + replies: { edges: [] }, }, relay: { hasMore: noop, diff --git a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap index d98e6786a..fc88a3379 100644 --- a/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/containers/__snapshots__/ReplyListContainer.spec.tsx.snap @@ -45,7 +45,7 @@ exports[`renders correctly 1`] = ` /> `; -exports[`renders correctly when replies are null 1`] = `""`; +exports[`renders correctly when replies are empty 1`] = `""`; exports[`when has more replies renders hasMore 1`] = ` { edited: false, editableUntil: "2018-07-06T18:24:30.000Z", }, + replies: { edges: [], pageInfo: {} }, }, }, clientMutationId: "0", diff --git a/src/core/server/graph/tenant/loaders/comments.ts b/src/core/server/graph/tenant/loaders/comments.ts index a77e50894..f696eb552 100644 --- a/src/core/server/graph/tenant/loaders/comments.ts +++ b/src/core/server/graph/tenant/loaders/comments.ts @@ -3,15 +3,36 @@ import DataLoader from "dataloader"; import Context from "talk-server/graph/tenant/context"; import { AssetToCommentsArgs, + CommentToParentsArgs, CommentToRepliesArgs, GQLCOMMENT_SORT, } from "talk-server/graph/tenant/schema/__generated__/types"; import { + Comment, retrieveCommentAssetConnection, + retrieveCommentParentsConnection, retrieveCommentRepliesConnection, retrieveCommentUserConnection, retrieveManyComments, } from "talk-server/models/comment"; +import { Connection } from "talk-server/models/connection"; + +/** + * primeCommentsFromConnection will prime a given context with the comments + * retrieved via a connection. + * + * @param ctx graph context to use to prime the loaders. + */ +const primeCommentsFromConnection = (ctx: Context) => ( + connection: Readonly>> +) => { + // For each of the edges, prime the comment loader. + connection.edges.forEach(({ node }) => { + ctx.loaders.Comments.comment.prime(node.id, node); + }); + + return connection; +}; export default (ctx: Context) => ({ comment: new DataLoader((ids: string[]) => @@ -44,7 +65,7 @@ export default (ctx: Context) => ({ first, orderBy, after, - }), + }).then(primeCommentsFromConnection(ctx)), forParent: ( assetID: string, parentID: string, @@ -65,5 +86,11 @@ export default (ctx: Context) => ({ orderBy, after, } - ), + ).then(primeCommentsFromConnection(ctx)), + parents: (comment: Comment, { last = 1, before }: CommentToParentsArgs) => + retrieveCommentParentsConnection(ctx.mongo, ctx.tenant.id, comment, { + last, + // The cursor passed here is always going to be a number. + before: before as number, + }).then(primeCommentsFromConnection(ctx)), }); diff --git a/src/core/server/graph/tenant/loaders/users.ts b/src/core/server/graph/tenant/loaders/users.ts index c684a9d23..2b070c6a0 100644 --- a/src/core/server/graph/tenant/loaders/users.ts +++ b/src/core/server/graph/tenant/loaders/users.ts @@ -2,8 +2,17 @@ import DataLoader from "dataloader"; import Context from "talk-server/graph/tenant/context"; import { retrieveManyUsers, User } from "talk-server/models/user"; -export default (ctx: Context) => ({ - user: new DataLoader(ids => +export default (ctx: Context) => { + const user = new DataLoader(ids => retrieveManyUsers(ctx.mongo, ctx.tenant.id, ids) - ), -}); + ); + + if (ctx.user) { + // Prime the current logged in user in the dataloader cache. + user.prime(ctx.user.id, ctx.user); + } + + return { + user, + }; +}; diff --git a/src/core/server/graph/tenant/resolvers/comment.ts b/src/core/server/graph/tenant/resolvers/comment.ts index ee775ff0f..d93612354 100644 --- a/src/core/server/graph/tenant/resolvers/comment.ts +++ b/src/core/server/graph/tenant/resolvers/comment.ts @@ -1,5 +1,10 @@ -import { GQLCommentTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; +import { getRequestedFields } from "talk-server/graph/tenant/resolvers/util"; +import { + GQLComment, + GQLCommentTypeResolver, +} from "talk-server/graph/tenant/schema/__generated__/types"; import { Comment } from "talk-server/models/comment"; +import { createConnection } from "talk-server/models/connection"; const Comment: GQLCommentTypeResolver = { editing: (comment, input, ctx) => ({ @@ -16,7 +21,63 @@ const Comment: GQLCommentTypeResolver = { author: (comment, input, ctx) => ctx.loaders.Users.user.load(comment.author_id), replies: (comment, input, ctx) => - ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input), + comment.reply_count > 0 + ? ctx.loaders.Comments.forParent(comment.asset_id, comment.id, input) + : createConnection(), + parentCount: comment => + comment.parent_id ? comment.grandparent_ids.length + 1 : 0, + depth: comment => + comment.parent_id ? comment.grandparent_ids.length + 1 : 0, + replyCount: comment => comment.reply_count, + rootParent: (comment, input, ctx, info) => { + // If there isn't a parent, then return nothing! + if (!comment.parent_id) { + return null; + } + + // rootParentID is the root parent id for a given comment. + const rootParentID = + comment.grandparent_ids.length > 0 + ? comment.grandparent_ids[0] + : comment.parent_id; + + // Get the field names of the fields being requested, if it's only the ID, + // we have that, so no need to make a database request. + const fields = getRequestedFields(info); + if (fields.length === 1 && fields[0] === "id") { + return { + id: rootParentID, + }; + } + + // We want more than the ID! Get the comment! + // TODO: (wyattjoh) if the parent and the parents (containing the parent) are requested, the parent comment is retrieved from the database twice. Investigate ways of reducing i/o. + return ctx.loaders.Comments.comment.load(rootParentID); + }, + parent: (comment, input, ctx, info) => { + // If there isn't a parent, then return nothing! + if (!comment.parent_id) { + return null; + } + + // Get the field names of the fields being requested, if it's only the ID, + // we have that, so no need to make a database request. + const fields = getRequestedFields(info); + if (fields.length === 1 && fields[0] === "id") { + return { + id: comment.parent_id, + }; + } + + // We want more than the ID! Get the comment! + // TODO: (wyattjoh) if the parent and the parents (containing the parent) are requested, the parent comment is retrieved from the database twice. Investigate ways of reducing i/o. + return ctx.loaders.Comments.comment.load(comment.parent_id); + }, + parents: (comment, input, ctx) => + // Some resolver optimization. + comment.parent_id + ? ctx.loaders.Comments.parents(comment, input) + : createConnection(), }; export default Comment; diff --git a/src/core/server/graph/tenant/resolvers/util.ts b/src/core/server/graph/tenant/resolvers/util.ts new file mode 100644 index 000000000..f35b4789d --- /dev/null +++ b/src/core/server/graph/tenant/resolvers/util.ts @@ -0,0 +1,12 @@ +import { GraphQLResolveInfo } from "graphql"; +import graphqlFields from "graphql-fields"; +import { pull } from "lodash"; + +/** + * getRequestedFields returns the fields in an array that are being queried for. + * + * @param info query information + */ +export function getRequestedFields(info: GraphQLResolveInfo) { + return pull(Object.keys(graphqlFields(info)), "__typename"); +} diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index cf5e0c40c..5b3e716e1 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -289,6 +289,27 @@ type Karma { thresholds: KarmaThresholds! } +################################################################################ +## CharCount +################################################################################ + +type CharCount { + """ + enabled when true, enables the character count moderation phase. + """ + enabled: Boolean! + + """ + min is the smallest length of a Comment that may be posted. + """ + min: Int + + """ + max is the largest length of a Comment that may be posted. + """ + max: Int +} + ################################################################################ ## Email ################################################################################ @@ -411,14 +432,9 @@ type Settings { editCommentWindowLength: Int! """ - charCountEnable is true when the character count restriction is enabled. + charCount stores the character count moderation settings. """ - charCountEnable: Boolean! - - """ - charCount is the maximum number of characters a comment may be. - """ - charCount: Int + charCount: CharCount! """ organizationName is the name of the organization. @@ -633,11 +649,22 @@ type Comment { """ status: COMMENT_STATUS! + """ + parentCount is the number of direct parents for this Comment. Currently this + value is the same as depth. + """ + parentCount: Int! + + """ + depth is the number of levels that a given comment is deep. + """ + depth: Int! + """ replyCount is the number of replies. Only direct replies to this Comment are counted. Deleted comments are included in this count. """ - replyCount: Int + replyCount: Int! """ replies will return the replies to this Comment. @@ -646,7 +673,24 @@ type Comment { first: Int = 10 orderBy: COMMENT_SORT = CREATED_AT_DESC after: Cursor - ): CommentsConnection + ): CommentsConnection! + + """ + parent is the immediate parent of a given comment. + """ + parent: Comment + + """ + rootParent is the highest level parent Comment. This Comment would have been + left on the Asset itself. + """ + rootParent: Comment + + """ + parents returns a CommentsConnection that allows accessing direct parents of + the given Comment. + """ + parents(last: Int = 1, before: Cursor): CommentsConnection! """ editing returns details about the edit status of a Comment. @@ -806,11 +850,6 @@ type Query { """ comment(id: ID!): Comment - """ - assets returns a AssetsConnection. - """ - assets(cursor: Cursor, limit: Int = 10): AssetsConnection - """ asset is the Asset specified by its ID/URL. """ @@ -1113,6 +1152,23 @@ input SettingsKarmaInput { thresholds: SettingsKarmaThresholdsInput } +input SettingsCharCountInput { + """ + enabled when true, enables the character count moderation phase. + """ + enabled: Boolean + + """ + min is the smallest length of a Comment that may be posted. + """ + min: Int + + """ + max is the largest length of a Comment that may be posted. + """ + max: Int +} + """ SettingsInput is the partial type of the Settings type for performing mutations. """ @@ -1204,16 +1260,6 @@ input SettingsInput { """ editCommentWindowLength: Int - """ - charCountEnable is true when the character count restriction is enabled. - """ - charCountEnable: Boolean - - """ - charCount is the maximum number of characters a comment may be. - """ - charCount: Int - """ organizationName is the name of the organization. """ @@ -1249,6 +1295,11 @@ input SettingsInput { handled. """ karma: SettingsKarmaInput + + """ + charCount stores the character count moderation settings. + """ + charCount: SettingsCharCountInput } """ diff --git a/src/core/server/models/comment.ts b/src/core/server/models/comment.ts index dc64046c1..bc47f6c96 100644 --- a/src/core/server/models/comment.ts +++ b/src/core/server/models/comment.ts @@ -10,6 +10,7 @@ import { import { ActionCounts } from "talk-server/models/actions"; import { Connection, + createConnection, Cursor, getPageInfo, nodesToEdges, @@ -28,7 +29,7 @@ export interface BodyHistoryItem { } export interface StatusHistoryItem { - status: GQLCOMMENT_STATUS; // TODO: migrate field + status: GQLCOMMENT_STATUS; assigned_by?: string; created_at: Date; } @@ -43,6 +44,8 @@ export interface Comment extends TenantResource { status: GQLCOMMENT_STATUS; status_history: StatusHistoryItem[]; action_counts: ActionCounts; + grandparent_ids: string[]; + reply_ids: string[]; reply_count: number; created_at: Date; deleted_at?: Date; @@ -54,6 +57,7 @@ export type CreateCommentInput = Omit< | "id" | "tenant_id" | "created_at" + | "reply_ids" | "reply_count" | "body_history" | "status_history" @@ -75,6 +79,7 @@ export async function createComment( id: uuid.v4(), tenant_id: tenantID, created_at: now, + reply_ids: [], reply_count: 0, body_history: [ { @@ -102,6 +107,31 @@ export async function createComment( return comment; } +/** + * pushChildCommentIDOntoParent will push the new child comment's ID onto the + * parent comment so it can reference direct children. + */ +export async function pushChildCommentIDOntoParent( + mongo: Db, + tenantID: string, + parentID: string, + childID: string +) { + // This pushes the new child ID onto the parent comment. + const result = await collection(mongo).findOneAndUpdate( + { + tenant_id: tenantID, + id: parentID, + }, + { + $push: { reply_ids: childID }, + $inc: { reply_count: 1 }, + } + ); + + return result.value; +} + export type EditCommentInput = Pick< Comment, "id" | "author_id" | "body" | "status" | "metadata" @@ -273,6 +303,100 @@ export async function retrieveCommentRepliesConnection( return retrieveConnection(input, query); } +/** + * retrieveCommentParentsConnection will return a comment connection used to + * represent the parents of a given comment. + * + * @param mongo the database connection to use when retrieving comments + * @param tenantID the tenant id for where the comment exists + * @param commentID the id of the comment to retrieve parents of + * @param pagination pagination options to paginate the results + */ +export async function retrieveCommentParentsConnection( + mongo: Db, + tenantID: string, + comment: Comment, + { last: limit, before: skip = -1 }: { last: number; before?: number } +): Promise>>> { + // Return nothing if this comment does not have any parents. + if (!comment.parent_id) { + return createConnection({ + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + }, + }); + } + + // TODO: (wyattjoh) maybe throw an error when the limit is zero? + + if (limit <= 0) { + return createConnection({ + pageInfo: { + hasNextPage: false, + hasPreviousPage: false, + }, + }); + } + + // If the last paramter is 1, and the after paramter is either unset or equal + // to zero, then all we have to return is the direct parent. + if (limit === 1 && skip < 0) { + const parent = await retrieveComment(mongo, tenantID, comment.parent_id); + if (!parent) { + throw new Error("parent comment not found"); + } + + return { + edges: [{ node: parent, cursor: 0 }], + pageInfo: { + hasNextPage: false, + hasPreviousPage: comment.grandparent_ids.length > 0, + endCursor: 0, + startCursor: 0, + }, + }; + } + + // Create a list of all the comment parent ids, in reverse order. + const parentIDs = [comment.parent_id, ...comment.grandparent_ids.reverse()]; + + // Fetch the subset of the comment id's that we are going to query for. + const parentIDSubset = parentIDs.slice(skip + 1, skip + 1 + limit); + + // Retrieve the parents via the subset list. + const parents = await retrieveManyComments(mongo, tenantID, parentIDSubset); + + // Loop over the list to ensure that none of the entries is null (indicating + // that there was a misplaced parent). We can assert the type here because we + // will throw an error and abort if one of the comments are null. + parents.forEach(parentComment => { + if (!parentComment) { + // TODO: (wyattjoh) replace with a better error. + throw new Error("parent id specified does not exist"); + } + + return true; + }); + + const edges = nodesToEdges( + // We can't have a null parent after the forEach filter above. + parents as Array>, + (_, index) => index + skip + 1 + ).reverse(); + + // Return the resolved connection. + return { + edges, + pageInfo: { + hasNextPage: false, + hasPreviousPage: parentIDs.length > limit + skip, + startCursor: edges.length > 0 ? edges[0].cursor : null, + endCursor: edges.length > 0 ? edges[edges.length - 1].cursor : null, + }, + }; +} + /** * retrieveAssetConnection returns a Connection for a given Asset's * comments. diff --git a/src/core/server/models/connection.ts b/src/core/server/models/connection.ts index e45e9657a..3eeacd937 100644 --- a/src/core/server/models/connection.ts +++ b/src/core/server/models/connection.ts @@ -1,3 +1,5 @@ +import { merge } from "lodash"; + export type Cursor = Date | number | string | null; export interface Edge { @@ -17,6 +19,25 @@ export interface Connection { pageInfo: PageInfo; } +/** + * createConnection will create a base Connection that can be used to satisfy + * the Connection interface. + * + * @param connection the base connection to optionally merge with the default base + * connection details. + */ +export function createConnection( + connection: Partial> = {} +): Connection { + return merge( + { + edges: [], + pageInfo: {}, + }, + connection + ); +} + export interface PaginationArgs { first: number; } diff --git a/src/core/server/models/settings.ts b/src/core/server/models/settings.ts index 451bfdf7a..cf5a3e2c7 100644 --- a/src/core/server/models/settings.ts +++ b/src/core/server/models/settings.ts @@ -1,5 +1,6 @@ import { GQLAuth, + GQLCharCount, GQLEmail, GQLExternalIntegrations, GQLKarma, @@ -62,8 +63,7 @@ export interface ModerationSettings { closedMessage?: string; disableCommenting: boolean; disableCommentingMessage?: string; - charCountEnable: boolean; - charCount?: number; + charCount: GQLCharCount; } export interface Settings extends ModerationSettings { diff --git a/src/core/server/models/tenant.ts b/src/core/server/models/tenant.ts index f67982e6d..7aebe644b 100644 --- a/src/core/server/models/tenant.ts +++ b/src/core/server/models/tenant.ts @@ -71,7 +71,9 @@ export async function createTenant(db: Db, input: CreateTenantInput) { closedTimeout: 60 * 60 * 24 * 7 * 2, disableCommenting: false, editCommentWindowLength: 30 * 1000, - charCountEnable: false, + charCount: { + enabled: false, + }, wordlist: { suspect: [], banned: [], diff --git a/src/core/server/services/comments/index.ts b/src/core/server/services/comments/index.ts index 25a264f5a..4d9151818 100644 --- a/src/core/server/services/comments/index.ts +++ b/src/core/server/services/comments/index.ts @@ -7,6 +7,7 @@ import { CreateCommentInput, editComment, EditCommentInput, + pushChildCommentIDOntoParent, retrieveComment, } from "talk-server/models/comment"; import { Tenant } from "talk-server/models/tenant"; @@ -16,7 +17,7 @@ import { Request } from "talk-server/types/express"; export type CreateComment = Omit< CreateCommentInput, - "status" | "action_counts" | "metadata" + "status" | "action_counts" | "metadata" | "grandparent_ids" >; export async function create( @@ -35,6 +36,7 @@ export async function create( // TODO: (wyattjoh) Check that the asset was visible. + const grandparentIDs: string[] = []; if (input.parent_id) { // Check to see that the reference parent ID exists. const parent = await retrieveComment(mongo, tenant.id, input.parent_id); @@ -44,6 +46,13 @@ export async function create( } // TODO: (wyattjoh) Check that the parent comment was visible. + + // Push the parent's parent id's into the comment's grandparent id's. + grandparentIDs.push(...parent.grandparent_ids); + if (parent.parent_id) { + // If this parent has a parent, push it down as well. + grandparentIDs.push(parent.parent_id); + } } // Run the comment through the moderation phases. @@ -61,11 +70,18 @@ export async function create( ...input, status, action_counts: {}, + grandparent_ids: grandparentIDs, metadata, }); if (input.parent_id) { - // TODO: update reply count of parent. + // Push the child's ID onto the parent. + await pushChildCommentIDOntoParent( + mongo, + tenant.id, + input.parent_id, + comment.id + ); } return comment; diff --git a/src/core/server/services/comments/moderation/phases/commentLength.ts b/src/core/server/services/comments/moderation/phases/commentLength.ts index cb51f1c56..c48db49fd 100644 --- a/src/core/server/services/comments/moderation/phases/commentLength.ts +++ b/src/core/server/services/comments/moderation/phases/commentLength.ts @@ -1,3 +1,6 @@ +import striptags from "striptags"; + +import { isNil } from "lodash"; import { GQLACTION_GROUP, GQLACTION_TYPE, @@ -9,28 +12,40 @@ import { IntermediatePhaseResult, } from "talk-server/services/comments/moderation"; -const testCharCount = (settings: Partial, length: number) => - settings.charCountEnable && settings.charCount && length > settings.charCount; +const testCharCount = ( + settings: Partial, + length: number +) => { + // settings.charCount.enable && settings.charCount && length > settings.charCount; + + if (settings.charCount && settings.charCount.enabled) { + if (!isNil(settings.charCount.min)) { + if (length < settings.charCount.min) { + return true; + } + } + if (!isNil(settings.charCount.max)) { + if (length > settings.charCount.max) { + return true; + } + } + } + + return false; +}; export const commentLength: IntermediateModerationPhase = ({ asset, tenant, comment, }): IntermediatePhaseResult | void => { - const length = comment.body ? comment.body.length : 0; + const length = comment.body ? striptags(comment.body).length : 0; - // Check to see if the body is too short, if it is, then complain about it! - if (length < 2) { - // TODO: (wyattjoh) return better error. - throw new Error("comment body too short"); - } - - // Reject if the comment is too long + // Reject if the comment is too long or too short. if ( testCharCount(tenant, length) || (asset.settings && testCharCount(asset.settings, length)) ) { - // Add the flag related to Trust to the comment. return { status: GQLCOMMENT_STATUS.REJECTED, actions: [ diff --git a/src/types/graphql-fields.d.ts b/src/types/graphql-fields.d.ts new file mode 100644 index 000000000..c5a9ee9f0 --- /dev/null +++ b/src/types/graphql-fields.d.ts @@ -0,0 +1,7 @@ +declare module "graphql-fields" { + import { GraphQLResolveInfo } from "graphql"; + + export default function graphqlFields( + info: GraphQLResolveInfo + ): { [P in keyof T]: any }; +}