diff --git a/packages/api/src/resolvers/webhooks/index.ts b/packages/api/src/resolvers/webhooks/index.ts
index 718c7f6fc..5ec23d9fe 100644
--- a/packages/api/src/resolvers/webhooks/index.ts
+++ b/packages/api/src/resolvers/webhooks/index.ts
@@ -194,20 +194,7 @@ export const setWebhookResolver = authorized<
}
webhookToSave.id = input.id
- } else {
- // Create
- const existingWebhook = await getRepository(Webhook).findOneBy({
- user: { id: uid },
- eventTypes: `{${input.eventTypes.join(',')}}`,
- })
-
- if (existingWebhook) {
- return {
- errorCodes: [SetWebhookErrorCode.AlreadyExists],
- }
- }
}
-
const webhook = await getRepository(Webhook).save({
user,
...webhookToSave,
diff --git a/packages/db/migrations/0111.do.remove_unique_key_on_webhooks.sql b/packages/db/migrations/0111.do.remove_unique_key_on_webhooks.sql
new file mode 100755
index 000000000..6142b79fe
--- /dev/null
+++ b/packages/db/migrations/0111.do.remove_unique_key_on_webhooks.sql
@@ -0,0 +1,9 @@
+-- Type: DO
+-- Name: remove_unique_key_on_webhooks
+-- Description: Remove unique constraint of user_id and event_types on webhooks table
+
+BEGIN;
+
+ALTER TABLE omnivore.webhooks DROP CONSTRAINT webhooks_user_id_event_types_key;
+
+COMMIT;
diff --git a/packages/db/migrations/0111.undo.remove_unique_key_on_webhooks.sql b/packages/db/migrations/0111.undo.remove_unique_key_on_webhooks.sql
new file mode 100755
index 000000000..8a0bd7c21
--- /dev/null
+++ b/packages/db/migrations/0111.undo.remove_unique_key_on_webhooks.sql
@@ -0,0 +1,9 @@
+-- Type: UNDO
+-- Name: remove_unique_key_on_webhooks
+-- Description: Remove unique constraint of user_id and event_types on webhooks table
+
+BEGIN;
+
+ALTER TABLE omnivore.webhooks ADD CONSTRAINT webhooks_user_id_event_types_key UNIQUE (user_id, event_types);
+
+COMMIT;
diff --git a/packages/web/components/elements/FormElements.tsx b/packages/web/components/elements/FormElements.tsx
index cdb98a1ca..088588bda 100644
--- a/packages/web/components/elements/FormElements.tsx
+++ b/packages/web/components/elements/FormElements.tsx
@@ -4,6 +4,11 @@ import Checkbox from './Checkbox'
import { HStack, VStack } from './LayoutPrimitives'
import { Label } from '@radix-ui/react-dropdown-menu'
+interface FormInputPropsOption {
+ label: string
+ value: string
+}
+
export interface FormInputProps {
name: string
label: string
@@ -15,7 +20,7 @@ export interface FormInputProps {
hidden?: boolean
required?: boolean
css?: any
- options?: string[]
+ options?: string[] | FormInputPropsOption[]
min?: any
}
@@ -69,23 +74,49 @@ export function GeneralFormInput(props: FormInputProps): JSX.Element {
return (
- {input.options?.map((label, index) => (
-
- {
- input.value[index] = arg
- setInput(input)
- props.onChange &&
- props.onChange(
- input.options?.filter((_, i) => input.value[i])
- )
- }}
- >
- {label}
-
- ))}
+ {input.options?.map((option, index) => {
+ if (typeof option === 'string') {
+ return (
+
+ {
+ input.value[index] = arg
+ setInput(input)
+ props.onChange &&
+ props.onChange(
+ (input.options as string[]).filter(
+ (_, i) => input.value[i]
+ )
+ )
+ }}
+ >
+ {option}
+
+ )
+ } else {
+ return (
+
+ {
+ input.value[index] = arg
+ setInput(input)
+ props.onChange &&
+ props.onChange(
+ (input.options as FormInputPropsOption[])
+ .filter((_, i) => input.value[i])
+ .map((option) => option.value)
+ )
+ }}
+ >
+ {option.label}
+
+ )
+ }
+ })}
)
} else if (props.type === 'select') {
@@ -100,11 +131,21 @@ export function GeneralFormInput(props: FormInputProps): JSX.Element {
minWidth: '196px',
}}
>
- {input.options?.map((label, index) => (
-
- ))}
+ {input.options?.map((option, index) => {
+ if (typeof option === 'string') {
+ return (
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ })}
)
} else {
diff --git a/packages/web/pages/settings/webhooks.tsx b/packages/web/pages/settings/webhooks.tsx
index 99d7ac7ba..777066ed5 100644
--- a/packages/web/pages/settings/webhooks.tsx
+++ b/packages/web/pages/settings/webhooks.tsx
@@ -25,16 +25,21 @@ interface Webhook {
updatedAt?: Date
}
+interface EventTypeOption {
+ label: string
+ value: WebhookEvent
+}
+
export default function Webhooks(): JSX.Element {
const { webhooks, revalidate } = useGetWebhooksQuery()
const [onDeleteId, setOnDeleteId] = useState(null)
const [addModelOpen, setAddModelOpen] = useState(false)
const [onEditWebhook, setOnEditWebhook] = useState(null)
const [url, setUrl] = useState('')
- const eventTypeOptions = [
- 'PAGE_CREATED',
- 'HIGHLIGHT_CREATED',
- 'LABEL_CREATED',
+ const eventTypeOptions: EventTypeOption[] = [
+ { label: 'PAGE_CREATED', value: 'PAGE_CREATED' },
+ { label: 'HIGHLIGHT_CREATED', value: 'HIGHLIGHT_CREATED' },
+ { label: 'LABEL_ADDED', value: 'LABEL_CREATED' },
]
const [eventTypes, setEventTypes] = useState([])
const [contentType, setContentType] = useState('application/json')
@@ -57,6 +62,14 @@ export default function Webhooks(): JSX.Element {
applyStoredTheme(false)
+ function validateEventTypes(eventTypes: WebhookEvent[]): boolean {
+ if (eventTypes.length > 0) return true
+ showErrorToast('Please select at least one event type', {
+ position: 'bottom-right',
+ })
+ return false
+ }
+
async function onDelete(id: string): Promise {
const result = await deleteWebhookMutation(id)
if (result) {
@@ -68,6 +81,7 @@ export default function Webhooks(): JSX.Element {
}
async function onCreate(): Promise {
+ if (!validateEventTypes(eventTypes)) return
const result = await setWebhookMutation({ url, eventTypes })
if (result) {
showSuccessToast('Webhook created', { position: 'bottom-right' })
@@ -78,6 +92,7 @@ export default function Webhooks(): JSX.Element {
}
async function onUpdate(): Promise {
+ if (!validateEventTypes(eventTypes)) return
const result = await setWebhookMutation({
id: onEditWebhook?.id,
url,
@@ -167,7 +182,7 @@ export default function Webhooks(): JSX.Element {
},
])
setUrl('')
- setEventTypes(eventTypeOptions as WebhookEvent[])
+ setEventTypes(['PAGE_CREATED', 'HIGHLIGHT_CREATED'])
setAddModelOpen(true)
}}
onEdit={(webhook) => {
@@ -183,7 +198,7 @@ export default function Webhooks(): JSX.Element {
label: 'Event Types',
name: 'eventTypes',
value: eventTypeOptions.map((option) =>
- webhook?.eventTypes.includes(option)
+ webhook?.eventTypes.includes(option.value)
),
onChange: setEventTypes,
options: eventTypeOptions,