import { zodResolver } from '@hookform/resolvers/zod' import { QUEUES_SCHEMA } from '@supabase/pg-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Form, FormControl, FormField, FormItem, Switch } from 'ui' import { Admonition } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { z } from 'zod' import { ConstrainedIntegrationTabScaffold } from '@/components/interfaces/Integrations/ConstrainedIntegrationTabScaffold' import { DocsButton } from '@/components/ui/DocsButton' import { FormHeader } from '@/components/ui/Forms/FormHeader' import { FormPanelContainer, FormPanelContent, FormPanelFooter, } from '@/components/ui/Forms/FormPanel' import { InlineLink } from '@/components/ui/InlineLink' import { useProjectPostgrestConfigQuery } from '@/data/config/project-postgrest-config-query' import { useProjectPostgrestConfigUpdateMutation } from '@/data/config/project-postgrest-config-update-mutation' import { useQueuesExposePostgrestStatusQuery } from '@/data/database-queues/database-queues-expose-postgrest-status-query' import { useQueuesQuery } from '@/data/database-queues/database-queues-query' import { useDatabaseQueueToggleExposeMutation } from '@/data/database-queues/database-queues-toggle-postgrest-mutation' import { useDatabaseQueuesVersionQuery } from '@/data/database-queues/database-queues-version-query' import { useTableUpdateMutation } from '@/data/tables/table-update-mutation' import { useTablesQuery } from '@/data/tables/tables-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' import { DOCS_URL, IS_PLATFORM } from '@/lib/constants' export const QueuesSettings = () => { const { data: project } = useSelectedProjectQuery() const { can: canUpdatePostgrestConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_postgrest' ) const [isToggling, setIsToggling] = useState(false) const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) const [isUpdatingRls, setIsUpdatingRls] = useState(false) const formSchema = z.object({ enable: z.boolean() }) const form = useForm>({ resolver: zodResolver(formSchema as any), mode: 'onChange', defaultValues: { enable: false }, }) const { formState } = form const { enable } = form.watch() const { data: queueTables } = useTablesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, schema: 'pgmq', }) const tablesWithoutRLS = queueTables?.filter((x) => x.name.startsWith('q_') && !x.rls_enabled) ?? [] // pgmq lowercases queue names when building q_/a_ relations, but pgmq.meta keeps // the original casing. Look up each relname in list_queues() so we render the // user-provided name rather than the lowercased relname slice. const { data: queues } = useQueuesQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const queueDisplayName = (relname: string) => { const stripped = relname.slice(2) return queues?.find((q) => q.queue_name.toLowerCase() === stripped)?.queue_name ?? stripped } const { data: config, error: configError } = useProjectPostgrestConfigQuery({ projectRef: project?.ref, }) const { data: isExposed, isSuccess, isPending: isLoading, } = useQueuesExposePostgrestStatusQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const schemas = config?.db_schema.replace(/ /g, '').split(',') ?? [] const { data: pgmqVersion } = useDatabaseQueuesVersionQuery({ projectRef: project?.ref, connectionString: project?.connectionString, }) const { mutateAsync: updateTable } = useTableUpdateMutation() const onPostgrestConfigUpdateSuccess = () => { if (enable) { toast.success('Queues can now be managed through client libraries or PostgREST endpoints!') } else { toast.success( 'Queues can no longer be managed through client libraries or PostgREST endpoints' ) } setIsToggling(false) form.reset({ enable }) } const { mutate: updatePostgrestConfig } = useProjectPostgrestConfigUpdateMutation({ onSuccess: onPostgrestConfigUpdateSuccess, onError: (error) => { setIsToggling(false) toast.error(`Failed to toggle queue exposure via PostgREST: ${error.message}`) }, }) const { mutate: toggleExposeQueuePostgrest } = useDatabaseQueueToggleExposeMutation({ onSuccess: (_, values) => { if (!IS_PLATFORM) return onPostgrestConfigUpdateSuccess() if (project && config) { if (values.enable) { const updatedSchemas = schemas.concat([QUEUES_SCHEMA]) updatePostgrestConfig({ projectRef: project?.ref, dbSchema: updatedSchemas.join(', '), maxRows: config.max_rows, dbExtraSearchPath: config.db_extra_search_path, dbPool: config.db_pool, }) } else { const updatedSchemas = schemas.filter((x) => x !== QUEUES_SCHEMA) updatePostgrestConfig({ projectRef: project?.ref, dbSchema: updatedSchemas.join(', '), maxRows: config.max_rows, dbExtraSearchPath: config.db_extra_search_path, dbPool: config.db_pool, }) } } }, onError: (error) => { setIsToggling(false) toast.error(`Failed to toggle queue exposure via PostgREST: ${error.message}`) }, }) const onToggleRLS = async () => { if (!project) return console.error('Project is required') setIsUpdatingRls(true) try { await Promise.all( tablesWithoutRLS.map((x) => updateTable({ projectRef: project?.ref, connectionString: project?.connectionString, id: x.id, name: x.name, schema: x.schema, payload: { id: x.id, rls_enabled: true }, }) ) ) toast.success( `Successfully enabled RLS on ${tablesWithoutRLS.length === 1 ? tablesWithoutRLS[0].name : `${tablesWithoutRLS.length} queue${tablesWithoutRLS.length > 1 ? 's' : ''}`} ` ) setRlsConfirmModalOpen(false) } catch (error: any) { setIsUpdatingRls(false) toast.error(`Failed to enable RLS on queues: ${error.message}`) } } const onSubmit = async (values: z.infer) => { if (!project) return console.error('Project is required') if (configError) { return toast.error( `Failed to toggle queue exposure via PostgREST: Unable to retrieve PostgREST configuration (${configError.message})` ) } if (!pgmqVersion) { return toast.error('Unable to retrieve PGMQ version. Please try again later.') } setIsToggling(true) toggleExposeQueuePostgrest({ projectRef: project.ref, connectionString: project.connectionString, enable: values.enable, pgmqVersion, }) } useEffect(() => { if (isSuccess) form.reset({ enable: isExposed }) }, [isSuccess]) return ( <>
(

When enabled, you will be able to use the following functions from the{' '} {QUEUES_SCHEMA} schema to manage your queues via any Briven client library or PostgREST endpoints:

send,{' '} send_batch,{' '} read,{' '} pop, archive, and{' '} delete

{!IS_PLATFORM ? (
When running Briven locally with the CLI or self-hosting using Docker Compose, you also need to update your configuration to expose the {QUEUES_SCHEMA}{' '} schema.
Learn more
) : null} } > 0 || !canUpdatePostgrestConfig } checked={field.value} onCheckedChange={(value) => field.onChange(value)} />
{tablesWithoutRLS.length > 0 && (

Please ensure that the following {tablesWithoutRLS.length} queue {tablesWithoutRLS.length > 1 ? 's' : ''} have RLS enabled in order to prevent anonymous access.

    {tablesWithoutRLS.map((x) => { return (
  • {queueDisplayName(x.name)}
  • ) })}
)} {formState.dirtyFields.enable && field.value === true && (

Queues will be exposed and managed through the{' '} {QUEUES_SCHEMA} schema

Database functions will be created in the{' '} {QUEUES_SCHEMA} schema upon enabling. Call these functions via any Briven client library or PostgREST endpoint to manage your queues. Permissions on individual queues can also be further managed through privileges and row level security (RLS).

)} {formState.dirtyFields.enable && field.value === false && (

The {QUEUES_SCHEMA} schema will be removed once disabled

Ensure that the database functions from the{' '} {QUEUES_SCHEMA} schema are not in use within your client applications before disabling.

)}
)} />
setRlsConfirmModalOpen(false)} onConfirm={() => onToggleRLS()} >

Are you sure you want to enable Row Level Security for the following queues:

    {tablesWithoutRLS.map((x) => { return (
  • {queueDisplayName(x.name)}
  • ) })}
) }