diff --git a/apps/builder/src/features/blocks/integrations/webhook/queries/createWebhookQuery.ts b/apps/builder/src/features/blocks/integrations/webhook/queries/createWebhookQuery.ts deleted file mode 100644 index 55289919688..00000000000 --- a/apps/builder/src/features/blocks/integrations/webhook/queries/createWebhookQuery.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Webhook } from '@typebot.io/schemas' -import { sendRequest } from '@typebot.io/lib' - -type Props = { - typebotId: string - data: Partial> -} - -export const createWebhookQuery = ({ typebotId, data }: Props) => - sendRequest<{ webhook: Webhook }>({ - method: 'POST', - url: `/api/typebots/${typebotId}/webhooks`, - body: { data }, - }) diff --git a/apps/builder/src/features/blocks/integrations/webhook/queries/duplicateWebhookQuery.ts b/apps/builder/src/features/blocks/integrations/webhook/queries/duplicateWebhookQuery.ts deleted file mode 100644 index 8faf94b5f21..00000000000 --- a/apps/builder/src/features/blocks/integrations/webhook/queries/duplicateWebhookQuery.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Webhook } from '@typebot.io/schemas' -import { sendRequest } from '@typebot.io/lib' -import { createWebhookQuery } from './createWebhookQuery' - -type Props = { - existingIds: { typebotId: string; webhookId: string } - newIds: { typebotId: string; webhookId: string } -} -export const duplicateWebhookQuery = async ({ - existingIds, - newIds, -}: Props): Promise => { - const { data } = await sendRequest<{ webhook: Webhook }>( - `/api/typebots/${existingIds.typebotId}/webhooks/${existingIds.webhookId}` - ) - if (!data) return - const newWebhook = { - ...data.webhook, - id: newIds.webhookId, - typebotId: newIds.typebotId, - } - await createWebhookQuery({ - typebotId: newIds.typebotId, - data: { ...data.webhook, id: newIds.webhookId }, - }) - return newWebhook -} diff --git a/apps/builder/src/features/blocks/integrations/webhook/queries/updateWebhookQuery.ts b/apps/builder/src/features/blocks/integrations/webhook/queries/updateWebhookQuery.ts deleted file mode 100644 index ebee1e91223..00000000000 --- a/apps/builder/src/features/blocks/integrations/webhook/queries/updateWebhookQuery.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Webhook } from '@typebot.io/schemas' -import { sendRequest } from '@typebot.io/lib' - -type Props = { - typebotId: string - webhookId: string - data: Partial> -} - -export const updateWebhookQuery = ({ typebotId, webhookId, data }: Props) => - sendRequest<{ webhook: Webhook }>({ - method: 'PATCH', - url: `/api/typebots/${typebotId}/webhooks/${webhookId}`, - body: { data }, - }) diff --git a/apps/builder/src/features/blocks/logic/typebotLink/api/getLinkedTypebots.ts b/apps/builder/src/features/blocks/logic/typebotLink/api/getLinkedTypebots.ts index 83ba1cfc769..eddc52bda88 100644 --- a/apps/builder/src/features/blocks/logic/typebotLink/api/getLinkedTypebots.ts +++ b/apps/builder/src/features/blocks/logic/typebotLink/api/getLinkedTypebots.ts @@ -24,7 +24,7 @@ export const getLinkedTypebots = authenticatedProcedure .output( z.object({ typebots: z.array( - typebotSchema.pick({ + typebotSchema._def.schema.pick({ id: true, groups: true, variables: true, @@ -58,7 +58,7 @@ export const getLinkedTypebots = authenticatedProcedure throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebot found' }) const linkedTypebotIds = - typebotSchema.shape.groups + typebotSchema._def.schema.shape.groups .parse(typebot.groups) .flatMap((group) => group.blocks) .reduce( @@ -102,8 +102,10 @@ export const getLinkedTypebots = authenticatedProcedure }) .map((typebot) => ({ ...typebot, - groups: typebotSchema.shape.groups.parse(typebot.groups), - variables: typebotSchema.shape.variables.parse(typebot.variables), + groups: typebotSchema._def.schema.shape.groups.parse(typebot.groups), + variables: typebotSchema._def.schema.shape.variables.parse( + typebot.variables + ), })) return { diff --git a/apps/builder/src/features/dashboard/api/parseNewTypebot.ts b/apps/builder/src/features/dashboard/api/parseNewTypebot.ts deleted file mode 100644 index 4e9abb92caf..00000000000 --- a/apps/builder/src/features/dashboard/api/parseNewTypebot.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { createId } from '@paralleldrive/cuid2' -import { - defaultSettings, - defaultTheme, - Group, - StartBlock, - Typebot, -} from '@typebot.io/schemas' - -// TODO: remove -export type NewTypebotProps = Omit< - Typebot, - | 'createdAt' - | 'updatedAt' - | 'id' - | 'publicId' - | 'customDomain' - | 'icon' - | 'isArchived' - | 'isClosed' - | 'resultsTablePreferences' -> - -export const parseNewTypebot = ({ - folderId, - name, - workspaceId, - isBrandingEnabled = true, -}: { - folderId: string | null - workspaceId: string - name: string - ownerAvatarUrl?: string - isBrandingEnabled?: boolean -}): NewTypebotProps => { - const startGroupId = createId() - const startBlockId = createId() - const startBlock: StartBlock = { - groupId: startGroupId, - id: startBlockId, - label: 'Start', - type: 'start', - } - const startGroup: Group = { - id: startGroupId, - title: 'Start', - graphCoordinates: { x: 0, y: 0 }, - blocks: [startBlock], - } - return { - folderId, - name, - version: '4', - workspaceId, - groups: [startGroup], - edges: [], - variables: [], - selectedThemeTemplateId: null, - theme: defaultTheme, - settings: defaultSettings({ isBrandingEnabled }), - } -} diff --git a/apps/builder/src/features/publish/publish.spec.ts b/apps/builder/src/features/publish/publish.spec.ts index 03c7a06e0a6..ea387e47217 100644 --- a/apps/builder/src/features/publish/publish.spec.ts +++ b/apps/builder/src/features/publish/publish.spec.ts @@ -33,9 +33,7 @@ test('should not be able to submit taken url ID', async ({ page }) => { await page.getByRole('textbox').press('Enter') await expect( page - .getByText( - 'Should contain only contain letters, numbers. Words can be separated by dashes.' - ) + .getByText('Can only contain lowercase letters, numbers and dashes.') .nth(0) ).toBeVisible() await page.getByText(`${typebotId}-public`).click() diff --git a/apps/builder/src/features/templates/components/ImportTypebotFromFileButton.tsx b/apps/builder/src/features/templates/components/ImportTypebotFromFileButton.tsx index c66593505c4..0ffed4d4d3f 100644 --- a/apps/builder/src/features/templates/components/ImportTypebotFromFileButton.tsx +++ b/apps/builder/src/features/templates/components/ImportTypebotFromFileButton.tsx @@ -1,4 +1,3 @@ -import { parseInvalidTypebot } from '@/features/typebot/helpers/parseInvalidTypebot' import { useToast } from '@/hooks/useToast' import { Button, ButtonProps, chakra } from '@chakra-ui/react' import { Typebot, typebotCreateSchema } from '@typebot.io/schemas' @@ -19,9 +18,7 @@ export const ImportTypebotFromFileButton = ({ const file = e.target.files[0] const fileContent = await readFile(file) try { - const typebot = typebotCreateSchema.parse( - parseInvalidTypebot(JSON.parse(fileContent)) - ) + const typebot = typebotCreateSchema.parse(JSON.parse(fileContent)) onNewTypebot(typebot as Typebot) } catch (err) { console.error(err) diff --git a/apps/builder/src/features/typebot/api/getPublishedTypebot.ts b/apps/builder/src/features/typebot/api/getPublishedTypebot.ts index ec11b290226..dd5249d0868 100644 --- a/apps/builder/src/features/typebot/api/getPublishedTypebot.ts +++ b/apps/builder/src/features/typebot/api/getPublishedTypebot.ts @@ -4,8 +4,6 @@ import { TRPCError } from '@trpc/server' import { publicTypebotSchema } from '@typebot.io/schemas' import { z } from 'zod' import { isReadTypebotForbidden } from '../helpers/isReadTypebotForbidden' -import { parseInvalidTypebot } from '../helpers/parseInvalidTypebot' -import { PublicTypebot } from '@typebot.io/schemas' export const getPublishedTypebot = authenticatedProcedure .meta({ @@ -50,7 +48,7 @@ export const getPublishedTypebot = authenticatedProcedure try { const parsedTypebot = publicTypebotSchema.parse( - parseInvalidTypebot(existingTypebot.publishedTypebot as PublicTypebot) + existingTypebot.publishedTypebot ) return { diff --git a/apps/builder/src/features/typebot/api/getTypebot.ts b/apps/builder/src/features/typebot/api/getTypebot.ts index 2d401d84113..517e07c0fc1 100644 --- a/apps/builder/src/features/typebot/api/getTypebot.ts +++ b/apps/builder/src/features/typebot/api/getTypebot.ts @@ -4,10 +4,7 @@ import { TRPCError } from '@trpc/server' import { Typebot, typebotSchema } from '@typebot.io/schemas' import { z } from 'zod' import { isReadTypebotForbidden } from '../helpers/isReadTypebotForbidden' -import { omit } from '@typebot.io/lib' -import { Typebot as TypebotFromDb } from '@typebot.io/prisma' import { migrateTypebotFromV3ToV4 } from '@typebot.io/lib/migrations/migrateTypebotFromV3ToV4' -import { parseInvalidTypebot } from '../helpers/parseInvalidTypebot' export const getTypebot = authenticatedProcedure .meta({ @@ -46,8 +43,8 @@ export const getTypebot = authenticatedProcedure throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' }) try { - const parsedTypebot = await parseTypebot( - omit(existingTypebot, 'collaborators') + const parsedTypebot = await migrateTypebot( + typebotSchema.parse(existingTypebot) ) return { @@ -66,10 +63,7 @@ export const getTypebot = authenticatedProcedure } }) -const parseTypebot = async (typebot: TypebotFromDb): Promise => { - const parsedTypebot = typebotSchema.parse( - typebot.version !== '5' ? parseInvalidTypebot(typebot as Typebot) : typebot - ) - if (['4', '5'].includes(parsedTypebot.version ?? '')) return parsedTypebot - return migrateTypebotFromV3ToV4(prisma)(parsedTypebot) +const migrateTypebot = async (typebot: Typebot): Promise => { + if (['4', '5'].includes(typebot.version ?? '')) return typebot + return migrateTypebotFromV3ToV4(prisma)(typebot) } diff --git a/apps/builder/src/features/typebot/api/listTypebots.ts b/apps/builder/src/features/typebot/api/listTypebots.ts index a5a2bd5d77f..31ce74db448 100644 --- a/apps/builder/src/features/typebot/api/listTypebots.ts +++ b/apps/builder/src/features/typebot/api/listTypebots.ts @@ -21,7 +21,7 @@ export const listTypebots = authenticatedProcedure .output( z.object({ typebots: z.array( - typebotSchema + typebotSchema._def.schema .pick({ name: true, icon: true, diff --git a/apps/builder/src/features/typebot/api/publishTypebot.ts b/apps/builder/src/features/typebot/api/publishTypebot.ts index eda90d07e9b..451c012ff8c 100644 --- a/apps/builder/src/features/typebot/api/publishTypebot.ts +++ b/apps/builder/src/features/typebot/api/publishTypebot.ts @@ -49,15 +49,21 @@ export const publishTypebot = authenticatedProcedure }, data: { version: existingTypebot.version, - edges: typebotSchema.shape.edges.parse(existingTypebot.edges), - groups: typebotSchema.shape.groups.parse(existingTypebot.groups), - settings: typebotSchema.shape.settings.parse( + edges: typebotSchema._def.schema.shape.edges.parse( + existingTypebot.edges + ), + groups: typebotSchema._def.schema.shape.groups.parse( + existingTypebot.groups + ), + settings: typebotSchema._def.schema.shape.settings.parse( existingTypebot.settings ), - variables: typebotSchema.shape.variables.parse( + variables: typebotSchema._def.schema.shape.variables.parse( existingTypebot.variables ), - theme: typebotSchema.shape.theme.parse(existingTypebot.theme), + theme: typebotSchema._def.schema.shape.theme.parse( + existingTypebot.theme + ), }, }) else @@ -65,15 +71,21 @@ export const publishTypebot = authenticatedProcedure data: { version: existingTypebot.version, typebotId: existingTypebot.id, - edges: typebotSchema.shape.edges.parse(existingTypebot.edges), - groups: typebotSchema.shape.groups.parse(existingTypebot.groups), - settings: typebotSchema.shape.settings.parse( + edges: typebotSchema._def.schema.shape.edges.parse( + existingTypebot.edges + ), + groups: typebotSchema._def.schema.shape.groups.parse( + existingTypebot.groups + ), + settings: typebotSchema._def.schema.shape.settings.parse( existingTypebot.settings ), - variables: typebotSchema.shape.variables.parse( + variables: typebotSchema._def.schema.shape.variables.parse( existingTypebot.variables ), - theme: typebotSchema.shape.theme.parse(existingTypebot.theme), + theme: typebotSchema._def.schema.shape.theme.parse( + existingTypebot.theme + ), }, }) diff --git a/apps/builder/src/features/typebot/api/updateTypebot.ts b/apps/builder/src/features/typebot/api/updateTypebot.ts index dde0acf7379..e8ce43186a3 100644 --- a/apps/builder/src/features/typebot/api/updateTypebot.ts +++ b/apps/builder/src/features/typebot/api/updateTypebot.ts @@ -27,7 +27,7 @@ export const updateTypebot = authenticatedProcedure z.object({ typebotId: z.string(), typebot: typebotCreateSchema.merge( - typebotSchema + typebotSchema._def.schema .pick({ isClosed: true, }) diff --git a/apps/builder/src/features/typebot/helpers/parseInvalidTypebot.ts b/apps/builder/src/features/typebot/helpers/parseInvalidTypebot.ts deleted file mode 100644 index bd2635e0872..00000000000 --- a/apps/builder/src/features/typebot/helpers/parseInvalidTypebot.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Edge, PublicTypebot, Typebot, edgeSchema } from '@typebot.io/schemas' - -export const parseInvalidTypebot = ( - typebot: Typebot | PublicTypebot -): Typebot | PublicTypebot => ({ - ...typebot, - version: typebot.version as null | '3' | '4' | '5', - edges: parseInvalidEdges(typebot.edges), -}) - -const parseInvalidEdges = (edges: Edge[]) => - edges?.filter((edge) => edgeSchema.safeParse(edge).success) diff --git a/apps/builder/src/pages/api/customDomains.ts b/apps/builder/src/pages/api/customDomains.ts deleted file mode 100644 index 6ec6d8ad146..00000000000 --- a/apps/builder/src/pages/api/customDomains.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { CustomDomain } from '@typebot.io/prisma' -import { got, HTTPError } from 'got' -import prisma from '@/lib/prisma' -import { NextApiRequest, NextApiResponse } from 'next' -import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' -import { - badRequest, - forbidden, - methodNotAllowed, - notAuthenticated, -} from '@typebot.io/lib/api' - -// TODO: delete -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const user = await getAuthenticatedUser(req, res) - if (!user) return notAuthenticated(res) - const workspaceId = req.query.workspaceId as string | undefined - if (!workspaceId) return badRequest(res) - if (req.method === 'GET') { - const customDomains = await prisma.customDomain.findMany({ - where: { - workspace: { id: workspaceId, members: { some: { userId: user.id } } }, - }, - }) - return res.send({ customDomains }) - } - if (req.method === 'POST') { - const workspace = await prisma.workspace.findFirst({ - where: { id: workspaceId, members: { some: { userId: user.id } } }, - select: { id: true }, - }) - if (!workspace) return forbidden(res) - const data = ( - typeof req.body === 'string' ? JSON.parse(req.body) : req.body - ) as CustomDomain - try { - await createDomainOnVercel(data.name) - } catch (err) { - console.log(err) - if (err instanceof HTTPError && err.response.statusCode !== 409) - return res.status(err.response.statusCode).send(err.response.body) - } - - const customDomains = await prisma.customDomain.create({ - data: { - ...data, - workspaceId, - }, - }) - return res.send({ customDomains }) - } - return methodNotAllowed(res) -} - -const createDomainOnVercel = (name: string) => - got.post({ - url: `https://api.vercel.com/v8/projects/${process.env.NEXT_PUBLIC_VERCEL_VIEWER_PROJECT_NAME}/domains?teamId=${process.env.VERCEL_TEAM_ID}`, - headers: { Authorization: `Bearer ${process.env.VERCEL_TOKEN}` }, - json: { name }, - }) - -export default handler diff --git a/apps/builder/src/pages/api/typebots.ts b/apps/builder/src/pages/api/typebots.ts deleted file mode 100644 index 06fb67af908..00000000000 --- a/apps/builder/src/pages/api/typebots.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Plan } from '@typebot.io/prisma' -import prisma from '@/lib/prisma' -import { NextApiRequest, NextApiResponse } from 'next' -import { - methodNotAllowed, - notAuthenticated, - notFound, -} from '@typebot.io/lib/api' -import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' -import { - NewTypebotProps, - parseNewTypebot, -} from '@/features/dashboard/api/parseNewTypebot' -import { omit } from '@typebot.io/lib' -import { sendTelemetryEvents } from '@typebot.io/lib/telemetry/sendTelemetryEvent' - -// TODO: Delete -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const user = await getAuthenticatedUser(req, res) - if (!user) return notAuthenticated(res) - try { - if (req.method === 'POST') { - const workspace = await prisma.workspace.findFirst({ - where: { id: req.body.workspaceId }, - select: { plan: true }, - }) - if (!workspace) return notFound(res, "Couldn't find workspace") - const data = - typeof req.body === 'string' ? JSON.parse(req.body) : req.body - const formattedData = removeOldProperties(data) as - | NewTypebotProps - | Omit - const typebot = await prisma.typebot.create({ - data: - 'groups' in formattedData - ? formattedData - : parseNewTypebot({ - isBrandingEnabled: workspace.plan === Plan.FREE, - ...data, - }), - }) - await sendTelemetryEvents([ - { - name: 'Typebot created', - userId: user.id, - workspaceId: typebot.workspaceId, - typebotId: typebot.id, - data: { - name: typebot.name, - }, - }, - ]) - return res.send(typebot) - } - return methodNotAllowed(res) - } catch (err) { - console.error(err) - if (err instanceof Error) { - return res.status(500).send({ title: err.name, message: err.message }) - } - return res.status(500).send({ message: 'An error occured', error: err }) - } -} - -const removeOldProperties = (data: unknown) => { - if (data && typeof data === 'object' && 'publishedTypebotId' in data) { - return omit(data, 'publishedTypebotId') - } - return data -} - -export default handler diff --git a/apps/builder/src/pages/api/typebots/[typebotId].ts b/apps/builder/src/pages/api/typebots/[typebotId].ts deleted file mode 100644 index 355f6ea98cf..00000000000 --- a/apps/builder/src/pages/api/typebots/[typebotId].ts +++ /dev/null @@ -1,180 +0,0 @@ -import { CollaborationType, Prisma } from '@typebot.io/prisma' -import prisma from '@/lib/prisma' -import { NextApiRequest, NextApiResponse } from 'next' -import { methodNotAllowed, notAuthenticated } from '@typebot.io/lib/api' -import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' -import { Group, Typebot } from '@typebot.io/schemas' -import { omit } from '@typebot.io/lib' -import { isReadTypebotForbidden } from '@/features/typebot/helpers/isReadTypebotForbidden' -import { archiveResults } from '@typebot.io/lib/api/helpers/archiveResults' -import { migrateTypebotFromV3ToV4 } from '@typebot.io/lib/migrations/migrateTypebotFromV3ToV4' -import { isWriteTypebotForbidden } from '@/features/typebot/helpers/isWriteTypebotForbidden' - -// TODO: delete -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const user = await getAuthenticatedUser(req, res) - if (!user) return notAuthenticated(res) - - const typebotId = req.query.typebotId as string - if (req.method === 'GET') { - const fullTypebot = await prisma.typebot.findFirst({ - where: { - id: typebotId, - isArchived: { not: true }, - }, - include: { - publishedTypebot: true, - collaborators: { select: { userId: true, type: true } }, - webhooks: true, - }, - }) - if (!fullTypebot || (await isReadTypebotForbidden(fullTypebot, user))) - return res.status(404).send({ typebot: null }) - - const { publishedTypebot, collaborators, webhooks, ...typebot } = - fullTypebot - const isReadOnly = - collaborators.find((c) => c.userId === user.id)?.type === - CollaborationType.READ - return res.send({ - typebot: await migrateTypebot(typebot as Typebot), - publishedTypebot, - isReadOnly, - webhooks, - }) - } - - if (req.method === 'DELETE') { - const typebot = await prisma.typebot.findUnique({ - where: { - id: typebotId, - }, - select: { - id: true, - workspaceId: true, - groups: true, - collaborators: { - select: { - userId: true, - type: true, - }, - }, - }, - }) - if (!typebot || (await isWriteTypebotForbidden(typebot, user))) - return res.status(404).send({ typebot: null }) - const { success } = await archiveResults(prisma)({ - typebot: { - groups: typebot.groups as Group[], - }, - resultsFilter: { typebotId }, - }) - if (!success) return res.status(500).send({ success: false, error: '' }) - await prisma.publicTypebot.deleteMany({ - where: { typebotId }, - }) - const typebots = await prisma.typebot.updateMany({ - where: { id: typebotId }, - data: { isArchived: true, publicId: null, customDomain: null }, - }) - return res.send({ typebots }) - } - - if (req.method === 'PUT') { - const data = ( - typeof req.body === 'string' ? JSON.parse(req.body) : req.body - ) as Typebot - - const typebot = await prisma.typebot.findUnique({ - where: { - id: typebotId, - }, - select: { - id: true, - workspaceId: true, - groups: true, - updatedAt: true, - collaborators: { - select: { - userId: true, - type: true, - }, - }, - }, - }) - if (!typebot || (await isWriteTypebotForbidden(typebot, user))) - return res.status(404).send({ message: 'Typebot not found' }) - - if ( - (typebot.updatedAt as Date).getTime() > new Date(data.updatedAt).getTime() - ) - return res.send({ - message: 'Found newer version of the typebot in database', - }) - - const updates = { - ...omit(data, 'id', 'createdAt', 'updatedAt'), - theme: data.theme ?? undefined, - settings: data.settings ?? undefined, - resultsTablePreferences: data.resultsTablePreferences ?? undefined, - groups: data.groups ?? [], - variables: data.variables ?? [], - edges: data.edges ?? [], - } satisfies Prisma.TypebotUpdateInput - - try { - const updatedTypebot = await prisma.typebot.update({ - where: { id: typebotId }, - data: updates, - }) - return res.send({ typebot: updatedTypebot }) - } catch (err) { - if (err instanceof Prisma.PrismaClientKnownRequestError) { - if (err.code === 'P2002') { - return res.status(409).send({ - message: - err.meta && 'target' in err.meta && Array.isArray(err.meta.target) - ? `${err.meta.target[0]} already exists` - : 'Duplicate conflict', - }) - } - return res.status(500).send({ message: err.message }) - } - } - } - - if (req.method === 'PATCH') { - const typebot = await prisma.typebot.findUnique({ - where: { - id: typebotId, - }, - select: { - id: true, - workspaceId: true, - groups: true, - collaborators: { - select: { - userId: true, - type: true, - }, - }, - }, - }) - if (!typebot || (await isWriteTypebotForbidden(typebot, user))) - return res.status(404).send({ message: 'Typebot not found' }) - const data = typeof req.body === 'string' ? JSON.parse(req.body) : req.body - const updatedTypebot = await prisma.typebot.update({ - where: { id: typebotId }, - data, - }) - return res.send({ typebot: updatedTypebot }) - } - return methodNotAllowed(res) -} - -const migrateTypebot = async (typebot: Typebot): Promise => { - if (typebot.version === '4') return typebot - return migrateTypebotFromV3ToV4(prisma)(typebot) -} - -export default handler diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts b/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts deleted file mode 100644 index 0c1216c496b..00000000000 --- a/apps/builder/src/pages/api/typebots/[typebotId]/analytics/answersCount.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { PublicTypebot } from '@typebot.io/schemas' -import prisma from '@/lib/prisma' -import { NextApiRequest, NextApiResponse } from 'next' -import { methodNotAllowed, notAuthenticated } from '@typebot.io/lib/api' -import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser' -import { canReadTypebots } from '@/helpers/databaseRules' - -// TODO: Delete (deprecated) -const handler = async (req: NextApiRequest, res: NextApiResponse) => { - const user = await getAuthenticatedUser(req, res) - if (!user) return notAuthenticated(res) - if (req.method === 'GET') { - const typebotId = req.query.typebotId as string - const typebot = await prisma.typebot.findFirst({ - where: canReadTypebots(typebotId, user), - select: { publishedTypebot: true }, - }) - const publishedTypebot = - typebot?.publishedTypebot as unknown as PublicTypebot - if (!publishedTypebot) return res.status(404).send({ answersCounts: [] }) - const answersCounts = await prisma.answer.groupBy({ - by: ['groupId'], - where: { - groupId: { in: publishedTypebot.groups.map((g) => g.id) }, - }, - _count: { _all: true }, - }) - return res.status(200).send({ - answersCounts: answersCounts.map((answer) => ({ - groupId: answer.groupId, - totalAnswers: answer._count._all, - })), - }) - } - return methodNotAllowed(res) -} - -export default handler diff --git a/apps/docs/openapi/builder/_spec_.json b/apps/docs/openapi/builder/_spec_.json index 2f979f59049..f3005d6996e 100644 --- a/apps/docs/openapi/builder/_spec_.json +++ b/apps/docs/openapi/builder/_spec_.json @@ -3044,8 +3044,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -7327,8 +7326,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -11184,8 +11182,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -15176,8 +15173,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -19049,8 +19045,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -22976,8 +22971,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -26966,8 +26960,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } @@ -31143,7 +31136,6 @@ "operationId": "customDomains-createCustomDomain", "summary": "Create custom domain", "tags": [ - "Workspace", "Custom domains" ], "security": [ @@ -31218,7 +31210,6 @@ "operationId": "customDomains-deleteCustomDomain", "summary": "Delete custom domain", "tags": [ - "Workspace", "Custom domains" ], "security": [ @@ -31276,7 +31267,6 @@ "operationId": "customDomains-listCustomDomains", "summary": "List custom domains", "tags": [ - "Workspace", "Custom domains" ], "security": [ diff --git a/apps/docs/openapi/chat/_spec_.json b/apps/docs/openapi/chat/_spec_.json index 2eb00d4a693..82ede514f77 100644 --- a/apps/docs/openapi/chat/_spec_.json +++ b/apps/docs/openapi/chat/_spec_.json @@ -2621,8 +2621,7 @@ } }, "required": [ - "id", - "valueToExtract" + "id" ], "additionalProperties": false } diff --git a/packages/schemas/features/blocks/integrations/openai.ts b/packages/schemas/features/blocks/integrations/openai.ts index a9a87bc9f0f..02df5dcc8d5 100644 --- a/packages/schemas/features/blocks/integrations/openai.ts +++ b/packages/schemas/features/blocks/integrations/openai.ts @@ -80,7 +80,10 @@ const chatCompletionOptionsSchema = z responseMapping: z.array( z.object({ id: z.string(), - valueToExtract: z.enum(chatCompletionResponseValues), + valueToExtract: z.preprocess( + (val) => (!val ? 'Message content' : val), + z.enum(chatCompletionResponseValues) + ), variableId: z.string().optional(), }) ), diff --git a/packages/schemas/features/chat.ts b/packages/schemas/features/chat.ts index 76a435986d2..da27f8b52b2 100644 --- a/packages/schemas/features/chat.ts +++ b/packages/schemas/features/chat.ts @@ -21,7 +21,7 @@ import { BubbleBlockType } from './blocks/bubbles/enums' import { inputBlockSchemas } from './blocks/schemas' import { chatCompletionMessageSchema } from './blocks/integrations/openai' -const typebotInSessionStateSchema = publicTypebotSchema.pick({ +const typebotInSessionStateSchema = publicTypebotSchema._def.schema.pick({ id: true, groups: true, edges: true, @@ -131,7 +131,7 @@ const scriptToExecuteSchema = z.object({ ), }) -const startTypebotSchema = typebotSchema.pick({ +const startTypebotSchema = typebotSchema._def.schema.pick({ id: true, groups: true, edges: true, @@ -286,7 +286,7 @@ export const chatReplySchema = z.object({ .optional(), clientSideActions: z.array(clientSideActionSchema).optional(), sessionId: z.string().optional(), - typebot: typebotSchema + typebot: typebotSchema._def.schema .pick({ id: true, theme: true, settings: true }) .optional(), resultId: z.string().optional(), diff --git a/packages/schemas/features/publicTypebot.ts b/packages/schemas/features/publicTypebot.ts index 557d1e1af8d..46c3c7e9a99 100644 --- a/packages/schemas/features/publicTypebot.ts +++ b/packages/schemas/features/publicTypebot.ts @@ -8,22 +8,30 @@ import { typebotSchema, } from './typebot' import { z } from 'zod' +import { preprocessTypebot } from './typebot/helpers/preprocessTypebot' -export const publicTypebotSchema = z.object({ - id: z.string(), - version: z.enum(['3', '4', '5']).nullable(), - createdAt: z.date(), - updatedAt: z.date(), - typebotId: z.string(), - groups: z.array(groupSchema), - edges: z.array(edgeSchema), - variables: z.array(variableSchema), - theme: themeSchema, - settings: settingsSchema, -}) satisfies z.ZodType +export const publicTypebotSchema = z.preprocess( + preprocessTypebot, + z.object({ + id: z.string(), + version: z.enum(['3', '4', '5']).nullable(), + createdAt: z.date(), + updatedAt: z.date(), + typebotId: z.string(), + groups: z.array(groupSchema), + edges: z.array(edgeSchema), + variables: z.array(variableSchema), + theme: themeSchema, + settings: settingsSchema, + }) +) satisfies z.ZodType -const publicTypebotWithName = publicTypebotSchema.merge( - typebotSchema.pick({ name: true, isArchived: true, isClosed: true }) +const publicTypebotWithName = publicTypebotSchema._def.schema.merge( + typebotSchema._def.schema.pick({ + name: true, + isArchived: true, + isClosed: true, + }) ) export type PublicTypebot = z.infer diff --git a/packages/schemas/features/typebot/helpers/preprocessTypebot.ts b/packages/schemas/features/typebot/helpers/preprocessTypebot.ts new file mode 100644 index 00000000000..f6711c2528e --- /dev/null +++ b/packages/schemas/features/typebot/helpers/preprocessTypebot.ts @@ -0,0 +1,25 @@ +import { Block } from '../../blocks' +import { Group, edgeSchema } from '../typebot' + +export const preprocessTypebot = (typebot: any) => { + if (!typebot || typebot.version === '5') return typebot + return { + ...typebot, + groups: typebot.groups.map(preprocessGroup), + edges: typebot.edges?.filter( + (edge: any) => edgeSchema.safeParse(edge).success + ), + } +} + +const preprocessGroup = (group: Group) => ({ + ...group, + blocks: group.blocks.map((block) => + preprocessBlock(block, { groupId: group.id }) + ), +}) + +const preprocessBlock = (block: Block, { groupId }: { groupId: string }) => ({ + ...block, + groupId: block.groupId ?? groupId, +}) diff --git a/packages/schemas/features/typebot/typebot.ts b/packages/schemas/features/typebot/typebot.ts index bebd7ddf18b..0b01b222f35 100644 --- a/packages/schemas/features/typebot/typebot.ts +++ b/packages/schemas/features/typebot/typebot.ts @@ -4,6 +4,7 @@ import { themeSchema } from './theme' import { variableSchema } from './variable' import { Typebot as TypebotPrisma } from '@typebot.io/prisma' import { blockSchema } from '../blocks/schemas' +import { preprocessTypebot } from './helpers/preprocessTypebot' export const groupSchema = z.object({ id: z.string(), @@ -43,35 +44,38 @@ const isDomainNameWithPathNameCompatible = (str: string) => str ) -export const typebotSchema = z.object({ - version: z.enum(['3', '4', '5']).nullable(), - id: z.string(), - name: z.string(), - groups: z.array(groupSchema), - edges: z.array(edgeSchema), - variables: z.array(variableSchema), - theme: themeSchema, - selectedThemeTemplateId: z.string().nullable(), - settings: settingsSchema, - createdAt: z.date(), - updatedAt: z.date(), - icon: z.string().nullable(), - folderId: z.string().nullable(), - publicId: z - .string() - .refine((str) => /^[a-zA-Z0-9-.]+$/.test(str)) - .nullable(), - customDomain: z - .string() - .refine(isDomainNameWithPathNameCompatible) - .nullable(), - workspaceId: z.string(), - resultsTablePreferences: resultsTablePreferencesSchema.nullable(), - isArchived: z.boolean(), - isClosed: z.boolean(), -}) satisfies z.ZodType +export const typebotSchema = z.preprocess( + preprocessTypebot, + z.object({ + version: z.enum(['3', '4', '5']).nullable(), + id: z.string(), + name: z.string(), + groups: z.array(groupSchema), + edges: z.array(edgeSchema), + variables: z.array(variableSchema), + theme: themeSchema, + selectedThemeTemplateId: z.string().nullable(), + settings: settingsSchema, + createdAt: z.date(), + updatedAt: z.date(), + icon: z.string().nullable(), + folderId: z.string().nullable(), + publicId: z + .string() + .refine((str) => /^[a-zA-Z0-9-.]+$/.test(str)) + .nullable(), + customDomain: z + .string() + .refine(isDomainNameWithPathNameCompatible) + .nullable(), + workspaceId: z.string(), + resultsTablePreferences: resultsTablePreferencesSchema.nullable(), + isArchived: z.boolean(), + isClosed: z.boolean(), + }) satisfies z.ZodType +) -export const typebotCreateSchema = typebotSchema +export const typebotCreateSchema = typebotSchema._def.schema .pick({ name: true, icon: true,