From a75ce3c44d88204891dc1e724e840d679545d249 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 26 May 2022 13:33:45 +0800 Subject: [PATCH] Add get/list/delete webhook resolver --- .../api/src/resolvers/function_resolvers.ts | 12 +- packages/api/src/resolvers/index.ts | 1 + packages/api/src/resolvers/webhooks/index.ts | 150 +++++++++++++++++- packages/api/test/routers/webhooks.test.ts | 2 +- 4 files changed, 158 insertions(+), 7 deletions(-) diff --git a/packages/api/src/resolvers/function_resolvers.ts b/packages/api/src/resolvers/function_resolvers.ts index 7c50351c5..de13e589d 100644 --- a/packages/api/src/resolvers/function_resolvers.ts +++ b/packages/api/src/resolvers/function_resolvers.ts @@ -32,6 +32,7 @@ import { deleteLabelResolver, deleteNewsletterEmailResolver, deleteReminderResolver, + deleteWebhookResolver, getAllUsersResolver, getArticleResolver, getArticlesResolver, @@ -64,6 +65,7 @@ import { setShareArticleResolver, setShareHighlightResolver, setUserPersonalizationResolver, + setWebhookResolver, signupResolver, subscribeResolver, subscriptionsResolver, @@ -78,8 +80,9 @@ import { updateUserResolver, uploadFileRequestResolver, validateUsernameResolver, - updatePageResolver, addPopularReadResolver, + webhookResolver, + webhooksResolver, } from './index' import { getShareInfoForArticle } from '../datalayer/links/share_info' import { @@ -88,7 +91,6 @@ import { } from '../utils/uploads' import { getPageByParam } from '../elastic/pages' import { generateApiKeyResolver } from './api_key' -import { setWebhookResolver } from './webhooks' /* eslint-disable @typescript-eslint/naming-convention */ type ResultResolveType = { @@ -154,6 +156,7 @@ export const functionResolvers = { subscribe: subscribeResolver, addPopularRead: addPopularReadResolver, setWebhook: setWebhookResolver, + deleteWebhook: deleteWebhookResolver, }, Query: { me: getMeUserResolver, @@ -173,6 +176,8 @@ export const functionResolvers = { labels: labelsResolver, search: searchResolver, subscriptions: subscriptionsResolver, + webhooks: webhooksResolver, + webhook: webhookResolver, }, User: { async sharedArticles( @@ -567,4 +572,7 @@ export const functionResolvers = { ...resultResolveTypeResolver('Subscribe'), ...resultResolveTypeResolver('AddPopularRead'), ...resultResolveTypeResolver('SetWebhook'), + ...resultResolveTypeResolver('Webhooks'), + ...resultResolveTypeResolver('DeleteWebhook'), + ...resultResolveTypeResolver('Webhook'), } diff --git a/packages/api/src/resolvers/index.ts b/packages/api/src/resolvers/index.ts index fc0da5241..58434a7e4 100644 --- a/packages/api/src/resolvers/index.ts +++ b/packages/api/src/resolvers/index.ts @@ -17,3 +17,4 @@ export * from './labels' export * from './subscriptions' export * from './update' export * from './popular_reads' +export * from './webhooks' diff --git a/packages/api/src/resolvers/webhooks/index.ts b/packages/api/src/resolvers/webhooks/index.ts index d3ca69a6e..91817c69c 100644 --- a/packages/api/src/resolvers/webhooks/index.ts +++ b/packages/api/src/resolvers/webhooks/index.ts @@ -1,10 +1,22 @@ import { authorized } from '../../utils/helpers' import { + DeleteWebhookError, + DeleteWebhookErrorCode, + DeleteWebhookSuccess, + MutationDeleteWebhookArgs, MutationSetWebhookArgs, + QueryWebhookArgs, SetWebhookError, SetWebhookErrorCode, SetWebhookSuccess, + Webhook as WebhookResponse, + WebhookError, + WebhookErrorCode, WebhookEvent, + WebhooksError, + WebhooksErrorCode, + WebhooksSuccess, + WebhookSuccess, } from '../../generated/graphql' import { getRepository } from '../../entity/utils' import { User } from '../../entity/user' @@ -12,6 +24,134 @@ import { Webhook } from '../../entity/webhook' import { analytics } from '../../utils/analytics' import { env } from '../../env' +export const webhooksResolver = authorized( + async (_obj, _params, { claims: { uid }, log }) => { + log.info('webhooksResolver') + + try { + const user = await getRepository(User).findOneBy({ id: uid }) + if (!user) { + return { + errorCodes: [WebhooksErrorCode.Unauthorized], + } + } + + const webhooks = await getRepository(Webhook).findBy({ + user: { id: uid }, + }) + + return { + webhooks: webhooks.map((webhook) => webhookDataToResponse(webhook)), + } + } catch (error) { + log.error(error) + + return { + errorCodes: [WebhooksErrorCode.BadRequest], + } + } + } +) + +export const webhookResolver = authorized< + WebhookSuccess, + WebhookError, + QueryWebhookArgs +>(async (_, { id }, { claims: { uid }, log }) => { + log.info('webhookResolver') + + try { + const user = await getRepository(User).findOneBy({ id: uid }) + if (!user) { + return { + errorCodes: [WebhookErrorCode.Unauthorized], + } + } + + const webhook = await getRepository(Webhook).findOne({ + where: { id }, + relations: ['user'], + }) + + if (!webhook) { + return { + errorCodes: [WebhookErrorCode.NotFound], + } + } + + if (webhook.user.id !== uid) { + return { + errorCodes: [WebhookErrorCode.Unauthorized], + } + } + + return { + webhook: webhookDataToResponse(webhook), + } + } catch (error) { + log.error(error) + + return { + errorCodes: [WebhookErrorCode.BadRequest], + } + } +}) + +export const deleteWebhookResolver = authorized< + DeleteWebhookSuccess, + DeleteWebhookError, + MutationDeleteWebhookArgs +>(async (_, { id }, { claims: { uid }, log }) => { + log.info('deleteWebhookResolver') + + try { + const user = await getRepository(User).findOneBy({ id: uid }) + if (!user) { + return { + errorCodes: [DeleteWebhookErrorCode.Unauthorized], + } + } + + const webhook = await getRepository(Webhook).findOne({ + where: { id }, + relations: ['user'], + }) + + if (!webhook) { + return { + errorCodes: [DeleteWebhookErrorCode.NotFound], + } + } + + if (webhook.user.id !== uid) { + return { + errorCodes: [DeleteWebhookErrorCode.Unauthorized], + } + } + + const deletedWebhook = await getRepository(Webhook).remove(webhook) + + analytics.track({ + userId: uid, + event: 'webhook_delete', + properties: { + webhookId: webhook.id, + env: env.server.apiEnv, + }, + }) + + return { + webhook: webhookDataToResponse(webhook), + } + } catch (error) { + log.error(error) + + return { + errorCodes: [DeleteWebhookErrorCode.BadRequest], + } + } +}) + export const setWebhookResolver = authorized< SetWebhookSuccess, SetWebhookError, @@ -82,10 +222,7 @@ export const setWebhookResolver = authorized< }) return { - webhook: { - ...webhook, - eventTypes: webhook.eventTypes as WebhookEvent[], - }, + webhook: webhookDataToResponse(webhook), } } catch (error) { log.error(error) @@ -95,3 +232,8 @@ export const setWebhookResolver = authorized< } } }) + +const webhookDataToResponse = (webhook: Webhook): WebhookResponse => ({ + ...webhook, + eventTypes: webhook.eventTypes as WebhookEvent[], +}) diff --git a/packages/api/test/routers/webhooks.test.ts b/packages/api/test/routers/webhooks.test.ts index a2e942e4b..1509a9420 100644 --- a/packages/api/test/routers/webhooks.test.ts +++ b/packages/api/test/routers/webhooks.test.ts @@ -47,7 +47,7 @@ describe('Webhooks Router', () => { .post('/svc/pubsub/webhooks/trigger/created?token=' + token) .send(data) .expect(200) - expect(res.body.data).to.eql('OK') + expect(res.text).to.eql('OK') }) }) })