Can't successfully upload a file to an existing entry from Node JS

Hi,

Yes i tried that exact way aswel and i have the same error:

UnhandledPromiseRejectionWarning: TypeError: Cannot destructure property ‘fileInfo’ of ‘data’ as it is undefined.
at Object.upload (/Users/jbm/Dev/project/backend/node_modules/strapi-plugin-upload/services/Upload.js:129:13)

It looks like there is an issue on how the upload function destructures the form data (node_modules/strapi-plugin-upload/controllers/services/Upload.js, Line 127)

when trying to console log it’s indeed undefined
async upload({ data, files }, { user } = {}) {

console.log('LOG DATAS', data, 'LOG FILES', files)

const { fileInfo, ...metas } = data;

etc…
}

Both are undefined.

Oh, my fault, I just noticed that you use the upload service, I was thinking you tried to upload them with Rest API.

You can try to append fileInfo to data, it is an object: {"caption":"new_caption","alternativeText":"alternative text"} that one should be stringified.

Also, you can take a look at the controller which uses the service:

I tried to append fileInfo, i have the same error, also i cant really understand what to do with the file you suggested me to watch, but thanks for your help. Im still at the same point right now.

It has been two days of trying to use strapi upload, if i had a suggestion to make is to enhance the documentation on this matter, because it’s very poor and confusing.

Maybe you could guide me on how to do it with rest api?
Still the issue with the service may be considered a bug?

It’s not a bug, it’s just not meant right now to be used directly.

Take a look at this discussion:

1 Like

@jbm I had the exact same problem I was able to implement this using the upload service using the following code

const fileStat = fs.statSync(filepath);
const attachment = await strapi.plugins.upload.services.upload.upload({
	data: {
		refId: result.id,
		ref: 'my-collection',
		field: 'attachments',
	},
	files: {
		path: filepath,
		name: `filename.pdf`,
		type: 'application/pdf', // mime type
		size: fileStat.size,
	},
});
9 Likes

You are a genius, thanks !!!

Thanks for your help Sunnyson, Periabytes have the solution, it should be added to the doc!

1 Like

Thanks! Genius! Awesome works.

May i ask where did you find the documentation for this ??

I didn’t find it in the documentation, I just tinkered with the upload plugin’s services and reading the source code, specifically the Upload.js file inside node_modules/strapi-plugin-upload/services/Upload.js

It looks good but what if I want to upload a file from an online url ? (like a png or jpg) If I set the file’s url as filepath your solution don’t work…

I think You must find a way to “download” the file from the URL then attach it the same way after you’ve downloaded it.

1 Like

Ok thanks!

Hello! Did you figure out a solution to uploading a file from an online URL?

Hi

I have taken this from a current project and slightly adjusted it for you. Hopefully, I haven’t made any mistakes. Don’t forget the authToken (in the format of Bearer ....).

Let me know if it solves your problem.

const fetch = require("node-fetch"); // is already a strapi dependency
const FormData = require("form-data"); // is already a strapi dependency
const path = require("path");
const { getAbsoluteServerUrl } = require("strapi-utils");

const uploadImage = async ({ url, fieldName, id, authToken, collection }) => {
  const imageRes = await fetch(url);
  if (imageRes.status !== 200) return;

  const filename = path.basename(url);

  const arrayBuffer = await imageRes.arrayBuffer();
  const buffer = Buffer.from(arrayBuffer);

  const formData = new FormData();

  formData.append("files", buffer, filename);

  // if adding to an existing entry ( we need the relevant collection, ID, and fieldName)
  if (id && fieldName && collection) {
    formData.append("ref", collection);
    formData.append("refId", id);
    formData.append("field", fieldName);
  }

  const baseUrl = getAbsoluteServerUrl(strapi.config);

  return fetch(`${baseUrl}/upload`, {
    method: "POST",
    body: formData,
    headers: {
      Authorization: authToken,
      ...formData.getHeaders(),
    },
  })
    .then((res) => res.json())
    .catch((e) => console.log("e", e));
};

Thank you @mattf96!

I have been trying to not make an API call to Strapi from within since we have access to all of the Strapi internal plugins but looks like I have no choice. The last part that I’m stuck on is getting the authToken. I’m making this call on a lifecycle hook afterCreate. I guess I can technically login to Strapi from within as well but wasn’t sure if anyone had a better way.

The only way I have been able to use the in-built strapi upload service is if you first save the file to a folder. I’m not sure what the implications will be for different hosting providers but you can probably programmatically delete this stored file.

const fetch = require("node-fetch"); // is already a strapi dependency
const mime = require("mime-types"); // is already a strapi dependency
const path = require("path");
const fs = require("fs");

const url = "YOUR_URL";

const filepath = path.basename(url);
const filename = path.basename(filepath, path.extname(filepath));

let dest = fs.createWriteStream(filepath);

await new Promise((resolve, reject) =>
  fetch(url)
    .then((res) => res.body.pipe(dest).on("finish", () => resolve(true))) // you can add more error handling
    .catch((e) => reject(e))
);

const fileStat = fs.statSync(filepath);

try {
  await strapi.plugins.upload.services.upload.upload({
    data: {},
    files: {
      path: filepath,
      name: filename,
      type: mime.lookup(filepath),
      size: fileStat.size,
    },
  });
} catch (e) {
  console.log("Error", e);
}

The problem is the enhanceFile function within the strapi-plugin-upload. It expects the file.path to be a string. You could override this to check if the file is already a buffer (I think you would need to override quite a few more files though).

async enhanceFile(file, fileInfo = {}, metas = {}) {
    console.log('enhanceFile',file)
    let readBuffer;
    try {
      readBuffer = await util.promisify(fs.readFile)(file.path);
    } catch (e) {
      if (e.code === 'ERR_FS_FILE_TOO_LARGE') {
        throw strapi.errors.entityTooLarge('FileTooBig', {
          errors: [
            {
              id: 'Upload.status.sizeLimit',
              message: `${file.name} file is bigger than the limit size!`,
              values: { file: file.name },
            },
          ],
        });
      }
      throw e;
    }