⚡ (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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user