[CORL-687] Webhooks (#2738)

* feat: initial webhook impl

* feat: added support for key rotation

* feat: harmonized fetcher

* feat: added expired secrets cleaning

* feat: event system refactor

* feat: added story event

* feat: simplfiied webhook handler

* feat: added ref's to locations where user events can be added

* feat: added UI to support webhooks

* fix: renaming some Webhook -> WebhookEndpoint

* fix: review comments to adjuist flow

* feat: added localizations

* fix: linting, updated snapshots

* fix: adapted for new fluent

* fix: rearranged folders

* fix: linting

* feat: added webhooks documentation

* feat: improved toc generation

* feat: added some tests to webhooks

* fix: chain transition hooks

* feat: added tests around webhook ui

* fix: renamed events

* fix: adjusted circle markdown linting

* fix: adjusted doctoc script call

* review: review fixes

* review: review comments

* review: adjusted signing secret confirmation

* review: adjusted styles to harmonize button usage

* fix: updated snapshots and tests

* review: move form out of webhooks

Moved the form out of the webhooks by relocating the layout used for the
route associated with the configure routes.

* fix: fixed bugs and snapshots with tests

* feat: revised slack message format to use block api

* fix: fixed a small text bug

Co-authored-by: Vinh <vinh@vinh.tech>
Co-authored-by: Kim Gardner <kgardnr@gmail.com>
This commit is contained in:
Wyatt Johnson
2020-02-18 13:25:48 -05:00
committed by GitHub
co-authored by Vinh Kim Gardner
parent 34ba2da88d
commit e42c2b925d
137 changed files with 5633 additions and 1020 deletions
@@ -86,7 +86,7 @@ export const storyModerationInputResolver = (
*
* @param source the source of the type, not used
* @param args the args of the type, not used
* @param ctx the TenantContext that will be used to get the shared counts
* @param ctx the GraphContext that will be used to get the shared counts
*/
export const sharedModerationInputResolver = async (
source: any,
@@ -106,7 +106,7 @@ export const sharedModerationInputResolver = async (
*
* @param source the source of the payload, not used
* @param args the args of the payload containing potentially a Story ID
* @param ctx the TenantContext for which we can use to retrieve the shared data
* @param ctx the GraphContext for which we can use to retrieve the shared data
*/
export const moderationQueuesResolver: QueryToModerationQueuesResolver = async (
source,
+54 -2
View File
@@ -33,9 +33,13 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
user: await ctx.mutators.Users.updateNotificationSettings(input),
clientMutationId,
}),
updateSettings: async (source, { input }, ctx) => ({
updateSettings: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
settings: await ctx.mutators.Settings.update(input),
clientMutationId: input.clientMutationId,
clientMutationId,
}),
createCommentReaction: async (source, { input }, ctx) => ({
comment: await ctx.mutators.Comments.createReaction(input),
@@ -252,4 +256,52 @@ export const Mutation: Required<GQLMutationTypeResolver<void>> = {
site: await ctx.mutators.Sites.update(input),
clientMutationId: input.clientMutationId,
}),
createWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
...(await ctx.mutators.Settings.createWebhookEndpoint(input)),
clientMutationId,
}),
updateWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
endpoint: await ctx.mutators.Settings.updateWebhookEndpoint(input),
clientMutationId,
}),
disableWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
endpoint: await ctx.mutators.Settings.disableWebhookEndpoint(input),
clientMutationId,
}),
enableWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
endpoint: await ctx.mutators.Settings.enableWebhookEndpoint(input),
clientMutationId,
}),
deleteWebhookEndpoint: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
endpoint: await ctx.mutators.Settings.deleteWebhookEndpoint(input),
clientMutationId,
}),
rotateWebhookEndpointSecret: async (
source,
{ input: { clientMutationId, ...input } },
ctx
) => ({
endpoint: await ctx.mutators.Settings.rotateWebhookEndpointSecret(input),
clientMutationId,
}),
};
+3
View File
@@ -1,3 +1,5 @@
import { getWebhookEndpoint } from "coral-server/models/tenant";
import { GQLQueryTypeResolver } from "coral-server/graph/schema/__generated__/types";
import { moderationQueuesResolver } from "./ModerationQueues";
@@ -25,4 +27,5 @@ export const Query: Required<GQLQueryTypeResolver<void>> = {
ctx.loaders.Stories.activeStories(limit),
sites: (source, args, ctx) => ctx.loaders.Sites.connection(args),
site: (source, { id }, ctx) => (id ? ctx.loaders.Sites.site.load(id) : null),
webhookEndpoint: (source, { id }, ctx) => getWebhookEndpoint(ctx.tenant, id),
};
@@ -2,7 +2,7 @@ import * as settings from "coral-server/models/settings";
import { GQLSSOAuthIntegrationTypeResolver } from "coral-server/graph/schema/__generated__/types";
function getActiveSSOKey(keys: settings.SSOKey[]) {
function getActiveSSOKey(keys: settings.Secret[]) {
// Any key that has been rotated cannot be the active key.
return keys.find(key => !key.rotatedAt);
}
@@ -6,6 +6,7 @@ import {
import {
GQLFEATURE_FLAG,
GQLSettingsTypeResolver,
GQLWEBHOOK_EVENT_NAME,
} from "coral-server/graph/schema/__generated__/types";
const filterValidFeatureFlags = () => {
@@ -27,4 +28,5 @@ export const Settings: GQLSettingsTypeResolver<Tenant> = {
const sites = await ctx.loaders.Sites.connection({});
return sites.edges.length > 1;
},
webhookEvents: () => Object.values(GQLWEBHOOK_EVENT_NAME),
};
@@ -17,3 +17,13 @@ export const Subscription: GQLSubscriptionTypeResolver = {
commentFeatured,
commentReleased,
};
export { CommentFeaturedInput } from "./commentFeatured";
export { CommentCreatedInput } from "./commentCreated";
export {
CommentEnteredModerationQueueInput,
} from "./commentEnteredModerationQueue";
export { CommentLeftModerationQueueInput } from "./commentLeftModerationQueue";
export { CommentReleasedInput } from "./commentReleased";
export { CommentReplyCreatedInput } from "./commentReplyCreated";
export { CommentStatusUpdatedInput } from "./commentStatusUpdated";
@@ -0,0 +1,10 @@
import * as tenant from "coral-server/models/tenant";
import { GQLWebhookEndpointTypeResolver } from "coral-server/graph/schema/__generated__/types";
export const WebhookEndpoint: GQLWebhookEndpointTypeResolver<
tenant.Endpoint
> = {
signingSecret: ({ signingSecrets }) =>
signingSecrets[signingSecrets.length - 1],
};
+2
View File
@@ -50,6 +50,7 @@ import { User } from "./User";
import { UsernameHistory } from "./UsernameHistory";
import { UsernameStatus } from "./UsernameStatus";
import { UserStatus } from "./UserStatus";
import { WebhookEndpoint } from "./WebhookEndpoint";
const Resolvers: GQLResolver = {
ApproveCommentPayload,
@@ -101,6 +102,7 @@ const Resolvers: GQLResolver = {
UserStatus,
Settings,
SlackConfiguration,
WebhookEndpoint,
};
export default Resolvers;