From 8104c6d3a650438c6b8ba30583ab472c23a4f6ed Mon Sep 17 00:00:00 2001 From: Colin Regourd Date: Wed, 26 Jun 2024 16:30:46 +0200 Subject: [PATCH] Create a new object for upload --- apps/docs/pages/docs/api-docs.mdx | 6 +++--- apps/example/options.tsx | 2 +- apps/example/pageRouterOptions.tsx | 2 +- packages/next-admin/src/types.ts | 12 +++++++++++- packages/next-admin/src/utils/server.ts | 25 ++++++++++++++++--------- packages/next-admin/src/utils/tools.ts | 13 +++++++++++++ 6 files changed, 45 insertions(+), 15 deletions(-) diff --git a/apps/docs/pages/docs/api-docs.mdx b/apps/docs/pages/docs/api-docs.mdx index e10b236b..951e5875 100644 --- a/apps/docs/pages/docs/api-docs.mdx +++ b/apps/docs/pages/docs/api-docs.mdx @@ -290,7 +290,7 @@ For the `edit` property, it can take the following: | `input` | a React Element that should receive [CustomInputProps](#custominputprops). For App Router, this element must be a client component. | | `handler` | an object that can take the following properties | | `handler.get` | a function that takes the field value as a parameter and returns a transformed value displayed in the form | -| `handler.upload` | an async function that is used only for formats `file` and `data-url`. It takes a File object as parameter and must return a string. Useful to upload a file to a remote provider | +| `handler.upload` | an async function that is used only for formats `file` and `data-url`. It takes a Buffer object and and informations object containing `name` and `type` property as parameter and must return a string. Useful to upload a file to a remote provider | | `handler.uploadErrorMessage` | an optional string displayed in the input field as an error message in case of a failure during the upload handler | | `optionFormatter` | only for relation fields, a function that takes the field values as a parameter and returns a string. Useful to display your record in related list | | `tooltip` | a tooltip content to show for the field | @@ -299,7 +299,7 @@ For the `edit` property, it can take the following: | `display` | only for relation fields, indicate which display format to use between `list`, `table` or `select`. Default `select` | | `required` | a true value to force a field to be required in the form, note that if the field is required by the Prisma schema, you cannot set `required` to false | | `relationOptionFormatter` | same as `optionFormatter`, but used to format data that comes from an [explicit many-to-many](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations/many-to-many-relations#explicit-many-to-many-relations) relationship. See [handling explicit many-to-many](/docs/code-snippets#explicit-many-to-many) | -| `orderField` | the field to use for relationship sorting. This allow to drag and drop the related records in the `list` display. | +| `orderField` | the field to use for relationship sorting. This allow to drag and drop the related records in the `list` display. | | `relationshipSearchField` | a field name of the explicit many-to-many relation table to apply the search on. See [handling explicit many-to-many](/docs/code-snippets#explicit-many-to-many) | ##### `filters` property @@ -430,7 +430,7 @@ const options: NextAdminOptions = { avatar: { format: "file", handler: { - upload: async (file: File) => { + upload: async (buffer, infos) => { return "https://www.gravatar.com/avatar/00000000000000000000000000000000"; }, }, diff --git a/apps/example/options.tsx b/apps/example/options.tsx index 15fca98f..1b459cfa 100644 --- a/apps/example/options.tsx +++ b/apps/example/options.tsx @@ -103,7 +103,7 @@ export const options: NextAdminOptions = { * for example you can upload the file to an S3 bucket. * Make sure to return a string. */ - upload: async (buffer: Buffer) => { + upload: async (buffer, infos) => { return "https://www.gravatar.com/avatar/00000000000000000000000000000000"; }, }, diff --git a/apps/example/pageRouterOptions.tsx b/apps/example/pageRouterOptions.tsx index c49dec04..54699343 100644 --- a/apps/example/pageRouterOptions.tsx +++ b/apps/example/pageRouterOptions.tsx @@ -78,7 +78,7 @@ export const options: NextAdminOptions = { * for example you can upload the file to an S3 bucket. * Make sure to return a string. */ - upload: async (file: Buffer) => { + upload: async (file, infos) => { return "https://www.gravatar.com/avatar/00000000000000000000000000000000"; }, }, diff --git a/packages/next-admin/src/types.ts b/packages/next-admin/src/types.ts index ca668dea..a9d08715 100644 --- a/packages/next-admin/src/types.ts +++ b/packages/next-admin/src/types.ts @@ -239,13 +239,23 @@ export type Handler< * @param file * @returns */ - upload?: (file: Buffer) => Promise; + upload?: (buffer: Buffer, infos: { + name: string; + type: string | null; + }) => Promise; /** * an optional string displayed in the input field as an error message in case of a failure during the upload handler. */ uploadErrorMessage?: string; }; +export type UploadParameters = Parameters<(buffer: Buffer, infos: { + name: string; + type: string | null; +}) => Promise> + + + export type RichTextFormat = "html" | "json"; export type FormatOptions = T extends string diff --git a/packages/next-admin/src/utils/server.ts b/packages/next-admin/src/utils/server.ts index 1cc6bb10..8180f38a 100644 --- a/packages/next-admin/src/utils/server.ts +++ b/packages/next-admin/src/utils/server.ts @@ -15,8 +15,9 @@ import { ObjectField, ScalarField, Schema, + UploadParameters, } from "../types"; -import { isNativeFunction, pipe } from "./tools"; +import { isNativeFunction, isUploadParameters, pipe } from "./tools"; export const models: readonly Prisma.DMMF.Model[] = Prisma.dmmf.datamodel .models as Prisma.DMMF.Model[]; @@ -757,7 +758,7 @@ export const formattedFormData = async ( ["data-url", "file"].includes( editOptions?.[dmmfPropertyName]?.format ?? "" ) && - formData[dmmfPropertyName] instanceof Buffer + isUploadParameters(formData[dmmfPropertyName]) ) { const uploadHandler = editOptions?.[dmmfPropertyName]?.handler?.upload; @@ -771,7 +772,7 @@ export const formattedFormData = async ( } else { try { const uploadResult = await uploadHandler( - formData[dmmfPropertyName] as unknown as Buffer + ...(formData[dmmfPropertyName] as unknown as UploadParameters) ); if (typeof uploadResult !== "string") { console.warn( @@ -1002,9 +1003,9 @@ export const getFormDataValues = async (req: IncomingMessage) => { }); }, }); - return new Promise>( + return new Promise>( (resolve, reject) => { - const files = {} as Record; + const files = {} as Record; form.on("fileBegin", (name, file) => { // @ts-expect-error @@ -1019,7 +1020,10 @@ export const getFormDataValues = async (req: IncomingMessage) => { if (!file.originalFilename) { files[name] = [null]; } else { - files[name] = [new File([Buffer.concat(chunks)], file.originalFilename)]; + files[name] = [[Buffer.concat(chunks), { + name: file.originalFilename, + type: file.mimetype, + }]]; } callback(); }, @@ -1038,7 +1042,7 @@ export const getFormDataValues = async (req: IncomingMessage) => { } return acc; }, - {} as Record + {} as Record ); resolve(joinedFormData); }); @@ -1055,7 +1059,7 @@ export const getFormValuesFromFormData = async (formData: FormData) => { tmpFormValues[key] = val; }); - const formValues = {} as Record; + const formValues = {} as Record; await Promise.allSettled( Object.entries(tmpFormValues).map(async ([key, value]) => { @@ -1066,7 +1070,10 @@ export const getFormValuesFromFormData = async (formData: FormData) => { return; } const buffer = await file.arrayBuffer(); - formValues[key] = Buffer.from(buffer); + formValues[key] = [Buffer.from(buffer), { + name: file.name, + type: file.type, + }]; } else { formValues[key] = value as string; } diff --git a/packages/next-admin/src/utils/tools.ts b/packages/next-admin/src/utils/tools.ts index 333ab13a..d14c03da 100644 --- a/packages/next-admin/src/utils/tools.ts +++ b/packages/next-admin/src/utils/tools.ts @@ -1,3 +1,5 @@ +import { UploadParameters } from "../types"; + export const capitalize = (str: T): Capitalize => { let capitalizedStr = str.charAt(0).toLocaleUpperCase() + str.slice(1); return capitalizedStr as Capitalize; @@ -63,3 +65,14 @@ export const formatLabel = (label: string) => { return capitalize(spacedLabel.toLowerCase()); }; + +//Create a function that check if object satifies UploadParameters +export const isUploadParameters = (obj: any): obj is UploadParameters => { + return ( + obj.length === 2 && + Buffer.isBuffer(obj[0]) && + typeof obj[1] === 'object' && + 'name' in obj[1] && + 'type' in obj[1] + ); +} \ No newline at end of file