🚸 Improve parsing preprocessing on typebots
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
import { Webhook } from '@typebot.io/schemas'
|
|
||||||
import { sendRequest } from '@typebot.io/lib'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
typebotId: string
|
|
||||||
data: Partial<Omit<Webhook, 'typebotId'>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createWebhookQuery = ({ typebotId, data }: Props) =>
|
|
||||||
sendRequest<{ webhook: Webhook }>({
|
|
||||||
method: 'POST',
|
|
||||||
url: `/api/typebots/${typebotId}/webhooks`,
|
|
||||||
body: { data },
|
|
||||||
})
|
|
||||||
@@ -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<Webhook | undefined> => {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { Webhook } from '@typebot.io/schemas'
|
|
||||||
import { sendRequest } from '@typebot.io/lib'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
typebotId: string
|
|
||||||
webhookId: string
|
|
||||||
data: Partial<Omit<Webhook, 'id' | 'typebotId'>>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const updateWebhookQuery = ({ typebotId, webhookId, data }: Props) =>
|
|
||||||
sendRequest<{ webhook: Webhook }>({
|
|
||||||
method: 'PATCH',
|
|
||||||
url: `/api/typebots/${typebotId}/webhooks/${webhookId}`,
|
|
||||||
body: { data },
|
|
||||||
})
|
|
||||||
@@ -24,7 +24,7 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
.output(
|
.output(
|
||||||
z.object({
|
z.object({
|
||||||
typebots: z.array(
|
typebots: z.array(
|
||||||
typebotSchema.pick({
|
typebotSchema._def.schema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
groups: true,
|
groups: true,
|
||||||
variables: true,
|
variables: true,
|
||||||
@@ -58,7 +58,7 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebot found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'No typebot found' })
|
||||||
|
|
||||||
const linkedTypebotIds =
|
const linkedTypebotIds =
|
||||||
typebotSchema.shape.groups
|
typebotSchema._def.schema.shape.groups
|
||||||
.parse(typebot.groups)
|
.parse(typebot.groups)
|
||||||
.flatMap((group) => group.blocks)
|
.flatMap((group) => group.blocks)
|
||||||
.reduce<string[]>(
|
.reduce<string[]>(
|
||||||
@@ -102,8 +102,10 @@ export const getLinkedTypebots = authenticatedProcedure
|
|||||||
})
|
})
|
||||||
.map((typebot) => ({
|
.map((typebot) => ({
|
||||||
...typebot,
|
...typebot,
|
||||||
groups: typebotSchema.shape.groups.parse(typebot.groups),
|
groups: typebotSchema._def.schema.shape.groups.parse(typebot.groups),
|
||||||
variables: typebotSchema.shape.variables.parse(typebot.variables),
|
variables: typebotSchema._def.schema.shape.variables.parse(
|
||||||
|
typebot.variables
|
||||||
|
),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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 }),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -33,9 +33,7 @@ test('should not be able to submit taken url ID', async ({ page }) => {
|
|||||||
await page.getByRole('textbox').press('Enter')
|
await page.getByRole('textbox').press('Enter')
|
||||||
await expect(
|
await expect(
|
||||||
page
|
page
|
||||||
.getByText(
|
.getByText('Can only contain lowercase letters, numbers and dashes.')
|
||||||
'Should contain only contain letters, numbers. Words can be separated by dashes.'
|
|
||||||
)
|
|
||||||
.nth(0)
|
.nth(0)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.getByText(`${typebotId}-public`).click()
|
await page.getByText(`${typebotId}-public`).click()
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { parseInvalidTypebot } from '@/features/typebot/helpers/parseInvalidTypebot'
|
|
||||||
import { useToast } from '@/hooks/useToast'
|
import { useToast } from '@/hooks/useToast'
|
||||||
import { Button, ButtonProps, chakra } from '@chakra-ui/react'
|
import { Button, ButtonProps, chakra } from '@chakra-ui/react'
|
||||||
import { Typebot, typebotCreateSchema } from '@typebot.io/schemas'
|
import { Typebot, typebotCreateSchema } from '@typebot.io/schemas'
|
||||||
@@ -19,9 +18,7 @@ export const ImportTypebotFromFileButton = ({
|
|||||||
const file = e.target.files[0]
|
const file = e.target.files[0]
|
||||||
const fileContent = await readFile(file)
|
const fileContent = await readFile(file)
|
||||||
try {
|
try {
|
||||||
const typebot = typebotCreateSchema.parse(
|
const typebot = typebotCreateSchema.parse(JSON.parse(fileContent))
|
||||||
parseInvalidTypebot(JSON.parse(fileContent))
|
|
||||||
)
|
|
||||||
onNewTypebot(typebot as Typebot)
|
onNewTypebot(typebot as Typebot)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import { TRPCError } from '@trpc/server'
|
|||||||
import { publicTypebotSchema } from '@typebot.io/schemas'
|
import { publicTypebotSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isReadTypebotForbidden } from '../helpers/isReadTypebotForbidden'
|
import { isReadTypebotForbidden } from '../helpers/isReadTypebotForbidden'
|
||||||
import { parseInvalidTypebot } from '../helpers/parseInvalidTypebot'
|
|
||||||
import { PublicTypebot } from '@typebot.io/schemas'
|
|
||||||
|
|
||||||
export const getPublishedTypebot = authenticatedProcedure
|
export const getPublishedTypebot = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -50,7 +48,7 @@ export const getPublishedTypebot = authenticatedProcedure
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedTypebot = publicTypebotSchema.parse(
|
const parsedTypebot = publicTypebotSchema.parse(
|
||||||
parseInvalidTypebot(existingTypebot.publishedTypebot as PublicTypebot)
|
existingTypebot.publishedTypebot
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import { TRPCError } from '@trpc/server'
|
|||||||
import { Typebot, typebotSchema } from '@typebot.io/schemas'
|
import { Typebot, typebotSchema } from '@typebot.io/schemas'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { isReadTypebotForbidden } from '../helpers/isReadTypebotForbidden'
|
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 { migrateTypebotFromV3ToV4 } from '@typebot.io/lib/migrations/migrateTypebotFromV3ToV4'
|
||||||
import { parseInvalidTypebot } from '../helpers/parseInvalidTypebot'
|
|
||||||
|
|
||||||
export const getTypebot = authenticatedProcedure
|
export const getTypebot = authenticatedProcedure
|
||||||
.meta({
|
.meta({
|
||||||
@@ -46,8 +43,8 @@ export const getTypebot = authenticatedProcedure
|
|||||||
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsedTypebot = await parseTypebot(
|
const parsedTypebot = await migrateTypebot(
|
||||||
omit(existingTypebot, 'collaborators')
|
typebotSchema.parse(existingTypebot)
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -66,10 +63,7 @@ export const getTypebot = authenticatedProcedure
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const parseTypebot = async (typebot: TypebotFromDb): Promise<Typebot> => {
|
const migrateTypebot = async (typebot: Typebot): Promise<Typebot> => {
|
||||||
const parsedTypebot = typebotSchema.parse(
|
if (['4', '5'].includes(typebot.version ?? '')) return typebot
|
||||||
typebot.version !== '5' ? parseInvalidTypebot(typebot as Typebot) : typebot
|
return migrateTypebotFromV3ToV4(prisma)(typebot)
|
||||||
)
|
|
||||||
if (['4', '5'].includes(parsedTypebot.version ?? '')) return parsedTypebot
|
|
||||||
return migrateTypebotFromV3ToV4(prisma)(parsedTypebot)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const listTypebots = authenticatedProcedure
|
|||||||
.output(
|
.output(
|
||||||
z.object({
|
z.object({
|
||||||
typebots: z.array(
|
typebots: z.array(
|
||||||
typebotSchema
|
typebotSchema._def.schema
|
||||||
.pick({
|
.pick({
|
||||||
name: true,
|
name: true,
|
||||||
icon: true,
|
icon: true,
|
||||||
|
|||||||
@@ -49,15 +49,21 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
version: existingTypebot.version,
|
version: existingTypebot.version,
|
||||||
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
|
edges: typebotSchema._def.schema.shape.edges.parse(
|
||||||
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
|
existingTypebot.edges
|
||||||
settings: typebotSchema.shape.settings.parse(
|
),
|
||||||
|
groups: typebotSchema._def.schema.shape.groups.parse(
|
||||||
|
existingTypebot.groups
|
||||||
|
),
|
||||||
|
settings: typebotSchema._def.schema.shape.settings.parse(
|
||||||
existingTypebot.settings
|
existingTypebot.settings
|
||||||
),
|
),
|
||||||
variables: typebotSchema.shape.variables.parse(
|
variables: typebotSchema._def.schema.shape.variables.parse(
|
||||||
existingTypebot.variables
|
existingTypebot.variables
|
||||||
),
|
),
|
||||||
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
|
theme: typebotSchema._def.schema.shape.theme.parse(
|
||||||
|
existingTypebot.theme
|
||||||
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
else
|
else
|
||||||
@@ -65,15 +71,21 @@ export const publishTypebot = authenticatedProcedure
|
|||||||
data: {
|
data: {
|
||||||
version: existingTypebot.version,
|
version: existingTypebot.version,
|
||||||
typebotId: existingTypebot.id,
|
typebotId: existingTypebot.id,
|
||||||
edges: typebotSchema.shape.edges.parse(existingTypebot.edges),
|
edges: typebotSchema._def.schema.shape.edges.parse(
|
||||||
groups: typebotSchema.shape.groups.parse(existingTypebot.groups),
|
existingTypebot.edges
|
||||||
settings: typebotSchema.shape.settings.parse(
|
),
|
||||||
|
groups: typebotSchema._def.schema.shape.groups.parse(
|
||||||
|
existingTypebot.groups
|
||||||
|
),
|
||||||
|
settings: typebotSchema._def.schema.shape.settings.parse(
|
||||||
existingTypebot.settings
|
existingTypebot.settings
|
||||||
),
|
),
|
||||||
variables: typebotSchema.shape.variables.parse(
|
variables: typebotSchema._def.schema.shape.variables.parse(
|
||||||
existingTypebot.variables
|
existingTypebot.variables
|
||||||
),
|
),
|
||||||
theme: typebotSchema.shape.theme.parse(existingTypebot.theme),
|
theme: typebotSchema._def.schema.shape.theme.parse(
|
||||||
|
existingTypebot.theme
|
||||||
|
),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const updateTypebot = authenticatedProcedure
|
|||||||
z.object({
|
z.object({
|
||||||
typebotId: z.string(),
|
typebotId: z.string(),
|
||||||
typebot: typebotCreateSchema.merge(
|
typebot: typebotCreateSchema.merge(
|
||||||
typebotSchema
|
typebotSchema._def.schema
|
||||||
.pick({
|
.pick({
|
||||||
isClosed: true,
|
isClosed: true,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -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<NewTypebotProps, 'groups'>
|
|
||||||
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
|
|
||||||
@@ -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<Typebot> => {
|
|
||||||
if (typebot.version === '4') return typebot
|
|
||||||
return migrateTypebotFromV3ToV4(prisma)(typebot)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default handler
|
|
||||||
@@ -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
|
|
||||||
@@ -3044,8 +3044,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -7327,8 +7326,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -11184,8 +11182,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -15176,8 +15173,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -19049,8 +19045,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -22976,8 +22971,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -26966,8 +26960,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -31143,7 +31136,6 @@
|
|||||||
"operationId": "customDomains-createCustomDomain",
|
"operationId": "customDomains-createCustomDomain",
|
||||||
"summary": "Create custom domain",
|
"summary": "Create custom domain",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Workspace",
|
|
||||||
"Custom domains"
|
"Custom domains"
|
||||||
],
|
],
|
||||||
"security": [
|
"security": [
|
||||||
@@ -31218,7 +31210,6 @@
|
|||||||
"operationId": "customDomains-deleteCustomDomain",
|
"operationId": "customDomains-deleteCustomDomain",
|
||||||
"summary": "Delete custom domain",
|
"summary": "Delete custom domain",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Workspace",
|
|
||||||
"Custom domains"
|
"Custom domains"
|
||||||
],
|
],
|
||||||
"security": [
|
"security": [
|
||||||
@@ -31276,7 +31267,6 @@
|
|||||||
"operationId": "customDomains-listCustomDomains",
|
"operationId": "customDomains-listCustomDomains",
|
||||||
"summary": "List custom domains",
|
"summary": "List custom domains",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Workspace",
|
|
||||||
"Custom domains"
|
"Custom domains"
|
||||||
],
|
],
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -2621,8 +2621,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id"
|
||||||
"valueToExtract"
|
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,10 @@ const chatCompletionOptionsSchema = z
|
|||||||
responseMapping: z.array(
|
responseMapping: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
valueToExtract: z.enum(chatCompletionResponseValues),
|
valueToExtract: z.preprocess(
|
||||||
|
(val) => (!val ? 'Message content' : val),
|
||||||
|
z.enum(chatCompletionResponseValues)
|
||||||
|
),
|
||||||
variableId: z.string().optional(),
|
variableId: z.string().optional(),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import { BubbleBlockType } from './blocks/bubbles/enums'
|
|||||||
import { inputBlockSchemas } from './blocks/schemas'
|
import { inputBlockSchemas } from './blocks/schemas'
|
||||||
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
|
import { chatCompletionMessageSchema } from './blocks/integrations/openai'
|
||||||
|
|
||||||
const typebotInSessionStateSchema = publicTypebotSchema.pick({
|
const typebotInSessionStateSchema = publicTypebotSchema._def.schema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
groups: true,
|
groups: true,
|
||||||
edges: true,
|
edges: true,
|
||||||
@@ -131,7 +131,7 @@ const scriptToExecuteSchema = z.object({
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
const startTypebotSchema = typebotSchema.pick({
|
const startTypebotSchema = typebotSchema._def.schema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
groups: true,
|
groups: true,
|
||||||
edges: true,
|
edges: true,
|
||||||
@@ -286,7 +286,7 @@ export const chatReplySchema = z.object({
|
|||||||
.optional(),
|
.optional(),
|
||||||
clientSideActions: z.array(clientSideActionSchema).optional(),
|
clientSideActions: z.array(clientSideActionSchema).optional(),
|
||||||
sessionId: z.string().optional(),
|
sessionId: z.string().optional(),
|
||||||
typebot: typebotSchema
|
typebot: typebotSchema._def.schema
|
||||||
.pick({ id: true, theme: true, settings: true })
|
.pick({ id: true, theme: true, settings: true })
|
||||||
.optional(),
|
.optional(),
|
||||||
resultId: z.string().optional(),
|
resultId: z.string().optional(),
|
||||||
|
|||||||
@@ -8,22 +8,30 @@ import {
|
|||||||
typebotSchema,
|
typebotSchema,
|
||||||
} from './typebot'
|
} from './typebot'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
|
import { preprocessTypebot } from './typebot/helpers/preprocessTypebot'
|
||||||
|
|
||||||
export const publicTypebotSchema = z.object({
|
export const publicTypebotSchema = z.preprocess(
|
||||||
id: z.string(),
|
preprocessTypebot,
|
||||||
version: z.enum(['3', '4', '5']).nullable(),
|
z.object({
|
||||||
createdAt: z.date(),
|
id: z.string(),
|
||||||
updatedAt: z.date(),
|
version: z.enum(['3', '4', '5']).nullable(),
|
||||||
typebotId: z.string(),
|
createdAt: z.date(),
|
||||||
groups: z.array(groupSchema),
|
updatedAt: z.date(),
|
||||||
edges: z.array(edgeSchema),
|
typebotId: z.string(),
|
||||||
variables: z.array(variableSchema),
|
groups: z.array(groupSchema),
|
||||||
theme: themeSchema,
|
edges: z.array(edgeSchema),
|
||||||
settings: settingsSchema,
|
variables: z.array(variableSchema),
|
||||||
}) satisfies z.ZodType<PrismaPublicTypebot>
|
theme: themeSchema,
|
||||||
|
settings: settingsSchema,
|
||||||
|
})
|
||||||
|
) satisfies z.ZodType<PrismaPublicTypebot, z.ZodTypeDef, unknown>
|
||||||
|
|
||||||
const publicTypebotWithName = publicTypebotSchema.merge(
|
const publicTypebotWithName = publicTypebotSchema._def.schema.merge(
|
||||||
typebotSchema.pick({ name: true, isArchived: true, isClosed: true })
|
typebotSchema._def.schema.pick({
|
||||||
|
name: true,
|
||||||
|
isArchived: true,
|
||||||
|
isClosed: true,
|
||||||
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
export type PublicTypebot = z.infer<typeof publicTypebotSchema>
|
export type PublicTypebot = z.infer<typeof publicTypebotSchema>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
@@ -4,6 +4,7 @@ import { themeSchema } from './theme'
|
|||||||
import { variableSchema } from './variable'
|
import { variableSchema } from './variable'
|
||||||
import { Typebot as TypebotPrisma } from '@typebot.io/prisma'
|
import { Typebot as TypebotPrisma } from '@typebot.io/prisma'
|
||||||
import { blockSchema } from '../blocks/schemas'
|
import { blockSchema } from '../blocks/schemas'
|
||||||
|
import { preprocessTypebot } from './helpers/preprocessTypebot'
|
||||||
|
|
||||||
export const groupSchema = z.object({
|
export const groupSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@@ -43,35 +44,38 @@ const isDomainNameWithPathNameCompatible = (str: string) =>
|
|||||||
str
|
str
|
||||||
)
|
)
|
||||||
|
|
||||||
export const typebotSchema = z.object({
|
export const typebotSchema = z.preprocess(
|
||||||
version: z.enum(['3', '4', '5']).nullable(),
|
preprocessTypebot,
|
||||||
id: z.string(),
|
z.object({
|
||||||
name: z.string(),
|
version: z.enum(['3', '4', '5']).nullable(),
|
||||||
groups: z.array(groupSchema),
|
id: z.string(),
|
||||||
edges: z.array(edgeSchema),
|
name: z.string(),
|
||||||
variables: z.array(variableSchema),
|
groups: z.array(groupSchema),
|
||||||
theme: themeSchema,
|
edges: z.array(edgeSchema),
|
||||||
selectedThemeTemplateId: z.string().nullable(),
|
variables: z.array(variableSchema),
|
||||||
settings: settingsSchema,
|
theme: themeSchema,
|
||||||
createdAt: z.date(),
|
selectedThemeTemplateId: z.string().nullable(),
|
||||||
updatedAt: z.date(),
|
settings: settingsSchema,
|
||||||
icon: z.string().nullable(),
|
createdAt: z.date(),
|
||||||
folderId: z.string().nullable(),
|
updatedAt: z.date(),
|
||||||
publicId: z
|
icon: z.string().nullable(),
|
||||||
.string()
|
folderId: z.string().nullable(),
|
||||||
.refine((str) => /^[a-zA-Z0-9-.]+$/.test(str))
|
publicId: z
|
||||||
.nullable(),
|
.string()
|
||||||
customDomain: z
|
.refine((str) => /^[a-zA-Z0-9-.]+$/.test(str))
|
||||||
.string()
|
.nullable(),
|
||||||
.refine(isDomainNameWithPathNameCompatible)
|
customDomain: z
|
||||||
.nullable(),
|
.string()
|
||||||
workspaceId: z.string(),
|
.refine(isDomainNameWithPathNameCompatible)
|
||||||
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
|
.nullable(),
|
||||||
isArchived: z.boolean(),
|
workspaceId: z.string(),
|
||||||
isClosed: z.boolean(),
|
resultsTablePreferences: resultsTablePreferencesSchema.nullable(),
|
||||||
}) satisfies z.ZodType<TypebotPrisma>
|
isArchived: z.boolean(),
|
||||||
|
isClosed: z.boolean(),
|
||||||
|
}) satisfies z.ZodType<TypebotPrisma, z.ZodTypeDef, unknown>
|
||||||
|
)
|
||||||
|
|
||||||
export const typebotCreateSchema = typebotSchema
|
export const typebotCreateSchema = typebotSchema._def.schema
|
||||||
.pick({
|
.pick({
|
||||||
name: true,
|
name: true,
|
||||||
icon: true,
|
icon: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user