2
0

(fileUpload) New visibility option: "Public", "Private" or "Auto" (#1196)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Introduced file visibility options for uploaded files, allowing users
to set files as public or private.
- Added a new API endpoint for retrieving temporary URLs for files,
enhancing file accessibility.
- Expanded file upload documentation to include information on file
visibility settings.
- Updated URL validation to support URLs with port numbers and
"http://localhost".
- **Enhancements**
- Improved media download functionality by replacing the `got` library
with a custom `downloadMedia` function.
- Enhanced bot flow continuation and session start logic to support a
wider range of reply types, including WhatsApp media messages.
- **Bug Fixes**
- Adjusted file path and URL construction in the `generateUploadUrl`
function to correctly reflect file visibility settings.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Baptiste Arnaud
2024-01-30 08:02:10 +01:00
committed by GitHub
parent 515fcafcd8
commit 6215cfbbaf
17 changed files with 305 additions and 76 deletions

View File

@@ -5,8 +5,12 @@ import React from 'react'
import { TextInput } from '@/components/inputs'
import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { defaultFileInputOptions } from '@typebot.io/schemas/features/blocks/inputs/file/constants'
import {
defaultFileInputOptions,
fileVisibilityOptions,
} from '@typebot.io/schemas/features/blocks/inputs/file/constants'
import { useTranslate } from '@tolgee/react'
import { DropdownList } from '@/components/DropdownList'
type Props = {
options: FileInputBlock['options']
@@ -37,6 +41,10 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
const updateSkipButtonLabel = (skip: string) =>
onOptionsChange({ ...options, labels: { ...options?.labels, skip } })
const updateVisibility = (
visibility: (typeof fileVisibilityOptions)[number]
) => onOptionsChange({ ...options, visibility })
return (
<Stack spacing={4}>
<SwitchWithLabel
@@ -91,6 +99,13 @@ export const FileInputSettings = ({ options, onOptionsChange }: Props) => {
onChange={updateSkipButtonLabel}
withVariableButton={false}
/>
<DropdownList
label="Visibility:"
moreInfoTooltip='This setting determines who can see the uploaded files. "Public" means that anyone who has the link can see the files. "Private" means that only a members of this workspace can see the files.'
currentItem={options?.visibility}
onItemSelect={updateVisibility}
items={fileVisibilityOptions}
/>
<Stack>
<FormLabel mb="0" htmlFor="variable">
{options?.isMultipleAllowed

View File

@@ -0,0 +1,66 @@
import prisma from '@typebot.io/lib/prisma'
import { NextApiRequest, NextApiResponse } from 'next'
import { getAuthenticatedUser } from '@/features/auth/helpers/getAuthenticatedUser'
import {
badRequest,
methodNotAllowed,
notAuthenticated,
notFound,
} from '@typebot.io/lib/api'
import { getFileTempUrl } from '@typebot.io/lib/s3/getFileTempUrl'
import { isReadTypebotForbidden } from '@/features/typebot/helpers/isReadTypebotForbidden'
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 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}/${fileName}`,
})
if (!tmpUrl) return notFound(res, 'File not found')
return res.redirect(tmpUrl)
}
return methodNotAllowed(res)
}
export default handler

View File

@@ -8,9 +8,8 @@ import {
} from '@typebot.io/lib/api'
import { isReadWorkspaceFobidden } from '@/features/workspace/helpers/isReadWorkspaceFobidden'
import { WhatsAppCredentials } from '@typebot.io/schemas/features/whatsapp'
import got from 'got'
import { decrypt } from '@typebot.io/lib/api/encryption/decrypt'
import { env } from '@typebot.io/env'
import { downloadMedia } from '@typebot.io/bot-engine/whatsapp/downloadMedia'
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === 'GET') {
@@ -61,25 +60,15 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
credentials.iv
)) as WhatsAppCredentials['data']
const { body } = await got.get({
url: `${env.WHATSAPP_CLOUD_API_URL}/v17.0/${mediaId}`,
headers: {
Authorization: `Bearer ${credentialsData.systemUserAccessToken}`,
},
const { file, mimeType } = await downloadMedia({
mediaId,
systemUserAccessToken: credentialsData.systemUserAccessToken,
})
const parsedBody = JSON.parse(body) as { url: string; mime_type: string }
const buffer = await got(parsedBody.url, {
headers: {
Authorization: `Bearer ${credentialsData.systemUserAccessToken}`,
},
}).buffer()
res.setHeader('Content-Type', parsedBody.mime_type)
res.setHeader('Content-Type', mimeType)
res.setHeader('Cache-Control', 'public, max-age=86400')
return res.send(buffer)
return res.send(file)
}
return methodNotAllowed(res)
}

View File

@@ -31,3 +31,11 @@ The placeholder accepts [HTML](https://en.wikipedia.org/wiki/HTML).
## Size limit
There is a 10MB fixed limit per uploaded file. If you want your respondents to upload larger files, you should ask them to upload their files to a cloud storage service (e.g. Google Drive, Dropbox, etc.) and share the link with you.
## Visibility
This option allows you to choose between generating public URLs for the uploaded files or keeping them private. If you choose to keep the files private, you will be able to see the file only if you are logged in to your Typebot account.
Note that if you choose to keep the files private, you will not be able to use the file URL with other blocks like Attachment in the Send email block or others. These services won't be able to read the files.
By default, this option is set to `Auto`. This means that the files will be public if uploaded from the web runtime but private if uploaded from the WhatsApp runtime.

View File

@@ -24342,6 +24342,14 @@
},
"sizeLimit": {
"type": "number"
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
}
@@ -27033,6 +27041,14 @@
"type": "string"
}
}
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
}

View File

@@ -7418,6 +7418,14 @@
},
"sizeLimit": {
"type": "number"
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
}
@@ -10534,6 +10542,14 @@
"type": "string"
}
}
},
"visibility": {
"type": "string",
"enum": [
"Auto",
"Public",
"Private"
]
}
}
}

View File

@@ -140,10 +140,6 @@ export const generateUploadUrl = publicProcedure
message: "Can't find workspaceId",
})
const resultId = session.state.typebotsQueue[0].resultId
const filePath = `public/workspaces/${workspaceId}/typebots/${typebotId}/results/${resultId}/${filePathProps.fileName}`
if (session.state.currentBlockId === undefined)
throw new TRPCError({
code: 'BAD_REQUEST',
@@ -163,6 +159,14 @@ export const generateUploadUrl = publicProcedure
message: "Can't find file upload block",
})
const resultId = session.state.typebotsQueue[0].resultId
const filePath = `${
fileUploadBlock.options?.visibility === 'Private' ? 'private' : 'public'
}/workspaces/${workspaceId}/typebots/${typebotId}/results/${resultId}/${
filePathProps.fileName
}`
const presignedPostPolicy = await generatePresignedPostPolicy({
fileType,
filePath,
@@ -175,8 +179,11 @@ export const generateUploadUrl = publicProcedure
return {
presignedUrl: presignedPostPolicy.postURL,
formData: presignedPostPolicy.formData,
fileUrl: env.S3_PUBLIC_CUSTOM_DOMAIN
? `${env.S3_PUBLIC_CUSTOM_DOMAIN}/${filePath}`
: `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`,
fileUrl:
fileUploadBlock.options?.visibility === 'Private'
? `${env.NEXTAUTH_URL}/api/typebots/${typebotId}/results/${resultId}/${filePathProps.fileName}`
: env.S3_PUBLIC_CUSTOM_DOMAIN
? `${env.S3_PUBLIC_CUSTOM_DOMAIN}/${filePath}`
: `${presignedPostPolicy.postURL}/${presignedPostPolicy.formData.key}`,
}
})