Files
talk/src/core/server/graph/resolvers/Comment.ts
T
Wyatt Johnson ed92f4916d [CORL-810] Custom Moderation Phases (#2901)
* feat: initial implementation

* feat: renamed fields from mutations

* fix: more renaming to streamline {Key,Secret}->SigningSecret

* feat: introduced WrappedInternalError

* feat: enhanced extern payload, more fetch options

- Added tenant.{id,domain} to extern payload
- Added site.id to the extern payload
- Added response size limit to fetch
- Added new SCRAPE_MAX_RESPONSE_SIZE env var for managing the size of
  responses for scraping

* fix: fixed bug with scrape invocation

* feat: added more queries + mutations

- Added Query.externalModerationPhase
- Added Mutation.createExternalModerationPhase
- Added Mutation.updateExternalModerationPhase
- Added Mutation.enableExternalModerationPhase
- Added Mutation.disableExternalModerationPhase
- Added Mutation.deleteExternalModerationPhase
- Added Mutation.rotateExternalModerationPhaseSigningSecret

* feat: added secret management

* fix: linting

* fix: merge conflict fix

* feat: added UI

* fix: linting

* fix: linting

* fix: updated snapshots

* fix: improved docs

* fix: improved docs

* fix: added locales

* review: improve naming

* review: some review changes

- Switched /moderation/phase to /moderation/phases
- Fixed scrolling
- Fixed redirection

* fix: added scroll timeout for webhooks
2020-05-14 19:20:35 +00:00

125 lines
4.6 KiB
TypeScript

import { GraphQLResolveInfo } from "graphql";
import { defaultTo } from "lodash";
import { StoryNotFoundError } from "coral-server/errors";
import { getRequestedFields } from "coral-server/graph/resolvers/util";
import {
ACTION_TYPE,
decodeActionCounts,
} from "coral-server/models/action/comment";
import * as comment from "coral-server/models/comment";
import {
getDepth,
getLatestRevision,
hasAncestors,
hasPublishedStatus,
} from "coral-server/models/comment/helpers";
import { createConnection } from "coral-server/models/helpers";
import { getURLWithCommentID } from "coral-server/models/story";
import { getCommentEditableUntilDate } from "coral-server/services/comments";
import {
GQLComment,
GQLCommentTypeResolver,
} from "coral-server/graph/schema/__generated__/types";
import GraphContext from "../context";
export const maybeLoadOnlyID = (
ctx: GraphContext,
info: GraphQLResolveInfo,
id: string
) => {
// 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<GQLComment>(info);
if (fields.length === 1 && fields[0] === "id") {
return {
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.visible.load(id);
};
export const Comment: GQLCommentTypeResolver<comment.Comment> = {
body: (c) => (c.revisions.length > 0 ? getLatestRevision(c).body : null),
// Send the whole comment back when you request revisions. This way, we get to
// know the comment ID. The field mapping is handled by the CommentRevision
// resolver.
revision: (c) =>
c.revisions.length > 0
? { revision: getLatestRevision(c), comment: c }
: null,
deleted: ({ deletedAt }) => !!deletedAt,
revisionHistory: (c) =>
c.revisions.map((revision) => ({ revision, comment: c })),
editing: ({ revisions, createdAt }, input, ctx) => ({
// When there is more than one body history, then the comment has been
// edited.
edited: revisions.length > 1,
// The date that the comment is editable until is the tenant's edit window
// length added to the comment created date.
editableUntil: getCommentEditableUntilDate(ctx.tenant, createdAt),
}),
author: (c, input, ctx) =>
c.authorID ? ctx.loaders.Users.user.load(c.authorID) : null,
statusHistory: ({ id }, input, ctx) =>
ctx.loaders.CommentModerationActions.forComment(input, id),
replies: (c, input, ctx) =>
// If there is at least one reply, then use the connection loader, otherwise
// return a blank connection.
c.childCount > 0
? ctx.loaders.Comments.forParent(c.storyID, c.id, input)
: createConnection(),
replyCount: async ({ childIDs }, input, ctx) => {
// TODO: (wyattjoh) the childCount should be used eventually, but it should be managed with the status so it's only a count of published comments
if (childIDs.length === 0) {
return 0;
}
const children = await ctx.loaders.Comments.visible.loadMany(childIDs);
return children.reduce(
(sum: any, c: any) => (c && hasPublishedStatus(c) ? sum + 1 : sum),
0
);
},
// Action Counts are encoded, decode them for use with the GraphQL system.
actionCounts: (c) => decodeActionCounts(c.actionCounts),
flags: ({ id }, { first, after }, ctx) =>
ctx.loaders.CommentActions.connection({
first: defaultTo(first, 10),
after,
filter: {
actionType: ACTION_TYPE.FLAG,
commentID: id,
},
}),
viewerActionPresence: (c, input, ctx) =>
ctx.user ? ctx.loaders.Comments.retrieveMyActionPresence.load(c.id) : null,
parentCount: (c) => getDepth(c),
depth: (c) => getDepth(c),
rootParent: (c, input, ctx, info) =>
hasAncestors(c)
? maybeLoadOnlyID(ctx, info, c.ancestorIDs[c.ancestorIDs.length - 1])
: null,
parent: (c, input, ctx, info) =>
hasAncestors(c) ? maybeLoadOnlyID(ctx, info, c.parentID) : null,
parents: (c, input, ctx) =>
// Some resolver optimization.
hasAncestors(c)
? ctx.loaders.Comments.parents(c, input)
: createConnection(),
story: (c, input, ctx) => ctx.loaders.Stories.story.load(c.storyID),
site: (c, input, ctx) => ctx.loaders.Sites.site.load(c.siteID),
permalink: async ({ id, storyID }, input, ctx) => {
const story = await ctx.loaders.Stories.story.load(storyID);
if (!story) {
throw new StoryNotFoundError(storyID);
}
return getURLWithCommentID(story.url, id);
},
};