premieroctet / next-admin

▲ Full-featured admin for Next.js and Prisma
https://next-admin.js.org
MIT License
321 stars 19 forks source link

Incorrect operation of upload property in avatar field configuration in next-admin library #480

Open joriksp opened 3 weeks ago

joriksp commented 3 weeks ago

Problem description

When using the upload property in the avatar field configuration in the User model, a problem occurs: instead of the expected string returned from the upload handler, the actual file itself is sent in the user change form. This leads to incorrect data processing on the server and can cause errors when saving data.

Steps for playback

  1. Configure the User model with the avatar field as shown in the example:
    
    import { upload } from "@/lib/fileStorage";
    import type { NextAdminOptions } from "@premieroctet/next-admin";

const options: NextAdminOptions = { model: { User: { edit: { fields: { avatar: { format: "file", required: true, handler: { upload: async (buffer, { name, type }) => { const uploadedFile = await upload( new File([buffer], name) ); return uploadedFile.url || ""; }, }, }, }, }, }, } }

export default options;


2. Try to change the user by uploading a file for the avatar field.
3. Pay attention to the data sent to the server.

### Expected behavior
When uploading a file, the value returned from the upload handler (in this case, the file name) should be sent for the avatar field, not the file itself.

### Actual behavior
The file itself is sent to the server, not the file name, which causes problems with data processing.
![{58481F4E-24CF-44FA-9742-6001B9E6ECE7}](https://github.com/user-attachments/assets/abe9c264-6acb-4a2c-addd-66b9b4339395)

### More info
Next-admin library version: 6.1.6.
Nextjs version: 14.2.16.
Operating system: Windows 11
Browser: Chrome 130.0.6723.71.
no-need-to-be-anonymous commented 2 weeks ago

I have the same issue. Also, I cannot see any logs in the upload fn.

Next-admin library version: 6.1.6. Nextjs version: 14.2.16. Operating system: MacOS Sonoma 14.0 Browser: Chrome 130.0.6723.70

cregourd commented 2 weeks ago

Hi,

The upload function runs on the server side. You need to use it to process the uploading file and return a string (URL) that will be stored in your database and used to find the file when you display the form of the object.

When a form containing a file object is submitted, the client sends file data and information to the server, it catches this information and passes it to the upload function.

I don't quite understand your request, can you please give more details about your request to see if I'm missing something ?

joriksp commented 2 weeks ago

This is a sample NextAdminOptions code from my project:

const options: NextAdminOptions = {
   model: {
      User: {
         edit: {
            fields: {
               avatar: {
                  format: "file",
                  required: true,
                  handler: {
                     upload: async (buffer, { name, type }) => {
                        const uploadedFile = await upload(
                           new File([buffer], name)
                        );
                        return uploadedFile.url || "";
                     },
                  },
               },
            },
         },
      },
};

Here, a function inside handler.upload with the same name takes in a File and returns an object with a download reference. Here is its code:

export const upload = async (file: File) => {
   const formData = new FormData();
   formData.append("file", file);

   try {
      const response = await fetch(`${fsHost}/upload`, {
         method: "POST",
         body: formData,
      });

      if (!response.ok) {
         return {
            success: false,
            message: `Request failed, ${response.status}`,
         };
      }

      const data: UploadedFile = await response.json();

      return data;
   } catch (error) {
      return {
         success: false,
         message: `Request failed, ${error}`,
      };
   }
};

if you add any debugging (console.log('example')) to handler.upload, it will not be displayed in the console

cregourd commented 2 weeks ago

Hi @joriksp, To optimize performance and reduce computation, the form submits only dirty fields, if you don't modify or add a file, the function handler.upload won't run due to unmodified data. Perhaps this behavior explains why you don't see the log. It's not actually an issue but a default behavior I hope this explains your issue, if not, please let us know

joriksp commented 1 week ago

Hello, the problem is a bit of a misnomer. After selecting an image when I want to save new data the following error appears:

{8CC2B1AD-9650-4015-B83D-04571064DA62}

cregourd commented 1 week ago

What's the return type of your handler.upload function? This return type is passed to Prisma in your field, which appears to be named image.

This error means that you are trying to submit the output of your upload function without passing through handler.upload.

jpalomino10 commented 1 week ago

@joriksp I had the same problem in my case was that I didn`t add the options on app/api/admin/[[...nextadmin]]/route.ts by default come commented

import { prisma } from "@/prisma";
import schema from "@/prisma/json-schema/json-schema.json";
import { createHandler } from "@premieroctet/next-admin/appHandler";

const { run } = createHandler({
  apiBasePath: "/api/admin",
  prisma,
  schema,
  /*options*/
});

export { run as DELETE, run as GET, run as POST };
joriksp commented 1 week ago

What's the return type of your handler.upload function? This return type is passed to Prisma in your field, which appears to be named image.

This error means that you are trying to submit the output of your upload function without passing through handler.upload.

handler: {
                    upload: async (buffer, { name, type }) => {
                          const uploadedFile = await upload(
                             new File([buffer], name)
                          );
                          return uploadedFile.url || "";
                       },
                 },

It returns string (url)

cregourd commented 1 week ago

@joriksp I had the same problem in my case was that I didn`t add the options on app/api/admin/[[...nextadmin]]/route.ts by default come commented

import { prisma } from "@/prisma";
import schema from "@/prisma/json-schema/json-schema.json";
import { createHandler } from "@premieroctet/next-admin/appHandler";

const { run } = createHandler({
  apiBasePath: "/api/admin",
  prisma,
  schema,
  /*options*/
});

export { run as DELETE, run as GET, run as POST };

Indeed, thanks for the precision If you are using options, which it seems you are, you need to pass the options to the createHandler function param. If you don't, any server-side function you add to the options file (such as handler.upload) will not be executed. In the example this part is commented because the options are optional, in your case you need to put options in there.

@joriksp please let me know if you have in the same case

cregourd commented 1 week ago

Also, to avoid other problems, we recommend putting options in the createHandler function params from the moment you use any options, even if it does not appear to be server-side execution.