From 61199f80c62ba1a0e0d6f0883784c1e04c8f52fc Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Mon, 14 Aug 2023 21:16:11 -0600 Subject: [PATCH] Webhook maintanability update --- package.json | 2 +- pnpm-lock.yaml | 86 +++++++++++++------------- src/pages/api/webhooks/index.ts | 51 +++++++++++++++ src/pages/api/webhooks/onUserCreate.ts | 55 ---------------- src/pages/api/webhooks/onUserDelete.ts | 37 ----------- src/server/unkey.ts | 1 - src/server/webhookHelpers.ts | 52 ++++++++++++++++ src/utils/types.ts | 6 ++ 8 files changed, 153 insertions(+), 137 deletions(-) create mode 100644 src/pages/api/webhooks/index.ts delete mode 100644 src/pages/api/webhooks/onUserCreate.ts delete mode 100644 src/pages/api/webhooks/onUserDelete.ts create mode 100644 src/server/webhookHelpers.ts diff --git a/package.json b/package.json index 171d54b..7d2f8a5 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dotenv": "^16.3.1", "drizzle-orm": "^0.28.2", "json2csv": "6.0.0-alpha.2", - "next": "^13.4.15", + "next": "^13.4.13", "nextjs-cors": "^2.1.2", "postcss": "^8.4.27", "react": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b66a84..ade0add 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,7 +10,7 @@ dependencies: version: 2.1.1(react-dom@18.2.0)(react@18.2.0) '@clerk/nextjs': specifier: ^4.23.2 - version: 4.23.2(next@13.4.15)(react-dom@18.2.0)(react@18.2.0) + version: 4.23.2(next@13.4.13)(react-dom@18.2.0)(react@18.2.0) '@paralleldrive/cuid2': specifier: ^2.2.2 version: 2.2.2 @@ -28,7 +28,7 @@ dependencies: version: 10.37.1(@trpc/server@10.37.1) '@trpc/next': specifier: 10.37.1 - version: 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.15)(react-dom@18.2.0)(react@18.2.0) + version: 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.13)(react-dom@18.2.0)(react@18.2.0) '@trpc/react-query': specifier: 10.37.1 version: 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) @@ -60,11 +60,11 @@ dependencies: specifier: 6.0.0-alpha.2 version: 6.0.0-alpha.2 next: - specifier: ^13.4.15 - version: 13.4.15(react-dom@18.2.0)(react@18.2.0) + specifier: ^13.4.13 + version: 13.4.13(react-dom@18.2.0)(react@18.2.0) nextjs-cors: specifier: ^2.1.2 - version: 2.1.2(next@13.4.15) + version: 2.1.2(next@13.4.13) postcss: specifier: ^8.4.27 version: 8.4.27 @@ -231,7 +231,7 @@ packages: tslib: 2.4.1 dev: false - /@clerk/nextjs@4.23.2(next@13.4.15)(react-dom@18.2.0)(react@18.2.0): + /@clerk/nextjs@4.23.2(next@13.4.13)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-99bSVu9r1E9MxybO/6mmPAufSKq4KU7SFeMVkylX7UF8sy5t/LE9cLHyc+9jitcCGgZNai9Om4sj1WIgkNOP8w==} engines: {node: '>=14'} peerDependencies: @@ -243,7 +243,7 @@ packages: '@clerk/clerk-react': 4.23.2(react@18.2.0) '@clerk/clerk-sdk-node': 4.12.2 '@clerk/types': 3.49.0 - next: 13.4.15(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(react-dom@18.2.0)(react@18.2.0) path-to-regexp: 6.2.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -995,8 +995,8 @@ packages: read-yaml-file: 1.1.0 dev: false - /@next/env@13.4.15: - resolution: {integrity: sha512-GQXUy/y5NW4VbrKQMbLjsuTykJ7+Jtb9zcKH1WrmgI1zG7yCoZQaoI65YFNksEh+9EPSHX6Xr7U1/StIIAOXog==} + /@next/env@13.4.13: + resolution: {integrity: sha512-fwz2QgVg08v7ZL7KmbQBLF2PubR/6zQdKBgmHEl3BCyWTEDsAQEijjw2gbFhI1tcKfLdOOJUXntz5vZ4S0Polg==} dev: false /@next/eslint-plugin-next@13.4.15: @@ -1005,8 +1005,8 @@ packages: glob: 7.1.7 dev: true - /@next/swc-darwin-arm64@13.4.15: - resolution: {integrity: sha512-S4/5CwFxUtV+MnXXv5a+zGpx8Swm6WfzKYrcKbyYgl71Ek7bj0TrqZXASD2HeXIlHwr+0SJnnhSFcU09Y4tHjA==} + /@next/swc-darwin-arm64@13.4.13: + resolution: {integrity: sha512-ZptVhHjzUuivnXMNCJ6lER33HN7lC+rZ01z+PM10Ows21NHFYMvGhi5iXkGtBDk6VmtzsbqnAjnx4Oz5um0FjA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1014,8 +1014,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@13.4.15: - resolution: {integrity: sha512-6bAi8s3vVRMFPU5qr4hZsfsQ0tzLU/Ig3tHjpJyjIN9AV3PMbTnX716P7hj1QioLfzZxIcUVnDczW7L45xHVfQ==} + /@next/swc-darwin-x64@13.4.13: + resolution: {integrity: sha512-t9nTiWCLApw8W4G1kqJyYP7y6/7lyal3PftmRturIxAIBlZss9wrtVN8nci50StDHmIlIDxfguYIEGVr9DbFTg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1023,8 +1023,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@13.4.15: - resolution: {integrity: sha512-LpMjwqsQAj269h0PIqw8drRGGpyg6cRHi8JEAKtD/putcCalQWCplfUzjtML8jplwMN1lcdPDNtFIab53t8AzQ==} + /@next/swc-linux-arm64-gnu@13.4.13: + resolution: {integrity: sha512-xEHUqC8eqR5DHe8SOmMnDU1K3ggrJ28uIKltrQAwqFSSSmzjnN/XMocZkcVhuncuxYrpbri0iMQstRyRVdQVWg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1032,8 +1032,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@13.4.15: - resolution: {integrity: sha512-zpV8C34OtLhu+1oiXIdDC576s88/TYxN325kipuL64Zs/j2kSEinR28mcLCZQTYI2g4OwtE3XIERy4vFoY3WiA==} + /@next/swc-linux-arm64-musl@13.4.13: + resolution: {integrity: sha512-sNf3MnLAm8rquSSAoeD9nVcdaDeRYOeey4stOWOyWIgbBDtP+C93amSgH/LPTDoUV7gNiU6f+ghepTjTjRgIUQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1041,8 +1041,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@13.4.15: - resolution: {integrity: sha512-Xk7QEfbX3zotvLmCoLzeGefbdyPpclCR8WyWHQOMMwUpftfmAuEVyU29WegcdfNCqYwc2QDXseVVI7xW/VwHCA==} + /@next/swc-linux-x64-gnu@13.4.13: + resolution: {integrity: sha512-WhcRaJJSHyx9OWmKjjz+OWHumiPZWRqmM/09Bt7Up4UqUJFFhGExeztR4trtv3rflvULatu9IH/nTV8fUUgaMA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1050,8 +1050,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@13.4.15: - resolution: {integrity: sha512-SxXxWBIUICkbHPUthg+T/FuRgOp75wE0e6eiLGs4P9Miq/P3kSXHv6x5LRnDreGDvYnKOlpzXlsXTxcPTRWttg==} + /@next/swc-linux-x64-musl@13.4.13: + resolution: {integrity: sha512-+Y4LLhOWWZQIDKVwr2R17lq2KSN0F1c30QVgGIWfnjjHpH8nrIWHEndhqYU+iFuW8It78CiJjQKTw4f51HD7jA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1059,8 +1059,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@13.4.15: - resolution: {integrity: sha512-Pu8zaW59XKbycyW/vzKRQOcbaZA2n3BK1zAFxv+hAO2bQ29FSGrtRb2nfhMDLwN0ggWIHKVKb+h4WMFHZkS9Qw==} + /@next/swc-win32-arm64-msvc@13.4.13: + resolution: {integrity: sha512-rWurdOR20uxjfqd1X9vDAgv0Jb26KjyL8akF9CBeFqX8rVaBAnW/Wf6A2gYEwyYY4Bai3T7p1kro6DFrsvBAAw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1068,8 +1068,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@13.4.15: - resolution: {integrity: sha512-V3y1Sc0X31mwK4cx0JQBi0wSTbPpmp/qRXjS0Vz3+2oSrbV06Oz27c/r2tW7ph0qhVp8+Jt03ZLnSc7KjjECiA==} + /@next/swc-win32-ia32-msvc@13.4.13: + resolution: {integrity: sha512-E8bSPwRuY5ibJ3CzLQmJEt8qaWrPYuUTwnrwygPUEWoLzD5YRx9SD37oXRdU81TgGwDzCxpl7z5Nqlfk50xAog==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1077,8 +1077,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@13.4.15: - resolution: {integrity: sha512-mUGemqDIuD2PjnqEkqMpeI8cXOTVjedo9cqoaMkrOVcoK1IX/w7h4qYeniMUCImamsDAoLms86Fq9LzxT24StQ==} + /@next/swc-win32-x64-msvc@13.4.13: + resolution: {integrity: sha512-4KlyC6jWRubPnppgfYsNTPeWfGCxtWLh5vaOAW/kdzAk9widqho8Qb5S4K2vHmal1tsURi7Onk2MMCV1phvyqA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1514,7 +1514,7 @@ packages: '@trpc/server': 10.37.1 dev: false - /@trpc/next@10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.15)(react-dom@18.2.0)(react@18.2.0): + /@trpc/next@10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/react-query@10.37.1)(@trpc/server@10.37.1)(next@13.4.13)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-0KEgr09mBfao56lkj7ZBfVOY86d3+bDH1o0zJkDHSH60Dp/hIJ7wLCnZJIhePlZxEwknCQjVeLsTy4Pqlu8NyQ==} peerDependencies: '@tanstack/react-query': ^4.18.0 @@ -1529,7 +1529,7 @@ packages: '@trpc/client': 10.37.1(@trpc/server@10.37.1) '@trpc/react-query': 10.37.1(@tanstack/react-query@4.32.6)(@trpc/client@10.37.1)(@trpc/server@10.37.1)(react-dom@18.2.0)(react@18.2.0) '@trpc/server': 10.37.1 - next: 13.4.15(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 react-dom: 18.2.0(react@18.2.0) react-ssr-prepass: 1.5.0(react@18.2.0) @@ -4470,8 +4470,8 @@ packages: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: true - /next@13.4.15(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-s4ZSBwZrvpY0IDzRtD7C5CY8FA/8ZIYqrJZHnwrf6mkUVA+Y+A6CtwBYyxml6VKgP/3DFNqCqkBQGInZuPHzmQ==} + /next@13.4.13(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-A3YVbVDNeXLhWsZ8Nf6IkxmNlmTNz0yVg186NJ97tGZqPDdPzTrHotJ+A1cuJm2XfuWPrKOUZILl5iBQkIf8Jw==} engines: {node: '>=16.8.0'} hasBin: true peerDependencies: @@ -4485,7 +4485,7 @@ packages: sass: optional: true dependencies: - '@next/env': 13.4.15 + '@next/env': 13.4.13 '@swc/helpers': 0.5.1 busboy: 1.6.0 caniuse-lite: 1.0.30001520 @@ -4496,27 +4496,27 @@ packages: watchpack: 2.4.0 zod: 3.21.4 optionalDependencies: - '@next/swc-darwin-arm64': 13.4.15 - '@next/swc-darwin-x64': 13.4.15 - '@next/swc-linux-arm64-gnu': 13.4.15 - '@next/swc-linux-arm64-musl': 13.4.15 - '@next/swc-linux-x64-gnu': 13.4.15 - '@next/swc-linux-x64-musl': 13.4.15 - '@next/swc-win32-arm64-msvc': 13.4.15 - '@next/swc-win32-ia32-msvc': 13.4.15 - '@next/swc-win32-x64-msvc': 13.4.15 + '@next/swc-darwin-arm64': 13.4.13 + '@next/swc-darwin-x64': 13.4.13 + '@next/swc-linux-arm64-gnu': 13.4.13 + '@next/swc-linux-arm64-musl': 13.4.13 + '@next/swc-linux-x64-gnu': 13.4.13 + '@next/swc-linux-x64-musl': 13.4.13 + '@next/swc-win32-arm64-msvc': 13.4.13 + '@next/swc-win32-ia32-msvc': 13.4.13 + '@next/swc-win32-x64-msvc': 13.4.13 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros dev: false - /nextjs-cors@2.1.2(next@13.4.15): + /nextjs-cors@2.1.2(next@13.4.13): resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==} peerDependencies: next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 dependencies: cors: 2.8.5 - next: 13.4.15(react-dom@18.2.0)(react@18.2.0) + next: 13.4.13(react-dom@18.2.0)(react@18.2.0) dev: false /no-case@3.0.4: diff --git a/src/pages/api/webhooks/index.ts b/src/pages/api/webhooks/index.ts new file mode 100644 index 0000000..34f469d --- /dev/null +++ b/src/pages/api/webhooks/index.ts @@ -0,0 +1,51 @@ +import type { NextApiRequest, NextApiResponse } from "next"; +import { validateRequest } from "~/server/unkey"; +import { + onUserCreatedHandler, + onUserDeletedHandler, +} from "~/server/webhookHelpers"; +import { WebhookEvent, WebhookEvents } from "~/utils/types"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const isValid = await validateRequest(req, res); + + if (!isValid) { + return; + } + + const requestBody = req.body as { + data: { + id: string; + email_addresses: + | [ + { + email_address: string; + id: string; + verification: { + status: string; + strategy: string; + }; + } + ] + | null; + }; + type: WebhookEvent; + }; + + switch (requestBody.type) { + case WebhookEvents.USER_CREATED: + onUserCreatedHandler(requestBody.data.id, res); + break; + + case WebhookEvents.USER_DELETED: + onUserDeletedHandler(requestBody.data.id, res); + break; + + default: + res.status(400).json({ error: "INVALID WEBHOOK EVENT" }); + break; + } +} diff --git a/src/pages/api/webhooks/onUserCreate.ts b/src/pages/api/webhooks/onUserCreate.ts deleted file mode 100644 index 3df9404..0000000 --- a/src/pages/api/webhooks/onUserCreate.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { env } from "~/env.mjs"; -import { validateRequest } from "~/server/unkey"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const success = await validateRequest(req, res); - - if (success) { - const requestBody = req.body as { - data: { - id: string; - email_addresses: [ - { - email_address: string; - id: string; - verification: { - status: string; - strategy: string; - }; - } - ]; - }; - object: string; - type: string; - }; - - const userUpdateResponse = await fetch( - `https://api.clerk.com/v1/users/${requestBody.data.id}/metadata`, - { - method: "PATCH", - headers: { - Authorization: `Bearer ${env.CLERK_SECRET_KEY}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - public_metadata: { - isVIP: false, - isAdmin: false, - }, - private_metadata: {}, - unsafe_metadata: {}, - }), - } - ); - - if (userUpdateResponse.status === 200) { - res.status(200).json({ result: "METADATA UPDATED" }); - } else { - res.status(500).json({ error: "ERROR UPDATING METADATA" }); - } - } -} diff --git a/src/pages/api/webhooks/onUserDelete.ts b/src/pages/api/webhooks/onUserDelete.ts deleted file mode 100644 index f549642..0000000 --- a/src/pages/api/webhooks/onUserDelete.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { eq } from "drizzle-orm"; -import type { NextApiRequest, NextApiResponse } from "next"; -import { db } from "~/server/db"; -import { logs, rooms, votes } from "~/server/schema"; -import { validateRequest } from "~/server/unkey"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - const success = await validateRequest(req, res); - - if (success) { - const requestBody = req.body as { - data: { - deleted: string; - id: string; - object: string; - }; - object: string; - type: string; - }; - - const deletedRoom = await db - .delete(rooms) - .where(eq(rooms.userId, requestBody.data.id)); - - if (deletedRoom.rowsAffected > 0) { - await db.delete(logs).where(eq(logs.userId, requestBody.data.id)); - await db.delete(votes).where(eq(votes.userId, requestBody.data.id)); - - res.status(200).json({ result: "USER DELETED" }); - } else { - res.status(500).json({ result: "ERROR DELETING USER" }); - } - } -} diff --git a/src/server/unkey.ts b/src/server/unkey.ts index de2d44b..20ff83b 100644 --- a/src/server/unkey.ts +++ b/src/server/unkey.ts @@ -28,7 +28,6 @@ export const validateRequest = async ( } } - // Error if the key is not valid if (!isValidKey) { res.status(403).json({ error: "UNAUTHORIZED" }); } diff --git a/src/server/webhookHelpers.ts b/src/server/webhookHelpers.ts new file mode 100644 index 0000000..edb5850 --- /dev/null +++ b/src/server/webhookHelpers.ts @@ -0,0 +1,52 @@ +import { eq } from "drizzle-orm"; +import { db } from "./db"; +import { logs, rooms, votes } from "./schema"; +import { env } from "~/env.mjs"; +import { NextApiResponse } from "next"; + +export const onUserDeletedHandler = async ( + userId: string, + res: NextApiResponse +) => { + const deletedRoom = await db.delete(rooms).where(eq(rooms.userId, userId)); + + if (deletedRoom.rowsAffected > 0) { + await db.delete(logs).where(eq(logs.userId, userId)); + await db.delete(votes).where(eq(votes.userId, userId)); + + // res.status(200).json({ result: "USER DELETED" }); + res.status(200).json({ result: "USER DELETED" }); + } else { + res.status(404).json({ error: "USER WITH THIS ID NOT FOUND" }); + } +}; + +export const onUserCreatedHandler = async ( + userId: string, + res: NextApiResponse +) => { + const userUpdateResponse = await fetch( + `https://api.clerk.com/v1/users/${userId}/metadata`, + { + method: "PATCH", + headers: { + Authorization: `Bearer ${env.CLERK_SECRET_KEY}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + public_metadata: { + isVIP: false, + isAdmin: false, + }, + private_metadata: {}, + unsafe_metadata: {}, + }), + } + ); + + if (userUpdateResponse.ok) { + res.status(200).json({ result: "USER CREATED" }); + } else { + res.status(404).json({ error: "USER WITH THIS ID NOT FOUND" }); + } +}; diff --git a/src/utils/types.ts b/src/utils/types.ts index f55e5e1..883414e 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -8,6 +8,12 @@ export const EventTypes = { } as const; export type EventType = BetterEnum; +export const WebhookEvents = { + USER_CREATED: "user.created", + USER_DELETED: "user.deleted", +} as const; +export type WebhookEvent = BetterEnum; + export interface PresenceItem { name: string; image: string;