PinataCloud / pinata

The new Pinata SDK
https://docs.pinata.cloud/sdk
MIT License
8 stars 4 forks source link

Upload from node server api #30

Closed schellenbergk closed 2 months ago

schellenbergk commented 2 months ago
import { PinataSDK } from 'pinata';
import { Blob } from "buffer";
...
const pinata = new PinataSDK({
  pinataJwt: process.env.IPFS_PINATA_JWT!,
});

const outputDir = path.join(process.cwd(), 'tmp');
const imgPath = path.join(outputDir, `new-image.png`);

const fileBuffer = fs.readFileSync(imgPath);
const blob = new Blob([fileBuffer], { type: "image/png" });

const file = new File([blob], path.basename(imgPath), { type: "image/png" });

// Upload the buffer directly to Pinata
const uploadResult = await pinata.upload.file(file, {
  metadata: {
    name: 'Test',
  },
});
const newPhoto = `${process.env.GATEWAY_URL}${uploadResult.IpfsHash}`;

The problem is with new File([blob], as it complains Type 'Blob' is not assignable to type 'BlobPart'

stevedylandev commented 2 months ago

Hey there! I believe this might have something to do with using Blob from buffer vs the web file api, although most versions of node support this natively. What version of node are you currently running?

schellenbergk commented 2 months ago

v18.18.0... So basically I'm using nextjs api route to upload to pinata and the image file comes from current directory.

schellenbergk commented 2 months ago

When i try Pinata example from docs in my nextjs api route:

const { PinataSDK } = require("pinata")
const fs = require("fs")
const { Blob } = require("buffer")

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud"
})

const blob = new Blob([fs.readFileSync("./hello-world.txt")]);
const file = new File([blob], "hello-world.txt", { type: "text/plain"})
const upload = await pinata.upload.file(file);

I'm getting Type 'Blob' is not assignable to type 'BlobPart'. because the issue here is that Node.js' buffer.Blob is not directly compatible with the Web API's Blob, even though they share a similar API surface.

stevedylandev commented 2 months ago

Gotcha! Couple of things, could you try removing const { Blob } = require("buffer") from the code and see what that does?

Also just to clarify, are you trying to upload a local file from the next directory or a file that a user selects from their computer? If the latter we have this guide: https://docs.pinata.cloud/web3/frameworks/next-js

Final note, I noticed you might be trying to use IPFS in which case you will want to use the pinata-web3

schellenbergk commented 2 months ago

Alright, some progress, but now ReferenceError: File is not defined

stevedylandev commented 2 months ago

Do you by chance have a repo you could share so I can try to reproduce this?

schellenbergk commented 2 months ago

REPO: https://github.com/3magine/pinata-nextjs-api-example

stevedylandev commented 2 months ago

Thanks!! Will get back to you on this

schellenbergk commented 2 months ago

For now, as a workaround I'm using the old SDK:

    // THIS WORKS:
    import pinataSDK from '@pinata/sdk';
    import path from 'path';
    import fs from 'fs';
    ...
    const pinata = new pinataSDK({
      pinataJWTKey: process.env.IPFS_PINATA_JWT!,
    });
    const outputDir = path.join(process.cwd(), 'tmp');
    const imgPath = path.join(outputDir, `new-image.png`);
    const readableStream = fs.createReadStream(imgPath);
    const uploadResult = await pinata.pinFileToIPFS(readableStream, {
      pinataMetadata: { name: path.basename(imgPath) },
    });
    const newPhoto = `${process.env.GATEWAY_URL!}${uploadResult.IpfsHash}`;
stevedylandev commented 2 months ago

Oh nice!! I wonder if it's from using a read stream instead of a file object. Could you perhaps expand on the use case of uploading repo file inside Next? Just want to understand better and see if there's a solution we could look into.

stevedylandev commented 2 months ago

Actually I just got it to work as well with the current SDK. I suspected the real issue was with how Next handles local repo files as files you can read. Once I put the file in a tmp folder it worked like a charm

import { PinataSDK } from "pinata-web3";
// import { PinataSDK } from 'pinata';

import { NextApiRequest, NextApiResponse } from "next";
import path from "path";
import fs from "fs";

const handler = async (
    req: NextApiRequest,
    res: NextApiResponse<boolean | any>,
) => {
    try {
        const outputDir = path.join(process.cwd(), "tmp");
        const filePath = path.join(outputDir, `slapcity.png`);
        const pinata = new PinataSDK({
            pinataJwt: process.env.IPFS_PINATA_JWT!,
        });

        const fileBuffer = fs.readFileSync(filePath);
        const blob = new Blob([fileBuffer], { type: "image/png" });
        const file = new File([blob], path.basename(filePath), {
            type: "image/png",
            lastModified: new Date().getTime(),
        });

        // Upload the buffer directly to Pinata
        const uploadResult = await pinata.upload.file(file, {
            metadata: {
                name: "SlapCityNft",
            },
        });
        console.log("Uploaded to Pinata:", uploadResult);
        const newPhoto = `${process.env.NEXT_PUBLIC_GATEWAY_URL}${uploadResult.IpfsHash}`;

        return res.json({
            filePath,
            uploadResult,
            newPhoto,
        });
    } catch (e) {
        console.log(e);
        return res.status(500).json("Error uploading to Pinata");
    }
};

export default handler;
schellenbergk commented 2 months ago

Oh nice!! I wonder if it's from using a read stream instead of a file object. Could you perhaps expand on the use case of uploading repo file inside Next? Just want to understand better and see if there's a solution we could look into.

the use case is that I generate a new image in my api route, then upload it to Pinata.

schellenbergk commented 2 months ago

Actually I just got it to work as well with the current SDK. I suspected the real issue was with how Next handles local repo files as files you can read. Once I put the file in a tmp folder it worked like a charm

Are you sure? I just tried it and it did not work... Still getting ReferenceError: File is not defined...

What's your node version?

polluterofminds commented 2 months ago

Hey @schellenbergk I just cloned your repo and everything worked as expected. I'm on node v20.11.1

image

I tried on node v18 and it fails with the same error. This is because the File class and the Blob class were introduced in node v20. But you can still upload using the SDK. This works using your existing repo code with a couple of modifications.

import {PinataSDK} from 'pinata-web3';
// import { PinataSDK } from 'pinata';

import {NextApiRequest, NextApiResponse} from 'next';
import path from 'path';
import fs from 'fs';
import { Blob } from "buffer"

const handler = async (
  req: NextApiRequest,
  res: NextApiResponse<boolean | any>
) => {
  try {
    const filePath = path.join(process.cwd(), 'assets', 'slapcity.png');

    const pinata = new PinataSDK({
      pinataJwt: process.env.IPFS_PINATA_JWT!,
    });

    const fileBuffer = fs.readFileSync(filePath);

    // Convert Buffer to Blob
    const blob: any = new Blob([fileBuffer], { type: 'image/png' });

    // Make sure to handle Pinata SDK correctly; assuming the SDK can accept form data directly
    const uploadResult = await pinata.upload.file(blob);
    console.log('Uploaded to Pinata:', uploadResult);
    const newPhoto = `${process.env.NEXT_PUBLIC_GATEWAY_URL}${uploadResult.IpfsHash}`;

    return res.json({
      filePath,
      uploadResult,
      newPhoto,
    });
  } catch (e) {
    console.log(e);
    return res.status(500).json('Error uploading to Pinata');
  }
};

export default handler;
polluterofminds commented 2 months ago

We'll update the docs to indicate how to do this for node 18 which is still part of LTS.

schellenbergk commented 2 months ago

Perfect, thanks for all your time. Pura Vida!