diff --git a/apps/builder/src/pages/api/typebots/[typebotId]/results/[resultId]/blocks/[blockId]/[fileName].ts b/apps/builder/src/pages/api/typebots/[typebotId]/results/[resultId]/blocks/[blockId]/[fileName].ts new file mode 100644 index 0000000000..977abe7cda --- /dev/null +++ b/apps/builder/src/pages/api/typebots/[typebotId]/results/[resultId]/blocks/[blockId]/[fileName].ts @@ -0,0 +1,67 @@ +import { getAuthenticatedUser } from "@/features/auth/helpers/getAuthenticatedUser"; +import { + badRequest, + methodNotAllowed, + notAuthenticated, + notFound, +} from "@typebot.io/lib/api/utils"; +import { getFileTempUrl } from "@typebot.io/lib/s3/getFileTempUrl"; +import prisma from "@typebot.io/prisma"; +import { isReadTypebotForbidden } from "@typebot.io/typebot/helpers/isReadTypebotForbidden"; +import type { NextApiRequest, NextApiResponse } from "next"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "GET") { + const user = await getAuthenticatedUser(req, res); + if (!user) return notAuthenticated(res); + + const typebotId = req.query.typebotId as string; + const resultId = req.query.resultId as string; + const blockId = req.query.blockId as string; + const fileName = req.query.fileName as string; + + if (!fileName) return badRequest(res, "fileName missing not found"); + + const typebot = await prisma.typebot.findFirst({ + where: { + id: typebotId, + }, + select: { + whatsAppCredentialsId: true, + collaborators: { + select: { + userId: true, + }, + }, + workspace: { + select: { + id: true, + isSuspended: true, + isPastDue: true, + members: { + select: { + userId: true, + }, + }, + }, + }, + }, + }); + + if (!typebot?.workspace || (await isReadTypebotForbidden(typebot, user))) + return notFound(res, "Workspace not found"); + + if (!typebot) return notFound(res, "Typebot not found"); + + const tmpUrl = await getFileTempUrl({ + key: `private/workspaces/${typebot.workspace.id}/typebots/${typebotId}/results/${resultId}/blocks/${blockId}/${fileName}`, + }); + + if (!tmpUrl) return notFound(res, "File not found"); + + return res.redirect(tmpUrl); + } + return methodNotAllowed(res); +}; + +export default handler; diff --git a/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts b/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts index d5ff9b079c..a877d8810a 100644 --- a/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts +++ b/apps/viewer/src/features/fileUpload/api/generateUploadUrl.ts @@ -24,6 +24,7 @@ export const generateUploadUrl = publicProcedure .input( z.object({ sessionId: z.string(), + blockId: z.string(), fileName: z.string(), fileType: z.string().optional(), }), @@ -35,7 +36,7 @@ export const generateUploadUrl = publicProcedure fileUrl: z.string(), }), ) - .mutation(async ({ input: { fileName, sessionId, fileType } }) => { + .mutation(async ({ input: { fileName, sessionId, fileType, blockId } }) => { if (!env.S3_ENDPOINT || !env.S3_ACCESS_KEY || !env.S3_SECRET_KEY) throw new TRPCError({ code: "INTERNAL_SERVER_ERROR", @@ -100,8 +101,8 @@ export const generateUploadUrl = publicProcedure "workspaceId" in typebot && typebot.workspaceId && resultId ? `${visibility === "Private" ? "private" : "public"}/workspaces/${ typebot.workspaceId - }/typebots/${typebotId}/results/${resultId}/${fileName}` - : `public/tmp/${typebotId}/${fileName}`; + }/typebots/${typebotId}/results/${resultId}/blocks/${blockId}/${fileName}` + : `public/tmp/typebots/${typebotId}/blocks/${blockId}/${fileName}`; const presignedPostPolicy = await generatePresignedPostPolicy({ fileType, @@ -114,7 +115,7 @@ export const generateUploadUrl = publicProcedure formData: presignedPostPolicy.formData, fileUrl: visibility === "Private" && !isPreview - ? `${env.NEXTAUTH_URL}/api/typebots/${typebotId}/results/${resultId}/${fileName}` + ? `${env.NEXTAUTH_URL}/api/typebots/${typebotId}/results/${resultId}/blocks/${blockId}/${fileName}` : env.S3_PUBLIC_CUSTOM_DOMAIN ? `${env.S3_PUBLIC_CUSTOM_DOMAIN}/${filePath}` : `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`, diff --git a/packages/embeds/js/src/features/blocks/inputs/fileUpload/components/FileUploadForm.tsx b/packages/embeds/js/src/features/blocks/inputs/fileUpload/components/FileUploadForm.tsx index 8efef573c5..94273aa83d 100644 --- a/packages/embeds/js/src/features/blocks/inputs/fileUpload/components/FileUploadForm.tsx +++ b/packages/embeds/js/src/features/blocks/inputs/fileUpload/components/FileUploadForm.tsx @@ -70,6 +70,7 @@ export const FileUploadForm = (props: Props) => { file, input: { sessionId: props.context.sessionId, + blockId: props.block.id, fileName: file.name, }, }, @@ -104,6 +105,7 @@ export const FileUploadForm = (props: Props) => { file: file, input: { sessionId: props.context.sessionId, + blockId: props.block.id, fileName: files.some((f) => f.name === file.name) ? file.name + `-${index}` : file.name, 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 b1aa9b3b86..f70a3527fc 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 @@ -6,6 +6,7 @@ type UploadFileProps = { file: File; input: { sessionId: string; + blockId: string; fileName: string; }; }[]; @@ -39,6 +40,7 @@ export const uploadFiles = async ({ fileName: input.fileName, sessionId: input.sessionId, fileType: file.type, + blockId: input.blockId, }, }); diff --git a/packages/embeds/js/src/features/blocks/inputs/textInput/components/TextInput.tsx b/packages/embeds/js/src/features/blocks/inputs/textInput/components/TextInput.tsx index c375696e42..4514475a84 100644 --- a/packages/embeds/js/src/features/blocks/inputs/textInput/components/TextInput.tsx +++ b/packages/embeds/js/src/features/blocks/inputs/textInput/components/TextInput.tsx @@ -70,6 +70,7 @@ export const TextInput = (props: Props) => { files: selectedFiles().map((file) => ({ file: file, input: { + blockId: props.block.id, sessionId: props.context.sessionId, fileName: file.name, }, @@ -217,6 +218,7 @@ export const TextInput = (props: Props) => { { file: audioFile, input: { + blockId: props.block.id, sessionId: props.context.sessionId, fileName: audioFile.name, },