(preview) Add preview runtime dropdown

User can select between Web and API previews

Closes #247
This commit is contained in:
Baptiste Arnaud
2023-02-22 11:40:04 +01:00
parent a265143dc0
commit 3967e5f1d0
17 changed files with 464 additions and 206 deletions

View File

@@ -27,7 +27,7 @@ import {
setResultAsCompleted,
startBotFlow,
} from '../utils'
import { omit } from 'utils'
import { env, omit } from 'utils'
export const sendMessageProcedure = publicProcedure
.meta({
@@ -41,61 +41,63 @@ export const sendMessageProcedure = publicProcedure
})
.input(sendMessageInputSchema)
.output(chatReplySchema)
.query(async ({ input: { sessionId, message, startParams } }) => {
const session = sessionId ? await getSession(sessionId) : null
.query(
async ({ input: { sessionId, message, startParams }, ctx: { user } }) => {
const session = sessionId ? await getSession(sessionId) : null
if (!session) {
const {
sessionId,
typebot,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
} = await startSession(startParams)
return {
sessionId,
typebot: typebot
? {
id: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
}
: undefined,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
}
} else {
const { messages, input, clientSideActions, newSessionState, logs } =
await continueBotFlow(session.state)(message)
if (!session) {
const {
sessionId,
typebot,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
} = await startSession(startParams, user?.id)
return {
sessionId,
typebot: typebot
? {
id: typebot.id,
theme: typebot.theme,
settings: typebot.settings,
}
: undefined,
messages,
input,
resultId,
dynamicTheme,
logs,
clientSideActions,
}
} else {
const { messages, input, clientSideActions, newSessionState, logs } =
await continueBotFlow(session.state)(message)
await prisma.chatSession.updateMany({
where: { id: session.id },
data: {
state: newSessionState,
},
})
await prisma.chatSession.updateMany({
where: { id: session.id },
data: {
state: newSessionState,
},
})
if (!input && session.state.result?.hasStarted)
await setResultAsCompleted(session.state.result.id)
if (!input && session.state.result?.hasStarted)
await setResultAsCompleted(session.state.result.id)
return {
messages,
input,
clientSideActions,
dynamicTheme: parseDynamicThemeReply(newSessionState),
logs,
return {
messages,
input,
clientSideActions,
dynamicTheme: parseDynamicThemeReply(newSessionState),
logs,
}
}
}
})
)
const startSession = async (startParams?: StartParams) => {
const startSession = async (startParams?: StartParams, userId?: string) => {
if (!startParams?.typebot)
throw new TRPCError({
code: 'BAD_REQUEST',
@@ -105,7 +107,7 @@ const startSession = async (startParams?: StartParams) => {
const isPreview =
startParams?.isPreview || typeof startParams?.typebot !== 'string'
const typebot = await getTypebot(startParams)
const typebot = await getTypebot(startParams, userId)
const startVariables = startParams.prefilledVariables
? parsePrefilledVariables(typebot.variables, startParams.prefilledVariables)
@@ -198,14 +200,16 @@ const startSession = async (startParams?: StartParams) => {
} satisfies ChatReply
}
const getTypebot = async ({
typebot,
isPreview,
}: Pick<StartParams, 'typebot' | 'isPreview'>): Promise<StartTypebot> => {
const getTypebot = async (
{ typebot, isPreview }: Pick<StartParams, 'typebot' | 'isPreview'>,
userId?: string
): Promise<StartTypebot> => {
if (typeof typebot !== 'string') return typebot
if (isPreview && !userId && env('E2E_TEST') !== 'true')
throw new TRPCError({ code: 'NOT_FOUND', message: 'Typebot not found' })
const typebotQuery = isPreview
? await prisma.typebot.findUnique({
where: { id: typebot },
? await prisma.typebot.findFirst({
where: { id: typebot, workspace: { members: { some: { userId } } } },
select: {
id: true,
groups: true,

View File

@@ -3,12 +3,14 @@ import { captureException } from '@sentry/nextjs'
import { createOpenApiNextHandler } from 'trpc-openapi'
import cors from 'nextjs-cors'
import { NextApiRequest, NextApiResponse } from 'next'
import { createContext } from '@/utils/server/context'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
await cors(req, res)
return createOpenApiNextHandler({
router: appRouter,
createContext,
onError({ error }) {
if (error.code === 'INTERNAL_SERVER_ERROR') {
captureException(error)

View File

@@ -0,0 +1,35 @@
import prisma from '@/lib/prisma'
import { inferAsyncReturnType } from '@trpc/server'
import * as trpcNext from '@trpc/server/adapters/next'
import { User } from 'db'
import { NextApiRequest } from 'next'
export async function createContext(opts: trpcNext.CreateNextContextOptions) {
const user = await getAuthenticatedUser(opts.req)
return {
user,
}
}
const getAuthenticatedUser = async (
req: NextApiRequest
): Promise<User | undefined> => {
const bearerToken = extractBearerToken(req)
if (!bearerToken) return
return authenticateByToken(bearerToken)
}
const authenticateByToken = async (
apiToken: string
): Promise<User | undefined> => {
if (typeof window !== 'undefined') return
return (await prisma.user.findFirst({
where: { apiTokens: { some: { token: apiToken } } },
})) as User
}
const extractBearerToken = (req: NextApiRequest) =>
req.headers['authorization']?.slice(7)
export type Context = inferAsyncReturnType<typeof createContext>

View File

@@ -1,13 +1,22 @@
import { initTRPC } from '@trpc/server'
import { OpenApiMeta } from 'trpc-openapi'
import superjson from 'superjson'
import { Context } from './context'
const t = initTRPC.meta<OpenApiMeta>().create({
const t = initTRPC.context<Context>().meta<OpenApiMeta>().create({
transformer: superjson,
})
const injectUser = t.middleware(({ next, ctx }) => {
return next({
ctx: {
user: ctx.user,
},
})
})
export const middleware = t.middleware
export const router = t.router
export const publicProcedure = t.procedure
export const publicProcedure = t.procedure.use(injectUser)