From c6b09f68440018d238372b578a4cfc83dd3fff86 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Sat, 23 Nov 2024 16:16:54 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20Fix=20retro=20compat=20?= =?UTF-8?q?and=20introduce=20new=20generateUpload=20route=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ateUploadUrl.ts => generateUploadUrlV1.ts} | 2 +- .../api/deprecated/generateUploadUrlV2.ts | 167 ++++++++++++++++++ .../fileUpload/api/generateUploadUrl.ts | 2 +- apps/viewer/src/helpers/server/appRouter.ts | 4 +- packages/embeds/js/package.json | 2 +- .../inputs/fileUpload/helpers/uploadFiles.ts | 2 +- packages/embeds/nextjs/package.json | 2 +- packages/embeds/react/package.json | 2 +- 8 files changed, 176 insertions(+), 7 deletions(-) rename apps/viewer/src/features/fileUpload/api/deprecated/{generateUploadUrl.ts => generateUploadUrlV1.ts} (99%) create mode 100644 apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV2.ts diff --git a/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrl.ts b/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV1.ts similarity index 99% rename from apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrl.ts rename to apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV1.ts index 7480fa20e5..c3fa4ef388 100644 --- a/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrl.ts +++ b/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV1.ts @@ -9,7 +9,7 @@ import { generatePresignedPostPolicy } from "@typebot.io/lib/s3/generatePresigne import prisma from "@typebot.io/prisma"; import { z } from "@typebot.io/zod"; -export const generateUploadUrl = publicProcedure +export const generateUploadUrlV1 = publicProcedure .meta({ openapi: { method: "POST", diff --git a/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV2.ts b/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV2.ts new file mode 100644 index 0000000000..0f9550df7d --- /dev/null +++ b/apps/viewer/src/features/fileUpload/api/deprecated/generateUploadUrlV2.ts @@ -0,0 +1,167 @@ +import { publicProcedure } from "@/helpers/server/trpc"; +import { TRPCError } from "@trpc/server"; +import { InputBlockType } from "@typebot.io/blocks-inputs/constants"; +import type { FileInputBlock } from "@typebot.io/blocks-inputs/file/schema"; +import type { TextInputBlock } from "@typebot.io/blocks-inputs/text/schema"; +import { getSession } from "@typebot.io/bot-engine/queries/getSession"; +import { env } from "@typebot.io/env"; +import { getBlockById } from "@typebot.io/groups/helpers"; +import { parseGroups } from "@typebot.io/groups/schemas"; +import { generatePresignedPostPolicy } from "@typebot.io/lib/s3/generatePresignedPostPolicy"; +import prisma from "@typebot.io/prisma"; +import type { Prisma } from "@typebot.io/prisma/types"; +import { z } from "@typebot.io/zod"; + +export const generateUploadUrlV2 = publicProcedure + .meta({ + openapi: { + method: "POST", + path: "/v2/generate-upload-url", + summary: "Generate upload URL", + description: "Used to upload anything from the client to S3 bucket", + }, + }) + .input( + z.object({ + sessionId: z.string(), + fileName: z.string(), + fileType: z.string().optional(), + }), + ) + .output( + z.object({ + presignedUrl: z.string(), + formData: z.record(z.string(), z.any()), + fileUrl: z.string(), + }), + ) + .mutation(async ({ input: { fileName, sessionId, fileType } }) => { + if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY) + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + "S3 not properly configured. Missing one of those variables: S3_ENDPOINT, S3_ACCESS_KEY, S3_SECRET_KEY", + }); + + const session = await getSession(sessionId); + + if (!session) + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Can't find session", + }); + + const typebotId = session.state.typebotsQueue[0].typebot.id; + + const isPreview = !session.state.typebotsQueue[0].resultId; + + const typebot = session.state.typebotsQueue[0].resultId + ? await getAndParsePublicTypebot( + session.state.typebotsQueue[0].typebot.id, + ) + : session.state.typebotsQueue[0].typebot; + + if (!typebot?.version) + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Can't find typebot", + }); + + if (session.state.currentBlockId === undefined) + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Can't find currentBlockId in session state", + }); + + const { block } = getBlockById( + session.state.currentBlockId, + parseGroups(typebot.groups, { + typebotVersion: typebot.version, + }), + ); + + if ( + block?.type !== InputBlockType.FILE && + (block.type !== InputBlockType.TEXT || + !block.options?.attachments?.isEnabled) && + (block.type !== InputBlockType.TEXT || + !block.options?.audioClip?.isEnabled) + ) + throw new TRPCError({ + code: "BAD_REQUEST", + message: "Current block does not expect file upload", + }); + + const { visibility, maxFileSize } = parseFileUploadParams(block); + + const resultId = session.state.typebotsQueue[0].resultId; + + const filePath = + "workspaceId" in typebot && typebot.workspaceId && resultId + ? `${visibility === "Private" ? "private" : "public"}/workspaces/${ + typebot.workspaceId + }/typebots/${typebotId}/results/${resultId}/${fileName}` + : `public/tmp/${typebotId}/${fileName}`; + + const presignedPostPolicy = await generatePresignedPostPolicy({ + fileType, + filePath, + maxFileSize, + }); + + return { + presignedUrl: presignedPostPolicy.postURL, + formData: presignedPostPolicy.formData, + fileUrl: + visibility === "Private" && !isPreview + ? `${env.NEXTAUTH_URL}/api/typebots/${typebotId}/results/${resultId}/${fileName}` + : env.S3_PUBLIC_CUSTOM_DOMAIN + ? `${env.S3_PUBLIC_CUSTOM_DOMAIN}/${filePath}` + : `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`, + }; + }); + +const getAndParsePublicTypebot = async (typebotId: string) => { + const publicTypebot = (await prisma.publicTypebot.findFirst({ + where: { + typebotId, + }, + select: { + version: true, + groups: true, + typebot: { + select: { + workspaceId: true, + }, + }, + }, + })) as (Prisma.PublicTypebot & { typebot: { workspaceId: string } }) | null; + + return { + ...publicTypebot, + workspaceId: publicTypebot?.typebot.workspaceId, + }; +}; + +const parseFileUploadParams = ( + block: FileInputBlock | TextInputBlock, +): { visibility: "Public" | "Private"; maxFileSize: number | undefined } => { + if (block.type === InputBlockType.FILE) { + return { + visibility: + block.options?.visibility === "Private" ? "Private" : "Public", + maxFileSize: + block.options && "sizeLimit" in block.options + ? (block.options.sizeLimit as number) + : env.NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE, + }; + } + + return { + visibility: + block.options?.attachments?.visibility === "Private" + ? "Private" + : "Public", + maxFileSize: env.NEXT_PUBLIC_BOT_FILE_UPLOAD_MAX_SIZE, + }; +}; diff --git a/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts b/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts index a877d8810a..2c2c3083b5 100644 --- a/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts +++ b/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts @@ -16,7 +16,7 @@ export const generateUploadUrl = publicProcedure .meta({ openapi: { method: "POST", - path: "/v2/generate-upload-url", + path: "/v3/generate-upload-url", summary: "Generate upload URL", description: "Used to upload anything from the client to S3 bucket", }, diff --git a/apps/viewer/src/helpers/server/appRouter.ts b/apps/viewer/src/helpers/server/appRouter.ts index 9f5a41dc4d..a6db50d68a 100644 --- a/apps/viewer/src/helpers/server/appRouter.ts +++ b/apps/viewer/src/helpers/server/appRouter.ts @@ -5,7 +5,8 @@ import { saveClientLogs } from "@/features/chat/api/saveClientLogs"; import { startChat } from "@/features/chat/api/startChat"; import { startChatPreview } from "@/features/chat/api/startChatPreview"; import { updateTypebotInSession } from "@/features/chat/api/updateTypebotInSession"; -import { generateUploadUrl as generateUploadUrlV1 } from "@/features/fileUpload/api/deprecated/generateUploadUrl"; +import { generateUploadUrlV1 } from "@/features/fileUpload/api/deprecated/generateUploadUrlV1"; +import { generateUploadUrlV2 } from "@/features/fileUpload/api/deprecated/generateUploadUrlV2"; import { getUploadUrl } from "@/features/fileUpload/api/deprecated/getUploadUrl"; import { generateUploadUrl } from "@/features/fileUpload/api/generateUploadUrl"; import { whatsAppRouter } from "@/features/whatsapp/api/router"; @@ -19,6 +20,7 @@ export const appRouter = router({ startChatPreview: startChatPreview, getUploadUrl, generateUploadUrlV1, + generateUploadUrlV2, generateUploadUrl, updateTypebotInSession, whatsAppRouter, diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index 89417feb05..724a59bbac 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/js", - "version": "0.3.31", + "version": "0.3.32", "description": "Javascript library to display typebots on your website", "license": "FSL-1.1-ALv2", "type": "module", diff --git a/packages/embeds/js/src/features/blocks/inputs/fileUpload/helpers/uploadFiles.ts b/packages/embeds/js/src/features/blocks/inputs/fileUpload/helpers/uploadFiles.ts index f70a3527fc..1bb0639257 100644 --- a/packages/embeds/js/src/features/blocks/inputs/fileUpload/helpers/uploadFiles.ts +++ b/packages/embeds/js/src/features/blocks/inputs/fileUpload/helpers/uploadFiles.ts @@ -35,7 +35,7 @@ export const uploadFiles = async ({ fileUrl: string; }>({ method: "POST", - url: `${apiHost}/api/v2/generate-upload-url`, + url: `${apiHost}/api/v3/generate-upload-url`, body: { fileName: input.fileName, sessionId: input.sessionId, diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json index 5698f00ac2..5ec1ff32a3 100644 --- a/packages/embeds/nextjs/package.json +++ b/packages/embeds/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/nextjs", - "version": "0.3.31", + "version": "0.3.32", "license": "FSL-1.1-ALv2", "description": "Convenient library to display typebots on your Next.js website", "type": "module", diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json index 27826b7b42..d9f2d2186d 100644 --- a/packages/embeds/react/package.json +++ b/packages/embeds/react/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/react", - "version": "0.3.31", + "version": "0.3.32", "description": "Convenient library to display typebots on your React app", "license": "FSL-1.1-ALv2", "type": "module",