Firebase Cloud Function in typescript not working with form data request

126 Views Asked by At

I am writing this cloud function in typescript and deploying it to firebase.

This endpoint receives form data http request with some files and then it writes these files to /tmp directory and after that it tries to upload these files to IPFS using pinata api

However, when I call this endpoint, it gives this error: Error uploading files to IPFS: AxiosError: Request failed with status code 400

I am pretty sure that my pinata bearer jwt token is correct

Can anyone pinpoint the problem in my code?

Note: same code is working just fine in a node app (outside firebase cloud function)

Code:

import * as functions from "firebase-functions";
import * as Busboy from "busboy";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import * as FormData from "form-data";
import axios from "axios";
import * as cors from "cors";
import * as express from "express";

const app = express();
app.use(cors({ origin: true }));
app.use(express.urlencoded({ extended: true }));

const JWT = functions.config().pinata.jwt;
const PINATA_API = functions.config().pinata.api;

app.post("/upload", async (req: any, res: any) => {
  if (req.method !== "POST") {
    res.status(400).json({ error: "Invalid request method. Please use POST." });
    return;
  }
  
  const busboy = (Busboy as any)({ headers: req.headers });
  req.pipe(busboy);
  let tempFileList : any = [];

  busboy.on("file", (fieldname: any, file: any, filename: any, encoding: any, mimetype: any) => {
    const tempFilePath = path.join(os.tmpdir(), filename.filename);

    tempFileList.push(tempFilePath);

    const writeStream = fs.createWriteStream(tempFilePath);
    file.pipe(writeStream);

    writeStream.on("finish", async () => {
      console.log("File written to temp folder:", tempFilePath);
    });
  });

  busboy.on("finish", async () => {
    try {
      for (let tempFilePath of tempFileList) {
        let formData = new FormData();
        let readStream = fs.createReadStream(tempFilePath);
        formData.append("file", readStream);

        formData.append(
          "pinataOptions",
          JSON.stringify({
            wrapWithDirectory: true,
          })
        );

        await axios.post(PINATA_API, formData, {
          headers: {
            ...formData.getHeaders(),
            "Authorization": `Bearer ${JWT}`
          },
        });
    }
      for (const tempFilePath of tempFileList) {
        fs.unlink(tempFilePath, (err) => {
          if (err) {
            console.error("Error deleting temp file:", err);
          }
        });
      }

    } catch (error) {
      res.status(500).send(`Error uploading files to IPFS: ${error}`);
    }
  });
  busboy.end(req.rawBody);
});

export const uploadNFTs = functions.https.onRequest(app);

I tried to upload files to ipfs using pinata api in my firebase cloud function written in typescript.

I expected the files to be uploaded successfully.

What actually resulted was that error when I call pinata api: AxiosError: Request failed with status code 400

1

There are 1 best solutions below

0
On

It looks like the problem with the upload code is that wrapWithDirectory doesn't really do what you think it might do. It only puts the upload inside of a directory rather than enabling directory uploads. Here is a script you can model after to handle a directory upload. The key is mapping over the files and appending them into an entry.

const fs = require("fs");
const FormData = require("form-data");
const rfs = require("recursive-fs");
const basePathConverter = require("base-path-converter");
const got = require('got');
const JWT = 'Bearer PASTE_YOUR_PINATA_JWT'

const pinDirectoryToPinata = async () => {
  const url = `https://api.pinata.cloud/pinning/pinFileToIPFS`;
  const src = "RELATIVE PATH TO DIRECTORY TO UPLOAD";
  var status = 0;
  try {
    const { dirs, files } = awaÇit rfs.read(src);
    let data = new FormData();
    for (const file of files) {
      data.append(`file`, fs.createReadStream(file), {
        filepath: basePathConverter(src, file),
      });
    }    
    const response = await got(url, {
      method: 'POST',
      headers: {
        "Content-Type": `multipart/form-data; boundary=${data._boundary}`,
        "Authorization": JWT
      },
      body: data
    })      
    .on('uploadProgress', progress => {
    console.log(progress);
    });
    console.log(JSON.parse(response.body));
  } catch (error) {
    console.log(error);
  }
};