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)
}