diff --git a/apps/web/components/webhook/WebhookDialogForm.tsx b/apps/web/components/webhook/WebhookDialogForm.tsx index 6773e9b649..e1988a5c0a 100644 --- a/apps/web/components/webhook/WebhookDialogForm.tsx +++ b/apps/web/components/webhook/WebhookDialogForm.tsx @@ -1,3 +1,4 @@ +import { Webhook } from "@prisma/client"; import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; @@ -20,10 +21,12 @@ export default function WebhookDialogForm(props: { defaultValues?: TWebhook; app?: string; handleClose: () => void; + webhooks: Webhook[]; }) { const { t } = useLocale(); const utils = trpc.useContext(); const appId = props.app; + const webhooks = props.webhooks; const triggers = !appId ? WEBHOOK_TRIGGER_EVENTS_GROUPED_BY_APP["core"] @@ -45,6 +48,10 @@ export default function WebhookDialogForm(props: { const hasSecretKey = !!defaultValues.secret; const currentSecret = defaultValues.secret; + const subscriberUrlReserved = (subscriberUrl: string, id: string): boolean => { + return !!webhooks.find((webhook) => webhook.subscriberUrl === subscriberUrl && webhook.id !== id); + }; + const form = useForm({ defaultValues, }); @@ -64,6 +71,10 @@ export default function WebhookDialogForm(props: { data-testid="WebhookDialogForm" form={form} handleSubmit={async (event) => { + if (subscriberUrlReserved(event.subscriberUrl, event.id)) { + showToast(t("webhook_subscriber_url_reserved"), "error"); + return; + } const e = changeSecret ? { ...event, eventTypeId: props.eventTypeId, appId } : { ...event, secret: currentSecret, eventTypeId: props.eventTypeId, appId }; diff --git a/apps/web/components/webhook/WebhookListContainer.tsx b/apps/web/components/webhook/WebhookListContainer.tsx index 255f122513..74f99e8b35 100644 --- a/apps/web/components/webhook/WebhookListContainer.tsx +++ b/apps/web/components/webhook/WebhookListContainer.tsx @@ -74,6 +74,7 @@ export default function WebhookListContainer(props: WebhookListContainerType) { setNewWebhookModal(false)} /> @@ -86,6 +87,7 @@ export default function WebhookListContainer(props: WebhookListContainerType) { setEditModalOpen(false)} defaultValues={editing} diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 8aa60ce502..9348881c68 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1040,6 +1040,7 @@ "company_size": "Company size", "what_help_needed": "What do you need help with?", "variable_format": "Variable format", + "webhook_subscriber_url_reserved": "Webhook subscriber url is already defined", "custom_input_as_variable_info": "Ignore all special characters of the additional input label (use only letters and numbers), use uppercase for all letters and replace whitespaces with underscores.", "using_additional_inputs_as_variables": "How to use additional inputs as variables?", "download_desktop_app": "Download desktop app", diff --git a/packages/prisma/migrations/20220811132430_add_unique_index_to_webhook/migration.sql b/packages/prisma/migrations/20220811132430_add_unique_index_to_webhook/migration.sql new file mode 100644 index 0000000000..08bf48cc20 --- /dev/null +++ b/packages/prisma/migrations/20220811132430_add_unique_index_to_webhook/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[userId,subscriberUrl]` on the table `Webhook` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Webhook_userId_subscriberUrl_key" ON "Webhook"("userId", "subscriberUrl"); diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 97e5f7e888..b4f6840fc5 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -407,6 +407,8 @@ model Webhook { app App? @relation(fields: [appId], references: [slug], onDelete: Cascade) appId String? secret String? + + @@unique([userId, subscriberUrl], name: "courseIdentifier") } model Impersonations {