2
0

feat(bot): ️ Add custom file upload size limit

This commit is contained in:
Baptiste Arnaud
2022-06-21 16:53:45 +02:00
parent 1931a5c9c0
commit ea765640cf
17 changed files with 141 additions and 44 deletions

View File

@@ -53,6 +53,7 @@ export const InputChatBlock = ({
groupId: block.groupId,
content: value,
variableId: variableId ?? null,
uploadedFiles: block.type === InputBlockType.FILE,
})
if (!isEditting) onTransitionEnd({ label, value, itemId }, isRetry)
setIsEditting(false)

View File

@@ -11,15 +11,17 @@ type Props = {
onSubmit: (url: InputSubmitContent) => void
}
const tenMB = 10 * 1024 * 1024
export const FileUploadForm = ({
block: {
id,
options: { isMultipleAllowed, labels },
options: { isMultipleAllowed, labels, sizeLimit },
},
onSubmit,
}: Props) => {
const { isPreview } = useTypebot()
const {
isPreview,
typebot: { typebotId },
} = useTypebot()
const { resultId } = useAnswers()
const [selectedFiles, setSelectedFiles] = useState<File[]>([])
const [isUploading, setIsUploading] = useState(false)
@@ -35,8 +37,8 @@ export const FileUploadForm = ({
const onNewFiles = (files: FileList) => {
setErrorMessage(undefined)
const newFiles = Array.from(files)
if (newFiles.some((file) => file.size > tenMB))
return setErrorMessage('A file is larger than 10MB')
if (newFiles.some((file) => file.size > (sizeLimit ?? 10) * 1024 * 1024))
return setErrorMessage(`A file is larger than ${sizeLimit ?? 10}MB`)
if (!isMultipleAllowed && files) return startSingleFileUpload(newFiles[0])
setSelectedFiles([...selectedFiles, ...newFiles])
}
@@ -55,6 +57,7 @@ export const FileUploadForm = ({
})
setIsUploading(true)
const urls = await uploadFiles({
basePath: `/api/typebots/${typebotId}/blocks/${id}`,
files: [
{
file,
@@ -76,6 +79,7 @@ export const FileUploadForm = ({
})
setIsUploading(true)
const urls = await uploadFiles({
basePath: `/api/typebots/${typebotId}/blocks/${id}`,
files: files.map((file) => ({
file: file,
path: `public/results/${resultId}/${id}/${file.name}`,

View File

@@ -33,7 +33,7 @@ export type TypebotViewerProps = {
startGroupId?: string
isLoading?: boolean
onNewGroupVisible?: (edge: Edge) => void
onNewAnswer?: (answer: Answer) => Promise<void>
onNewAnswer?: (answer: Answer & { uploadedFiles: boolean }) => Promise<void>
onNewLog?: (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) => void
onCompleted?: () => void
onVariablesUpdated?: (variables: VariableWithValue[]) => void
@@ -64,7 +64,8 @@ export const TypebotViewer = ({
const handleNewGroupVisible = (edge: Edge) =>
onNewGroupVisible && onNewGroupVisible(edge)
const handleNewAnswer = (answer: Answer) => onNewAnswer && onNewAnswer(answer)
const handleNewAnswer = (answer: Answer & { uploadedFiles: boolean }) =>
onNewAnswer && onNewAnswer(answer)
const handleNewLog = (log: Omit<Log, 'id' | 'createdAt' | 'resultId'>) =>
onNewLog && onNewLog(log)

View File

@@ -4,7 +4,9 @@ import React, { createContext, ReactNode, useContext, useState } from 'react'
const answersContext = createContext<{
resultId?: string
resultValues: ResultValues
addAnswer: (answer: Answer) => Promise<void> | undefined
addAnswer: (
answer: Answer & { uploadedFiles: boolean }
) => Promise<void> | undefined
updateVariables: (variables: VariableWithValue[]) => void
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
@@ -17,7 +19,9 @@ export const AnswersContext = ({
onVariablesUpdated,
}: {
resultId?: string
onNewAnswer: (answer: Answer) => Promise<void> | undefined
onNewAnswer: (
answer: Answer & { uploadedFiles: boolean }
) => Promise<void> | undefined
onVariablesUpdated?: (variables: VariableWithValue[]) => void
children: ReactNode
}) => {
@@ -27,7 +31,7 @@ export const AnswersContext = ({
createdAt: new Date().toISOString(),
})
const addAnswer = (answer: Answer) => {
const addAnswer = (answer: Answer & { uploadedFiles: boolean }) => {
setResultValues((resultValues) => ({
...resultValues,
answers: [...resultValues.answers, answer],

View File

@@ -0,0 +1,22 @@
/*
Warnings:
- Made the column `groups` on table `PublicTypebot` required. This step will fail if there are existing NULL values in that column.
- Made the column `edges` on table `PublicTypebot` required. This step will fail if there are existing NULL values in that column.
- Made the column `groups` on table `Typebot` required. This step will fail if there are existing NULL values in that column.
- Made the column `edges` on table `Typebot` required. This step will fail if there are existing NULL values in that column.
*/
-- AlterTable
ALTER TABLE "Answer" ADD COLUMN "storageUsed" INTEGER;
-- AlterTable
ALTER TABLE "PublicTypebot" ALTER COLUMN "groups" SET NOT NULL,
ALTER COLUMN "edges" SET NOT NULL;
-- AlterTable
ALTER TABLE "Result" ADD COLUMN "hasStarted" BOOLEAN;
-- AlterTable
ALTER TABLE "Typebot" ALTER COLUMN "groups" SET NOT NULL,
ALTER COLUMN "edges" SET NOT NULL;

View File

@@ -112,15 +112,15 @@ enum GraphNavigation {
}
model CustomDomain {
name String @id
createdAt DateTime @default(now())
name String @id
createdAt DateTime @default(now())
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
}
model Credentials {
id String @id @default(cuid())
createdAt DateTime @default(now())
id String @id @default(cuid())
createdAt DateTime @default(now())
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
data String // Encrypted data
@@ -155,7 +155,7 @@ model DashboardFolder {
childrenFolder DashboardFolder[] @relation("ParentChild")
typebots Typebot[]
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
}
model Typebot {
@@ -180,7 +180,7 @@ model Typebot {
invitations Invitation[]
webhooks Webhook[]
workspaceId String
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)
}
model Invitation {
@@ -210,16 +210,16 @@ enum CollaborationType {
}
model PublicTypebot {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
typebotId String @unique
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
groups Json
variables Json[]
edges Json
theme Json
settings Json
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
typebotId String @unique
typebot Typebot @relation(fields: [typebotId], references: [id], onDelete: Cascade)
groups Json
variables Json[]
edges Json
theme Json
settings Json
}
model Result {
@@ -231,6 +231,7 @@ model Result {
answers Answer[]
variables Json[]
isCompleted Boolean
hasStarted Boolean?
logs Log[]
}
@@ -248,10 +249,11 @@ model Answer {
createdAt DateTime @default(now())
resultId String
result Result @relation(fields: [resultId], references: [id], onDelete: Cascade)
blockId String
blockId String
groupId String
variableId String?
content String
storageUsed Int?
@@unique([resultId, blockId, groupId])
}

View File

@@ -1,6 +1,9 @@
import { Answer as AnswerFromPrisma } from 'db'
export type Answer = Omit<AnswerFromPrisma, 'resultId' | 'createdAt'>
export type Answer = Omit<
AnswerFromPrisma,
'resultId' | 'createdAt' | 'storageUsed'
> & { storageUsed?: number }
export type Stats = {
totalViews: number

View File

@@ -8,6 +8,7 @@ export const fileInputOptionsSchema = optionBaseSchema.and(
placeholder: z.string(),
button: z.string(),
}),
sizeLimit: z.number().optional(),
})
)

View File

@@ -3,14 +3,16 @@ import { config, Endpoint, S3 } from 'aws-sdk'
type GeneratePresignedUrlProps = {
filePath: string
fileType: string
sizeLimit?: number
}
const tenMB = 10485760
const tenMB = 10 * 1024 * 1024
const oneHundredAndTwentySeconds = 120
export const generatePresignedUrl = ({
filePath,
fileType,
sizeLimit = tenMB,
}: GeneratePresignedUrlProps): S3.PresignedPost => {
if (
!process.env.S3_ENDPOINT ||
@@ -45,7 +47,7 @@ export const generatePresignedUrl = ({
'Content-Type': fileType,
},
Expires: oneHundredAndTwentySeconds,
Conditions: [['content-length-range', 0, tenMB]],
Conditions: [['content-length-range', 0, sizeLimit]],
})
return presignedUrl
}

View File

@@ -193,6 +193,7 @@ export const generateId = (idDesiredLength: number): string => {
}
type UploadFileProps = {
basePath?: string
files: {
file: File
path: string
@@ -202,6 +203,7 @@ type UploadFileProps = {
type UrlList = string[]
export const uploadFiles = async ({
basePath = '/api',
files,
onUploadProgress,
}: UploadFileProps): Promise<UrlList> => {
@@ -209,9 +211,9 @@ export const uploadFiles = async ({
const { data } = await sendRequest<{
presignedUrl: { url: string; fields: any }
}>(
`/api/storage/upload-url?filePath=${encodeURIComponent(path)}&fileType=${
file.type
}`
`${basePath}/storage/upload-url?filePath=${encodeURIComponent(
path
)}&fileType=${file.type}`
)
if (!data?.presignedUrl) return null