Skip to content

Commit

Permalink
Merge pull request #1494 from dubinc/wh-cache-update
Browse files Browse the repository at this point in the history
Webhook cache setup change
  • Loading branch information
steven-tey authored Dec 2, 2024
2 parents 8638e40 + 2220698 commit 81a5da6
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 30 deletions.
14 changes: 7 additions & 7 deletions apps/web/app/api/webhooks/[webhookId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ export const PATCH = withWorkspace(
},
});

const existingWebhook = await prisma.webhook.findUniqueOrThrow({
where: {
id: webhookId,
projectId: workspace.id,
},
});

const webhook = await prisma.webhook.update({
where: {
id: webhookId,
Expand Down Expand Up @@ -139,13 +146,6 @@ export const PATCH = withWorkspace(

waitUntil(
(async () => {
const existingWebhook = await prisma.webhook.findUniqueOrThrow({
where: {
id: webhookId,
projectId: workspace.id,
},
});

// If the webhook is being changed from link level to workspace level, delete the cache
if (
isLinkLevelWebhook(existingWebhook) &&
Expand Down
36 changes: 19 additions & 17 deletions apps/web/lib/webhook/cache.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
import { redis } from "@/lib/upstash";
import { WebhookCacheProps } from "../types";
import { WEBHOOK_REDIS_KEY } from "./constants";
import { isLinkLevelWebhook } from "./utils";

const WEBHOOK_CACHE_KEY_PREFIX = "webhook";

class WebhookCache {
async set(webhook: WebhookCacheProps) {
return await redis.hset(WEBHOOK_REDIS_KEY, {
[webhook.id]: this.format(webhook),
});
}
if (!isLinkLevelWebhook(webhook)) {
return;
}

async get(webhookId: string) {
return await redis.hget<WebhookCacheProps>(WEBHOOK_REDIS_KEY, webhookId);
return await redis.set(
this._createKey(webhook.id),
JSON.stringify(this._format(webhook)),
);
}

async mget(webhookIds: string[]) {
const webhooks = await redis.hmget<Record<string, WebhookCacheProps>>(
WEBHOOK_REDIS_KEY,
...webhookIds,
const webhooks = await redis.mget<WebhookCacheProps[]>(
webhookIds.map(this._createKey),
);

if (!webhooks) {
return [];
}

return Object.values(webhooks);
return webhooks.filter(Boolean);
}

async delete(webhookId: string) {
return await redis.hdel(WEBHOOK_REDIS_KEY, webhookId);
return await redis.del(this._createKey(webhookId));
}

format(webhook: WebhookCacheProps) {
_format(webhook: WebhookCacheProps) {
return {
id: webhook.id,
url: webhook.url,
Expand All @@ -39,6 +37,10 @@ class WebhookCache {
...(webhook.disabledAt && { disabledAt: webhook.disabledAt }),
};
}

_createKey(webhookId: string) {
return `${WEBHOOK_CACHE_KEY_PREFIX}:${webhookId}`;
}
}

export const webhookCache = new WebhookCache();
10 changes: 4 additions & 6 deletions apps/web/lib/webhook/failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,20 @@ export const handleWebhookFailure = async (webhookId: string) => {
webhook.consecutiveFailures >= WEBHOOK_FAILURE_NOTIFY_THRESHOLD;

if (failureThresholdReached) {
const disabledAt = new Date();

// Disable the webhook
await prisma.webhook.update({
const updatedWebhook = await prisma.webhook.update({
where: { id: webhookId },
data: {
disabledAt,
disabledAt: new Date(),
},
});

await Promise.allSettled([
// Notify the user
sendFailureNotification(webhook),
sendFailureNotification(updatedWebhook),

// Update the webhook cache
webhookCache.set({ ...webhook, disabledAt }),
webhookCache.set(updatedWebhook),

// Update the project webhookEnabled flag
updateWebhookStatusForWorkspace({
Expand Down
29 changes: 29 additions & 0 deletions apps/web/scripts/update-webhook-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { prisma } from "@/lib/prisma";
import { webhookCache } from "@/lib/webhook/cache";
import "dotenv-flow/config";

async function main() {
// Find link level webhooks
const linkLevelWebhooks = await prisma.webhook.findMany({
where: {
triggers: {
array_contains: ["link.clicked"],
},
},
select: {
id: true,
url: true,
secret: true,
triggers: true,
disabledAt: true,
},
});

const result = await Promise.all(
linkLevelWebhooks.map((webhook) => webhookCache.set(webhook)),
);

console.log(`Cache updated for ${result.length} webhooks`);
}

main();
2 changes: 2 additions & 0 deletions apps/web/ui/modals/delete-webhook-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
useState,
} from "react";
import { toast } from "sonner";
import { mutate } from "swr";

function DeleteWebhookModal({
showDeleteWebhookModal,
Expand Down Expand Up @@ -44,6 +45,7 @@ function DeleteWebhookModal({
}

setShowDeleteWebhookModal(false);
mutate(`/api/webhooks?workspaceId=${workspaceId}`);
router.push(`/${workspaceSlug}/settings/webhooks`);
};

Expand Down

0 comments on commit 81a5da6

Please sign in to comment.