diff --git a/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts b/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts index 5a5a87804..8534cb20f 100644 --- a/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts +++ b/src/core/client/stream/mutations/RemoveCommentReactionMutation.ts @@ -5,12 +5,17 @@ import { commitMutationPromiseNormalized, createMutationContainer, } from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; import { RemoveCommentReactionMutation as MutationTypes } from "talk-stream/__generated__/RemoveCommentReactionMutation.graphql"; -import { CreateCommentReactionInput } from "talk-stream/mutations/CreateCommentReactionMutation"; + +export type RemoveCommentReactionInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; const mutation = graphql` - mutation RemoveCommentReactionMutation($input: CreateCommentReactionInput!) { + mutation RemoveCommentReactionMutation($input: RemoveCommentReactionInput!) { removeCommentReaction(input: $input) { comment { ...ReactionButtonContainer_comment @@ -22,7 +27,7 @@ const mutation = graphql` let clientMutationId = 0; -function commit(environment: Environment, input: CreateCommentReactionInput) { +function commit(environment: Environment, input: RemoveCommentReactionInput) { const source = environment.getStore().getSource(); const currentCount = source.get( source.get(source.get(input.commentID)!.actionCounts.__ref)!.reaction.__ref @@ -60,5 +65,5 @@ export const withRemoveCommentReactionMutation = createMutationContainer( ); export type RemoveCommentReactionMutation = ( - input: CreateCommentReactionInput + input: RemoveCommentReactionInput ) => Promise; diff --git a/src/core/server/graph/tenant/mutators/Comment.ts b/src/core/server/graph/tenant/mutators/Comment.ts index 072f46631..c0a31a644 100644 --- a/src/core/server/graph/tenant/mutators/Comment.ts +++ b/src/core/server/graph/tenant/mutators/Comment.ts @@ -17,6 +17,13 @@ import { removeDontAgree, removeReaction, } from "talk-server/services/comments/actions"; +import { validateMaximumLength } from "./util"; + +/** + * MAX_ADDITIONAL_DETAILS_LENGTH defines the maximum length for the + * additionalDetails field. + */ +const MAX_ADDITIONAL_DETAILS_LENGTH = 500; export const Comment = (ctx: TenantContext) => ({ create: ({ @@ -58,10 +65,16 @@ export const Comment = (ctx: TenantContext) => ({ createDontAgree: ({ commentID, commentRevisionID, + additionalDetails, }: GQLCreateCommentDontAgreeInput) => createDontAgree(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, { commentID, commentRevisionID, + // TODO: (wyattjoh) move this validation to the schema when bug is fixed: https://github.com/apollographql/graphql-tools/issues/842 + additionalDetails: validateMaximumLength( + MAX_ADDITIONAL_DETAILS_LENGTH, + additionalDetails + ), }), removeDontAgree: ({ commentID }: GQLRemoveCommentDontAgreeInput) => removeDontAgree(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, { @@ -71,10 +84,16 @@ export const Comment = (ctx: TenantContext) => ({ commentID, commentRevisionID, reason, + additionalDetails, }: GQLCreateCommentFlagInput) => createFlag(ctx.mongo, ctx.redis, ctx.tenant, ctx.user!, { commentID, commentRevisionID, reason, + // TODO: (wyattjoh) move this validation to the schema when bug is fixed: https://github.com/apollographql/graphql-tools/issues/842 + additionalDetails: validateMaximumLength( + MAX_ADDITIONAL_DETAILS_LENGTH, + additionalDetails + ), }), }); diff --git a/src/core/server/graph/tenant/mutators/util.spec.ts b/src/core/server/graph/tenant/mutators/util.spec.ts new file mode 100644 index 000000000..883f3cf49 --- /dev/null +++ b/src/core/server/graph/tenant/mutators/util.spec.ts @@ -0,0 +1,13 @@ +import { validateMaximumLength } from "./util"; + +it("limits to maximum when string is provided", () => { + expect(() => validateMaximumLength(5, "123456")).toThrow(); +}); + +it("limits to maximum when string is not provided", () => { + expect(validateMaximumLength(5)).toEqual(undefined); +}); + +it("returns the string when string is provided and is within the length allowances", () => { + expect(validateMaximumLength(5, "1234")).toEqual("1234"); +}); diff --git a/src/core/server/graph/tenant/mutators/util.ts b/src/core/server/graph/tenant/mutators/util.ts new file mode 100644 index 000000000..5d96d38b2 --- /dev/null +++ b/src/core/server/graph/tenant/mutators/util.ts @@ -0,0 +1,14 @@ +/** + * validateMaximumLength will limit the value of an optional string to the + * specified amount, otherwise will throw an error. + * + * @param maxLength maximum length to limit a value to + * @param value the value that should be limited + */ +export function validateMaximumLength(maxLength: number, value?: string) { + if (value && value.length > maxLength) { + throw new Error(`Exceeded maximum length of ${maxLength} characters`); + } + + return value; +} diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index d39819904..d4bfb239b 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -46,6 +46,12 @@ enum COMMENT_FLAG_REPORTED_REASON { spam. """ COMMENT_REPORTED_SPAM + + """ + COMMENT_REPORTED_OTHER is used when a User reported a Comment that doesn't + fit into the other reported reasons. + """ + COMMENT_REPORTED_OTHER } """ @@ -65,12 +71,6 @@ enum COMMENT_FLAG_DETECTED_REASON { """ COMMENT_DETECTED_SPAM - """ - COMMENT_DETECTED_BODY_COUNT is used when the Comment was detected as exceeding - the body length by the system. - """ - COMMENT_DETECTED_BODY_COUNT - """ COMMENT_DETECTED_TRUST is used when the Comment being left was done by a User that has a low karma/trust score. @@ -103,9 +103,9 @@ and COMMENT_FLAG_DETECTED_REASON types. enum COMMENT_FLAG_REASON { COMMENT_REPORTED_OFFENSIVE COMMENT_REPORTED_SPAM + COMMENT_REPORTED_OTHER COMMENT_DETECTED_TOXIC COMMENT_DETECTED_SPAM - COMMENT_DETECTED_BODY_COUNT COMMENT_DETECTED_TRUST COMMENT_DETECTED_LINKS COMMENT_DETECTED_BANNED_WORD @@ -137,9 +137,9 @@ type DontAgreeActionCounts { type FlagReasonActionCounts { COMMENT_REPORTED_OFFENSIVE: Int! COMMENT_REPORTED_SPAM: Int! + COMMENT_REPORTED_OTHER: Int! COMMENT_DETECTED_TOXIC: Int! COMMENT_DETECTED_SPAM: Int! - COMMENT_DETECTED_BODY_COUNT: Int! COMMENT_DETECTED_TRUST: Int! COMMENT_DETECTED_LINKS: Int! COMMENT_DETECTED_BANNED_WORD: Int! @@ -2230,6 +2230,12 @@ input CreateCommentDontAgreeInput { """ commentRevisionID: ID! + """ + additionalDetails stores information from the User as to why the Flag was + created or is relevant. + """ + additionalDetails: String + """ clientMutationId is required for Relay support. """ @@ -2297,6 +2303,12 @@ input CreateCommentFlagInput { """ reason: COMMENT_FLAG_REPORTED_REASON! + """ + additionalDetails stores information from the User as to why the Flag was + created or is relevant. + """ + additionalDetails: String + """ clientMutationId is required for Relay support. """ @@ -2964,8 +2976,8 @@ type Mutation { User on a Comment if it exists. """ removeCommentReaction( - input: CreateCommentReactionInput! - ): CreateCommentReactionPayload @auth + input: RemoveCommentReactionInput! + ): RemoveCommentReactionPayload @auth """ createCommentDontAgree will create a DontAgree authored by the current logged in @@ -2980,8 +2992,8 @@ type Mutation { User on a Comment if it exists. """ removeCommentDontAgree( - input: CreateCommentDontAgreeInput! - ): CreateCommentDontAgreePayload @auth + input: RemoveCommentDontAgreeInput! + ): RemoveCommentDontAgreePayload @auth """ createCommentFlag will create a Flag authored by the current logged in User on diff --git a/src/core/server/models/action/__snapshots__/comment.spec.ts.snap b/src/core/server/models/action/__snapshots__/comment.spec.ts.snap index df5d294c3..980659774 100644 --- a/src/core/server/models/action/__snapshots__/comment.spec.ts.snap +++ b/src/core/server/models/action/__snapshots__/comment.spec.ts.snap @@ -5,7 +5,7 @@ Object { "DONT_AGREE": 1, "FLAG": 2, "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, - "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, + "FLAG__COMMENT_REPORTED_OTHER": 1, "REACTION": 3, } `; @@ -18,13 +18,13 @@ Object { "flag": Object { "reasons": Object { "COMMENT_DETECTED_BANNED_WORD": 1, - "COMMENT_DETECTED_BODY_COUNT": 1, "COMMENT_DETECTED_LINKS": 0, "COMMENT_DETECTED_SPAM": 0, "COMMENT_DETECTED_SUSPECT_WORD": 0, "COMMENT_DETECTED_TOXIC": 0, "COMMENT_DETECTED_TRUST": 0, "COMMENT_REPORTED_OFFENSIVE": 0, + "COMMENT_REPORTED_OTHER": 1, "COMMENT_REPORTED_SPAM": 0, }, "total": 2, @@ -40,6 +40,6 @@ Object { "DONT_AGREE": 1, "FLAG": 2, "FLAG__COMMENT_DETECTED_BANNED_WORD": 1, - "FLAG__COMMENT_DETECTED_BODY_COUNT": 1, + "FLAG__COMMENT_REPORTED_OTHER": 1, } `; diff --git a/src/core/server/models/action/comment.spec.ts b/src/core/server/models/action/comment.spec.ts index fbac9adb7..275da64c5 100644 --- a/src/core/server/models/action/comment.spec.ts +++ b/src/core/server/models/action/comment.spec.ts @@ -19,7 +19,7 @@ describe("#encodeActionCounts", () => { }, { actionType: ACTION_TYPE.FLAG, - reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER, }, ]; const actionCounts = encodeActionCounts(...(actions as CommentAction[])); @@ -41,7 +41,7 @@ describe("#decodeActionCounts", () => { }, { actionType: ACTION_TYPE.FLAG, - reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER, }, ]; @@ -76,7 +76,7 @@ describe("#validateAction", () => { }, { actionType: ACTION_TYPE.FLAG, - reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER, }, { actionType: ACTION_TYPE.FLAG, @@ -109,7 +109,7 @@ describe("#validateAction", () => { }, { actionType: ACTION_TYPE.DONT_AGREE, - reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, + reason: GQLCOMMENT_FLAG_REASON.COMMENT_REPORTED_OTHER, }, { actionType: ACTION_TYPE.FLAG, diff --git a/src/core/server/models/action/comment.ts b/src/core/server/models/action/comment.ts index 1f4c6d645..52c10b43e 100644 --- a/src/core/server/models/action/comment.ts +++ b/src/core/server/models/action/comment.ts @@ -75,6 +75,12 @@ export interface CommentAction extends TenantResource { */ reason?: FLAG_REASON; + /** + * additionalDetails stores information from the User as to why the Flag was + * created or is relevant. + */ + additionalDetails?: string; + /** * storyID represents the ID of the Story where the comment was left on. */ @@ -168,7 +174,7 @@ export async function createAction( tenantID: string, input: CreateActionInput ): Promise { - const { metadata, ...filter } = input; + const { metadata, additionalDetails, ...filter } = input; // Create a new ID for the action. const id = uuid.v4(); @@ -185,6 +191,7 @@ export async function createAction( const action: Readonly = { ...defaults, ...input, + additionalDetails, }; // Create the upsert/update operation. diff --git a/src/core/server/services/comments/actions.ts b/src/core/server/services/comments/actions.ts index d86c74b16..b2d07f0ca 100644 --- a/src/core/server/services/comments/actions.ts +++ b/src/core/server/services/comments/actions.ts @@ -226,7 +226,7 @@ export async function removeReaction( export type CreateCommentDontAgree = Pick< CreateActionInput, - "commentID" | "commentRevisionID" + "commentID" | "commentRevisionID" | "additionalDetails" >; export async function createDontAgree( @@ -240,6 +240,7 @@ export async function createDontAgree( actionType: ACTION_TYPE.DONT_AGREE, commentID: input.commentID, commentRevisionID: input.commentRevisionID, + additionalDetails: input.additionalDetails, userID: author.id, }); } @@ -262,7 +263,7 @@ export async function removeDontAgree( export type CreateCommentFlag = Pick< CreateActionInput, - "commentID" | "commentRevisionID" + "commentID" | "commentRevisionID" | "additionalDetails" > & { reason: GQLCOMMENT_FLAG_REPORTED_REASON; }; @@ -279,6 +280,7 @@ export async function createFlag( reason: input.reason, commentID: input.commentID, commentRevisionID: input.commentRevisionID, + additionalDetails: input.additionalDetails, userID: author.id, }); } diff --git a/src/core/server/services/comments/pipeline/phases/commentLength.ts b/src/core/server/services/comments/pipeline/phases/commentLength.ts index d1eb4a7bc..b20373e19 100644 --- a/src/core/server/services/comments/pipeline/phases/commentLength.ts +++ b/src/core/server/services/comments/pipeline/phases/commentLength.ts @@ -1,11 +1,6 @@ import striptags from "striptags"; import { isNil } from "lodash"; -import { - GQLCOMMENT_FLAG_REASON, - GQLCOMMENT_STATUS, -} from "talk-server/graph/tenant/schema/__generated__/types"; -import { ACTION_TYPE } from "talk-server/models/action/comment"; import { ModerationSettings } from "talk-server/models/settings"; import { IntermediateModerationPhase, @@ -16,22 +11,26 @@ 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; + throw new Error( + `Body did not meet minimum length requirement of ${ + settings.charCount.min + }` + ); } } if (!isNil(settings.charCount.max)) { if (length > settings.charCount.max) { - return true; + throw new Error( + `Body exceeded maximum length requirement of ${ + settings.charCount.max + }` + ); } } } - - return false; }; export const commentLength: IntermediateModerationPhase = ({ @@ -42,22 +41,8 @@ export const commentLength: IntermediateModerationPhase = ({ const length = comment.body ? striptags(comment.body).length : 0; // Reject if the comment is too long or too short. - if ( - testCharCount(tenant, length) || - (story.settings && testCharCount(story.settings, length)) - ) { - return { - status: GQLCOMMENT_STATUS.REJECTED, - actions: [ - { - userID: null, - actionType: ACTION_TYPE.FLAG, - reason: GQLCOMMENT_FLAG_REASON.COMMENT_DETECTED_BODY_COUNT, - metadata: { - count: length, - }, - }, - ], - }; + testCharCount(tenant, length); + if (story.settings) { + testCharCount(story.settings, length); } };