diff --git a/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts b/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts index 792d28633..8a896a1e6 100644 --- a/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts +++ b/apps/builder/src/features/blocks/integrations/openai/api/listModels.ts @@ -10,15 +10,6 @@ import { OpenAI, ClientOptions } from 'openai' import { defaultOpenAIOptions } from '@typebot.io/schemas/features/blocks/integrations/openai/constants' export const listModels = authenticatedProcedure - .meta({ - openapi: { - method: 'GET', - path: '/openai/models', - protect: true, - summary: 'List OpenAI models', - tags: ['OpenAI'], - }, - }) .input( z.object({ credentialsId: z.string(), @@ -28,11 +19,6 @@ export const listModels = authenticatedProcedure type: z.enum(['gpt', 'tts']), }) ) - .output( - z.object({ - models: z.array(z.string()), - }) - ) .query( async ({ input: { credentialsId, workspaceId, baseUrl, apiVersion, type }, diff --git a/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts b/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts index eaf5ec189..cabdc8dea 100644 --- a/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts +++ b/apps/builder/src/features/blocks/integrations/zemanticAi/api/listProjects.ts @@ -8,31 +8,12 @@ import { ZemanticAiCredentials } from '@typebot.io/schemas/features/blocks/integ import got from 'got' export const listProjects = authenticatedProcedure - .meta({ - openapi: { - method: 'GET', - path: '/zemantic-ai/projects', - protect: true, - summary: 'List Zemantic AI projects', - tags: ['ZemanticAi'], - }, - }) .input( z.object({ credentialsId: z.string(), workspaceId: z.string(), }) ) - .output( - z.object({ - projects: z.array( - z.object({ - label: z.string(), - value: z.string(), - }) - ), - }) - ) .query(async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => { const workspace = await prisma.workspace.findFirst({ where: { id: workspaceId }, diff --git a/apps/builder/src/features/forge/api/credentials/createCredentials.ts b/apps/builder/src/features/forge/api/credentials/createCredentials.ts index 5957144ce..3b86bd2de 100644 --- a/apps/builder/src/features/forge/api/credentials/createCredentials.ts +++ b/apps/builder/src/features/forge/api/credentials/createCredentials.ts @@ -14,13 +14,6 @@ const inputShape = { } as const export const createCredentials = authenticatedProcedure - .meta({ - openapi: { - method: 'POST', - path: '/credentials', - protect: true, - }, - }) .input( z.object({ credentials: z.discriminatedUnion( @@ -31,11 +24,6 @@ export const createCredentials = authenticatedProcedure ), }) ) - .output( - z.object({ - credentialsId: z.string(), - }) - ) .mutation(async ({ input: { credentials }, ctx: { user } }) => { const workspace = await prisma.workspace.findFirst({ where: { diff --git a/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts b/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts index 9929607ff..6c0531ec6 100644 --- a/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts +++ b/apps/builder/src/features/forge/api/credentials/deleteCredentials.ts @@ -11,11 +11,6 @@ export const deleteCredentials = authenticatedProcedure workspaceId: z.string(), }) ) - .output( - z.object({ - credentialsId: z.string(), - }) - ) .mutation( async ({ input: { credentialsId, workspaceId }, ctx: { user } }) => { const workspace = await prisma.workspace.findFirst({ diff --git a/apps/builder/src/features/forge/api/credentials/listCredentials.ts b/apps/builder/src/features/forge/api/credentials/listCredentials.ts index d588561a4..a89598bef 100644 --- a/apps/builder/src/features/forge/api/credentials/listCredentials.ts +++ b/apps/builder/src/features/forge/api/credentials/listCredentials.ts @@ -12,11 +12,6 @@ export const listCredentials = authenticatedProcedure type: z.enum(enabledBlocks), }) ) - .output( - z.object({ - credentials: z.array(z.object({ id: z.string(), name: z.string() })), - }) - ) .query(async ({ input: { workspaceId, type }, ctx: { user } }) => { const workspace = await prisma.workspace.findFirst({ where: { diff --git a/apps/builder/src/features/forge/api/credentials/router.ts b/apps/builder/src/features/forge/api/credentials/router.ts deleted file mode 100644 index f22addf17..000000000 --- a/apps/builder/src/features/forge/api/credentials/router.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { router } from '@/helpers/server/trpc' -import { createCredentials } from './createCredentials' -import { deleteCredentials } from './deleteCredentials' -import { listCredentials } from './listCredentials' - -export const forgedCredentialsRouter = router({ - createCredentials, - listCredentials, - deleteCredentials, -}) diff --git a/apps/builder/src/features/forge/api/fetchSelectItems.ts b/apps/builder/src/features/forge/api/fetchSelectItems.ts index 659f916e6..45f91b7ee 100644 --- a/apps/builder/src/features/forge/api/fetchSelectItems.ts +++ b/apps/builder/src/features/forge/api/fetchSelectItems.ts @@ -15,13 +15,6 @@ export const fetchSelectItems = authenticatedProcedure workspaceId: z.string(), }) ) - .output( - z.object({ - items: z.array( - z.string().or(z.object({ label: z.string(), value: z.string() })) - ), - }) - ) .query( async ({ input: { workspaceId, integrationId, fetcherId, options }, diff --git a/apps/builder/src/features/forge/api/router.ts b/apps/builder/src/features/forge/api/router.ts index e6f7637c6..1c2bd6c75 100644 --- a/apps/builder/src/features/forge/api/router.ts +++ b/apps/builder/src/features/forge/api/router.ts @@ -1,6 +1,12 @@ import { router } from '@/helpers/server/trpc' import { fetchSelectItems } from './fetchSelectItems' +import { createCredentials } from './credentials/createCredentials' +import { deleteCredentials } from './credentials/deleteCredentials' +import { listCredentials } from './credentials/listCredentials' -export const integrationsRouter = router({ +export const forgeRouter = router({ fetchSelectItems, + createCredentials, + listCredentials, + deleteCredentials, }) diff --git a/apps/builder/src/features/forge/components/ForgeSelectInput.tsx b/apps/builder/src/features/forge/components/ForgeSelectInput.tsx index 2d9dfa671..393f3fe7e 100644 --- a/apps/builder/src/features/forge/components/ForgeSelectInput.tsx +++ b/apps/builder/src/features/forge/components/ForgeSelectInput.tsx @@ -54,7 +54,7 @@ export const ForgeSelectInput = ({ return fetchers.find((fetcher) => fetcher.id === fetcherId) }, [baseFetcher, blockDef.actions, fetcherId]) - const { data } = trpc.integrations.fetchSelectItems.useQuery( + const { data } = trpc.forge.fetchSelectItems.useQuery( { integrationId: blockDef.id, options: pick(options, [ diff --git a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx index 2bc33be2a..d8725224a 100644 --- a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx +++ b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsDropdown.tsx @@ -34,14 +34,13 @@ export const ForgedCredentialsDropdown = ({ const router = useRouter() const { showToast } = useToast() const { workspace, currentRole } = useWorkspace() - const { data, refetch, isLoading } = - trpc.integrationCredentials.listCredentials.useQuery( - { - workspaceId: workspace?.id as string, - type: blockDef.id, - }, - { enabled: !!workspace?.id } - ) + const { data, refetch, isLoading } = trpc.forge.listCredentials.useQuery( + { + workspaceId: workspace?.id as string, + type: blockDef.id, + }, + { enabled: !!workspace?.id } + ) const [isDeleting, setIsDeleting] = useState() const { mutate } = trpc.credentials.deleteCredentials.useMutation({ diff --git a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsModal.tsx b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsModal.tsx index be3261d6b..2e260d8ed 100644 --- a/apps/builder/src/features/forge/components/credentials/ForgedCredentialsModal.tsx +++ b/apps/builder/src/features/forge/components/credentials/ForgedCredentialsModal.tsx @@ -42,7 +42,7 @@ export const ForgedCredentialsModal = ({ listCredentials: { refetch: refetchCredentials }, }, } = trpc.useContext() - const { mutate } = trpc.integrationCredentials.createCredentials.useMutation({ + const { mutate } = trpc.forge.createCredentials.useMutation({ onMutate: () => setIsCreating(true), onSettled: () => setIsCreating(false), onError: (err) => { diff --git a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx index ab27d29f8..6d1f1316e 100644 --- a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx +++ b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppCredentialsModal.tsx @@ -100,12 +100,13 @@ export const WhatsAppCredentialsModal = ({ }, }) - const { data: tokenInfoData } = trpc.whatsApp.getSystemTokenInfo.useQuery( - { - token: systemUserAccessToken, - }, - { enabled: isNotEmpty(systemUserAccessToken) } - ) + const { data: tokenInfoData } = + trpc.whatsAppInternal.getSystemTokenInfo.useQuery( + { + token: systemUserAccessToken, + }, + { enabled: isNotEmpty(systemUserAccessToken) } + ) const resetForm = () => { setActiveStep(0) @@ -133,7 +134,7 @@ export const WhatsAppCredentialsModal = ({ setIsVerifying(true) try { const { expiresAt, scopes } = - await trpcVanilla.whatsApp.getSystemTokenInfo.query({ + await trpcVanilla.whatsAppInternal.getSystemTokenInfo.query({ token: systemUserAccessToken, }) if (expiresAt !== 0) { @@ -167,16 +168,18 @@ export const WhatsAppCredentialsModal = ({ const isPhoneNumberAvailable = async () => { setIsVerifying(true) try { - const { name } = await trpcVanilla.whatsApp.getPhoneNumber.query({ + const { name } = await trpcVanilla.whatsAppInternal.getPhoneNumber.query({ systemToken: systemUserAccessToken, phoneNumberId, }) setPhoneNumberName(name) try { const { message } = - await trpcVanilla.whatsApp.verifyIfPhoneNumberAvailable.query({ - phoneNumberDisplayName: name, - }) + await trpcVanilla.whatsAppInternal.verifyIfPhoneNumberAvailable.query( + { + phoneNumberDisplayName: name, + } + ) if (message === 'taken') { setIsVerifying(false) @@ -186,7 +189,7 @@ export const WhatsAppCredentialsModal = ({ return false } const { verificationToken } = - await trpcVanilla.whatsApp.generateVerificationToken.mutate() + await trpcVanilla.whatsAppInternal.generateVerificationToken.mutate() setVerificationToken(verificationToken) } catch (err) { console.error(err) diff --git a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppModal.tsx b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppModal.tsx index 862e6818a..84143d2c2 100644 --- a/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppModal.tsx +++ b/apps/builder/src/features/publish/components/embeds/modals/WhatsAppModal/WhatsAppModal.tsx @@ -56,14 +56,15 @@ export const WhatsAppModal = ({ isOpen, onClose }: ModalProps): JSX.Element => { const whatsAppSettings = typebot?.settings.whatsApp - const { data: phoneNumberData } = trpc.whatsApp.getPhoneNumber.useQuery( - { - credentialsId: typebot?.whatsAppCredentialsId as string, - }, - { - enabled: !!typebot?.whatsAppCredentialsId, - } - ) + const { data: phoneNumberData } = + trpc.whatsAppInternal.getPhoneNumber.useQuery( + { + credentialsId: typebot?.whatsAppCredentialsId as string, + }, + { + enabled: !!typebot?.whatsAppCredentialsId, + } + ) const toggleEnableWhatsApp = (isChecked: boolean) => { if (!phoneNumberData?.id || !typebot) return diff --git a/apps/builder/src/features/upload/api/generateUploadUrl.ts b/apps/builder/src/features/upload/api/generateUploadUrl.ts index 7438764ee..952b3ad75 100644 --- a/apps/builder/src/features/upload/api/generateUploadUrl.ts +++ b/apps/builder/src/features/upload/api/generateUploadUrl.ts @@ -42,22 +42,7 @@ export type FilePathUploadProps = z.infer< > export const generateUploadUrl = authenticatedProcedure - .meta({ - openapi: { - method: 'POST', - path: '/generate-upload-url', - summary: 'Generate upload URL', - description: 'Generate the needed URL to upload a file from the client', - }, - }) .input(inputSchema) - .output( - z.object({ - presignedUrl: z.string(), - formData: z.record(z.string(), z.any()), - fileUrl: z.string(), - }) - ) .mutation(async ({ input: { filePathProps, fileType }, ctx: { user } }) => { if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY) throw new TRPCError({ diff --git a/apps/builder/src/features/whatsapp/generateVerificationToken.ts b/apps/builder/src/features/whatsapp/generateVerificationToken.ts index ca53b371b..38d4e6639 100644 --- a/apps/builder/src/features/whatsapp/generateVerificationToken.ts +++ b/apps/builder/src/features/whatsapp/generateVerificationToken.ts @@ -1,23 +1,9 @@ import { authenticatedProcedure } from '@/helpers/server/trpc' -import { z } from 'zod' import prisma from '@typebot.io/lib/prisma' import { createId } from '@paralleldrive/cuid2' -export const generateVerificationToken = authenticatedProcedure - .meta({ - openapi: { - method: 'POST', - path: '/verficiationTokens', - protect: true, - }, - }) - .input(z.void()) - .output( - z.object({ - verificationToken: z.string(), - }) - ) - .mutation(async () => { +export const generateVerificationToken = authenticatedProcedure.mutation( + async () => { const oneHourLater = new Date(Date.now() + 1000 * 60 * 60) const verificationToken = await prisma.verificationToken.create({ data: { @@ -28,4 +14,5 @@ export const generateVerificationToken = authenticatedProcedure }) return { verificationToken: verificationToken.token } - }) + } +) diff --git a/apps/builder/src/features/whatsapp/getPhoneNumber.ts b/apps/builder/src/features/whatsapp/getPhoneNumber.ts index 57d02e529..7e9d1aa63 100644 --- a/apps/builder/src/features/whatsapp/getPhoneNumber.ts +++ b/apps/builder/src/features/whatsapp/getPhoneNumber.ts @@ -13,20 +13,7 @@ const inputSchema = z.object({ }) export const getPhoneNumber = authenticatedProcedure - .meta({ - openapi: { - method: 'GET', - path: '/whatsapp/phoneNumber', - protect: true, - }, - }) .input(inputSchema) - .output( - z.object({ - id: z.string(), - name: z.string(), - }) - ) .query(async ({ input, ctx: { user } }) => { const credentials = await getCredentials(user.id, input) if (!credentials) diff --git a/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts b/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts index 6c08f107f..5f84bfb86 100644 --- a/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts +++ b/apps/builder/src/features/whatsapp/getSystemTokenInfo.ts @@ -12,22 +12,7 @@ const inputSchema = z.object({ }) export const getSystemTokenInfo = authenticatedProcedure - .meta({ - openapi: { - method: 'GET', - path: '/whatsapp/systemToken', - protect: true, - }, - }) .input(inputSchema) - .output( - z.object({ - appId: z.string(), - appName: z.string(), - expiresAt: z.number(), - scopes: z.array(z.string()), - }) - ) .query(async ({ input, ctx: { user } }) => { if (!input.token && !input.credentialsId) throw new TRPCError({ diff --git a/apps/builder/src/features/whatsapp/receiveMessagePreview.ts b/apps/builder/src/features/whatsapp/receiveMessagePreview.ts index 9ae10b17f..97d03c52f 100644 --- a/apps/builder/src/features/whatsapp/receiveMessagePreview.ts +++ b/apps/builder/src/features/whatsapp/receiveMessagePreview.ts @@ -10,7 +10,7 @@ export const receiveMessagePreview = publicProcedure .meta({ openapi: { method: 'POST', - path: '/whatsapp/preview/webhook', + path: '/v1/whatsapp/preview/webhook', summary: 'Message webhook', tags: ['WhatsApp'], }, diff --git a/apps/builder/src/features/whatsapp/router.ts b/apps/builder/src/features/whatsapp/router.ts index 6f4dd298c..f65aaa01c 100644 --- a/apps/builder/src/features/whatsapp/router.ts +++ b/apps/builder/src/features/whatsapp/router.ts @@ -7,11 +7,14 @@ import { startWhatsAppPreview } from './startWhatsAppPreview' import { subscribePreviewWebhook } from './subscribePreviewWebhook' import { receiveMessagePreview } from './receiveMessagePreview' -export const whatsAppRouter = router({ +export const internalWhatsAppRouter = router({ getPhoneNumber, getSystemTokenInfo, verifyIfPhoneNumberAvailable, generateVerificationToken, +}) + +export const publicWhatsAppRouter = router({ startWhatsAppPreview, subscribePreviewWebhook, receiveMessagePreview, diff --git a/apps/builder/src/features/whatsapp/startWhatsAppPreview.ts b/apps/builder/src/features/whatsapp/startWhatsAppPreview.ts index ae49bff20..2ea1903f7 100644 --- a/apps/builder/src/features/whatsapp/startWhatsAppPreview.ts +++ b/apps/builder/src/features/whatsapp/startWhatsAppPreview.ts @@ -16,7 +16,7 @@ export const startWhatsAppPreview = authenticatedProcedure .meta({ openapi: { method: 'POST', - path: '/typebots/{typebotId}/whatsapp/start-preview', + path: '/v1/typebots/{typebotId}/whatsapp/start-preview', summary: 'Start preview', tags: ['WhatsApp'], protect: true, diff --git a/apps/builder/src/features/whatsapp/subscribePreviewWebhook.ts b/apps/builder/src/features/whatsapp/subscribePreviewWebhook.ts index b08d49af7..68cf8514e 100644 --- a/apps/builder/src/features/whatsapp/subscribePreviewWebhook.ts +++ b/apps/builder/src/features/whatsapp/subscribePreviewWebhook.ts @@ -7,7 +7,7 @@ export const subscribePreviewWebhook = publicProcedure .meta({ openapi: { method: 'GET', - path: '/whatsapp/preview/webhook', + path: '/v1/whatsapp/preview/webhook', summary: 'Subscribe webhook', tags: ['WhatsApp'], }, diff --git a/apps/builder/src/features/whatsapp/verifyIfPhoneNumberAvailable.ts b/apps/builder/src/features/whatsapp/verifyIfPhoneNumberAvailable.ts index 9c32186c0..b6ba3b626 100644 --- a/apps/builder/src/features/whatsapp/verifyIfPhoneNumberAvailable.ts +++ b/apps/builder/src/features/whatsapp/verifyIfPhoneNumberAvailable.ts @@ -3,19 +3,7 @@ import { z } from 'zod' import prisma from '@typebot.io/lib/prisma' export const verifyIfPhoneNumberAvailable = authenticatedProcedure - .meta({ - openapi: { - method: 'GET', - path: '/whatsapp/phoneNumber/{phoneNumberDisplayName}/available', - protect: true, - }, - }) .input(z.object({ phoneNumberDisplayName: z.string() })) - .output( - z.object({ - message: z.enum(['available', 'taken']), - }) - ) .query(async ({ input: { phoneNumberDisplayName } }) => { const existingWhatsAppCredentials = await prisma.credentials.findFirst({ where: { diff --git a/apps/builder/src/helpers/server/routers/internalRouter.ts b/apps/builder/src/helpers/server/routers/internalRouter.ts index b609a77bd..4857c7465 100644 --- a/apps/builder/src/helpers/server/routers/internalRouter.ts +++ b/apps/builder/src/helpers/server/routers/internalRouter.ts @@ -2,20 +2,18 @@ import { getAppVersionProcedure } from '@/features/dashboard/api/getAppVersionPr import { router } from '../trpc' import { generateUploadUrl } from '@/features/upload/api/generateUploadUrl' import { openAIRouter } from '@/features/blocks/integrations/openai/api/router' -import { whatsAppRouter } from '@/features/whatsapp/router' +import { internalWhatsAppRouter } from '@/features/whatsapp/router' import { zemanticAiRouter } from '@/features/blocks/integrations/zemanticAi/api/router' -import { forgedCredentialsRouter } from '@/features/forge/api/credentials/router' -import { integrationsRouter } from '@/features/forge/api/router' +import { forgeRouter } from '@/features/forge/api/router' import { googleSheetsRouter } from '@/features/blocks/integrations/googleSheets/api/router' export const internalRouter = router({ getAppVersionProcedure, generateUploadUrl, - whatsApp: whatsAppRouter, + whatsAppInternal: internalWhatsAppRouter, openAI: openAIRouter, zemanticAI: zemanticAiRouter, - integrationCredentials: forgedCredentialsRouter, - integrations: integrationsRouter, + forge: forgeRouter, sheets: googleSheetsRouter, }) diff --git a/apps/builder/src/helpers/server/routers/publicRouter.ts b/apps/builder/src/helpers/server/routers/publicRouter.ts index 1452ee9b9..484bc20fd 100644 --- a/apps/builder/src/helpers/server/routers/publicRouter.ts +++ b/apps/builder/src/helpers/server/routers/publicRouter.ts @@ -11,6 +11,7 @@ import { analyticsRouter } from '@/features/analytics/api/router' import { collaboratorsRouter } from '@/features/collaboration/api/router' import { customDomainsRouter } from '@/features/customDomains/api/router' import { processTelemetryEvent } from '@/features/telemetry/api/processTelemetryEvent' +import { publicWhatsAppRouter } from '@/features/whatsapp/router' export const publicRouter = router({ getLinkedTypebots, @@ -25,6 +26,7 @@ export const publicRouter = router({ collaborators: collaboratorsRouter, customDomains: customDomainsRouter, processTelemetryEvent, + whatsApp: publicWhatsAppRouter, }) export type PublicRouter = typeof publicRouter diff --git a/apps/docs/openapi/builder/_spec_.json b/apps/docs/openapi/builder/_spec_.json index 400aee6bb..5e0ab0bb1 100644 --- a/apps/docs/openapi/builder/_spec_.json +++ b/apps/docs/openapi/builder/_spec_.json @@ -56965,6 +56965,551 @@ } } } + }, + "/v1/typebots/{typebotId}/whatsapp/start-preview": { + "post": { + "operationId": "whatsApp-startWhatsAppPreview", + "summary": "Start preview", + "tags": [ + "WhatsApp" + ], + "security": [ + { + "Authorization": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "to": { + "type": "string", + "minLength": 1 + }, + "startFrom": { + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "group" + ] + }, + "groupId": { + "type": "string" + } + }, + "required": [ + "type", + "groupId" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "event" + ] + }, + "eventId": { + "type": "string" + } + }, + "required": [ + "type", + "eventId" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "to" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [ + { + "name": "typebotId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/v1/whatsapp/preview/webhook": { + "get": { + "operationId": "whatsApp-subscribePreviewWebhook", + "summary": "Subscribe webhook", + "tags": [ + "WhatsApp" + ], + "parameters": [ + { + "name": "hub.challenge", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "hub.verify_token", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "number" + } + } + } + }, + "default": { + "$ref": "#/components/responses/error" + } + } + }, + "post": { + "operationId": "whatsApp-receiveMessagePreview", + "summary": "Message webhook", + "tags": [ + "WhatsApp" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "entry": { + "type": "array", + "items": { + "type": "object", + "properties": { + "changes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "object", + "properties": { + "metadata": { + "type": "object", + "properties": { + "phone_number_id": { + "type": "string" + } + }, + "required": [ + "phone_number_id" + ], + "additionalProperties": false + }, + "contacts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "profile" + ], + "additionalProperties": false + } + }, + "messages": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "text" + ] + }, + "text": { + "type": "object", + "properties": { + "body": { + "type": "string" + } + }, + "required": [ + "body" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "text", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "button" + ] + }, + "button": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "payload": { + "type": "string" + } + }, + "required": [ + "text", + "payload" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "button", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "interactive" + ] + }, + "interactive": { + "type": "object", + "properties": { + "button_reply": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "required": [ + "id", + "title" + ], + "additionalProperties": false + } + }, + "required": [ + "button_reply" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "interactive", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "image" + ] + }, + "image": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "image", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "video" + ] + }, + "video": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "video", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "audio" + ] + }, + "audio": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "audio", + "timestamp" + ], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "document" + ] + }, + "document": { + "type": "object", + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "additionalProperties": false + }, + "timestamp": { + "type": "string" + } + }, + "required": [ + "from", + "type", + "document", + "timestamp" + ], + "additionalProperties": false + } + ] + } + } + }, + "required": [ + "metadata" + ], + "additionalProperties": false + } + }, + "required": [ + "value" + ], + "additionalProperties": false + } + } + }, + "required": [ + "changes" + ], + "additionalProperties": false + } + } + }, + "required": [ + "entry" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/error" + } + } + } } }, "components": { diff --git a/packages/bot-engine/blocks/inputs/url/validateUrl.ts b/packages/bot-engine/blocks/inputs/url/validateUrl.ts index 37112d380..7958cdd16 100644 --- a/packages/bot-engine/blocks/inputs/url/validateUrl.ts +++ b/packages/bot-engine/blocks/inputs/url/validateUrl.ts @@ -1,4 +1,8 @@ -const urlRegex = - /^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})$/ - -export const validateUrl = (url: string) => urlRegex.test(url) +export const validateUrl = (url: string) => { + try { + new URL(url) + return true + } catch (_) { + return false + } +} diff --git a/packages/bot-engine/continueBotFlow.ts b/packages/bot-engine/continueBotFlow.ts index d35b457f3..33babbfc5 100644 --- a/packages/bot-engine/continueBotFlow.ts +++ b/packages/bot-engine/continueBotFlow.ts @@ -445,7 +445,9 @@ const parseReply = return block.options?.isRequired ?? defaultFileInputOptions.isRequired ? { status: 'fail' } : { status: 'skip' } - return { status: 'success', reply: inputValue } + const urls = inputValue.split(', ') + const status = urls.some((url) => validateUrl(url)) ? 'success' : 'fail' + return { status, reply: inputValue } } case InputBlockType.PAYMENT: { if (!inputValue) return { status: 'fail' } diff --git a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts index 30710b347..ba87ab179 100644 --- a/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts +++ b/packages/bot-engine/whatsapp/resumeWhatsAppFlow.ts @@ -140,11 +140,15 @@ const getIncomingMessageContent = async ({ } case 'document': case 'audio': - return case 'video': case 'image': if (!typebotId) return - const mediaId = 'video' in message ? message.video.id : message.image.id + let mediaId: string | undefined + if (message.type === 'video') mediaId = message.video.id + if (message.type === 'image') mediaId = message.image.id + if (message.type === 'audio') mediaId = message.audio.id + if (message.type === 'document') mediaId = message.document.id + if (!mediaId) return return ( env.NEXTAUTH_URL + `/api/typebots/${typebotId}/whatsapp/media/${mediaId}`