Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Filebase storage for IPFS #64

Merged
merged 7 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21,430 changes: 15,125 additions & 6,305 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"author": "Vijay Krishnavanshi",
"license": "ISC",
"dependencies": {
"@filebase/sdk": "^1.0.4",
"@pinata/sdk": "^1.1.26",
"aws-sdk": "^2.1209.0",
"bunyan": "^1.8.15",
Expand Down
5 changes: 2 additions & 3 deletions src/domain/content.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
const { Readable } = require('stream');
const IPFS = require('./ipfs');
const GetIpfsService = require('./ipfs');
const Cache = require('./cache');
const ipfs = new IPFS();
const cache = new Cache();

async function content(ipfsHash) {
const stream = await ipfs.get(ipfsHash);
const stream = await GetIpfsService().get(ipfsHash);
return {
contentStream: stream,
};
Expand Down
123 changes: 123 additions & 0 deletions src/domain/ipfs/filebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const { ObjectManager, PinManager } = require('@filebase/sdk');

const config = require('../../../config');
const { Readable, PassThrough } = require('stream');
const IpfsStorageInterface = require('./interface');
const { error } = require('console');

/**
* Represents a Filebase object.
* @class
* @extends IpfsStorageInterface
*/
class FileBase extends IpfsStorageInterface {
/**
* Represents a Filebase object.
* @constructor
*/
constructor() {
super();
const accessKey = config.FILEBASE_ACCESS_KEY
const secret = config.FILEBASE_SECRET
const bucketName = config.FILEBASE_BUCKET_NAME

this.objectManager = new ObjectManager(accessKey, secret, {
bucket: bucketName
});

this.pinManager = new PinManager(accessKey, secret, {
bucket: bucketName
});

}

/**
* Uploads a file to IPFS using the Filebase storage.
*
* @param {ReadableStream} readableStreamForFile - The readable stream for the file to be uploaded.
* @param {Object} options - The options for the upload.
* @param {string} options.name - The name of the file.
* @param {string} options.attribute - The attribute of the file.
* @param {number} options.filesize - The size of the file in bytes.
* @returns {Object} - The upload result containing the IPFS URL, IPFS hash, storage type, pin size, and timestamp.
*/
async upload(readableStreamForFile, { name, attribute, filesize }) {
try {
let response = await this.objectManager.upload(name, readableStreamForFile);
let cid = response.cid;

return {
ipfsUrl: `https://ipfs.filebase.io/ipfs/${cid}`,
ipfsHash: `${cid}`,
ipfsStorage: 'filebase',
pinSize: filesize,
timestamp: Date.now(),
};
} catch (error) {
console.error("Error while uploading object to filebase:", error);
}

}

/**
* Retrieves the IPFS content from the specified URL.
* @param {Object} options - The options for retrieving the IPFS content.
* @param {string} options.ipfsUrl - The URL of the IPFS content.
* @returns {ReadableStream} - A readable stream containing the IPFS content.
*/
async get({ ipfsUrl }) {
try {
let resp = await this.pinManager.download(ipfsUrl, {
endpoint: "https://ipfs.filebase.io/"
});

const ipfsStream = Readable.from(resp);
return ipfsStream;
} catch (error) {
console.error("Error while getting object from filebase:", error);
}
}

/**
* Removes a file from IPFS based on the given IPFS hash.
* @param {Object} options - The options for removing the file.
* @param {string} options.ipfsHash - The IPFS hash of the file to be removed.
* @returns {Promise} A promise that resolves when the file is successfully removed.
*/
async remove({ ipfsHash }) {
try {
let hashes = ipfsHash.split('/');

const requestid = await this.getRequestId({ hashes });
if (requestid == "" || requestid == undefined) {
throw error("No object found for the given IPFS hash");
}

return await this.pinManager.delete(requestid);
} catch (error) {
console.error("Error while removing object from filebase:", error);
}
}

/**
* Retrieves the request ID associated with the given IPFS hash.
*
* @param {Object} options - The options object.
* @param {string} options.ipfsHash - The IPFS hash.
* @returns {string} The request ID.
*/
async getRequestId({ ipfsHash }) {
try {
const resp = await this.pinManager.list({ cid: [ipfsHash] });
if (resp['count'] == 0) {
console.log("No pin found with the given cid: ", cid);
return;
}
return resp['results'][0]['requestid']
} catch (error) {
console.error("Error while getting request ID from filebase:", error);
}
}
}

module.exports = FileBase;
47 changes: 22 additions & 25 deletions src/domain/ipfs/index.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,31 @@
const config = require('../../../config');
const Web3StorageService = require('./web3storage');
const Pinata = require('./pinata');
const FileBase = require('./filebase');

class IPFS {
constructor() {
this.web3Storage = new Web3StorageService();
this.pinata = new Pinata();
this.defaultService = config.DEFAULT_IPFS_SERVICE || 'pinata';
}

async upload(readableStreamForFile, { name, attributes }) {
if (this.defaultService === 'web3.storage') {
return this.web3Storage.upload(readableStreamForFile, {
name,
attributes,
});
}
return this.pinata.upload(readableStreamForFile, { name, attributes });
}

async get(ipfsHash) {
return this.web3Storage.get({ ipfsHash });
}
/**
* Returns an instance of the IPFS service based on the provided IPFS storage.
* If no IPFS storage is provided, it defaults to the 'pinata' service.
*
* @param {string} ipfsStorage - The IPFS storage to use.
* @returns {IpfsStorageInterface} - An instance of the IPFS service.
*/
function GetIpfsService(ipfsStorage) {
let defaultService = config.DEFAULT_IPFS_SERVICE ? config.DEFAULT_IPFS_SERVICE : 'pinata';
ipfsStorage = ipfsStorage ? ipfsStorage : defaultService;

async remove({ ipfsHash, ipfsStorage }) {
if (ipfsStorage === 'web3.storage') {
return this.web3Storage.remove({ ipfsHash });
}
return this.pinata.remove({ ipfsHash });
switch (ipfsStorage) {
case 'web3.storage':
return new Web3StorageService();
case 'pinata':
return new Pinata();
case 'filebase':
return new FileBase();
default:
console.error(`Invalid IPFS storage: ${ipfsStorage}. Using default IPFS storage: ${defaultService}`);
return GetIpfsService(defaultService)
}
}

module.exports = IPFS;
module.exports = { GetIpfsService };
18 changes: 18 additions & 0 deletions src/domain/ipfs/interface.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
class IpfsStorageInterface {
constructor() {

}
async upload(readableStreamForFile, { name, attribute, filesize }) {
throw new Error('No Ipfs storage service found');
}

async get({ ipfsUrl }) {
throw new Error('No Ipfs storage service found');
}

async remove({ ipfsHash }) {
throw new Error('No Ipfs storage service found');
}
}

module.exports = IpfsStorageInterface;
7 changes: 5 additions & 2 deletions src/domain/ipfs/pinata.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ const PassThrough = require('stream').PassThrough;
const request = require('request');
const config = require('../../../config');
const pinataSDK = require('@pinata/sdk');
const IpfsStorageInterface = require('./interface');

class Pinata {

class Pinata extends IpfsStorageInterface {
constructor() {
super();
this.apiKey = config.PINATA_API_KEY;
this.secretApiKey = config.PINATA_SECRET_KEY;
this.pinata = pinataSDK(this.apiKey, this.secretApiKey);
Expand All @@ -20,7 +23,7 @@ class Pinata {
};
}

async upload(readableStreamForFile, { name, attributes }) {
async upload(readableStreamForFile, { name, attributes, filesize }) {
const keyvalues = {};
(attributes || []).forEach((attribute) => {
keyvalues[attribute.trait_type] = attribute.value;
Expand Down
15 changes: 8 additions & 7 deletions src/domain/ipfs/web3storage.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
const PassThrough = require('stream').PassThrough;
const request = require('request');
const { Web3Storage } = require('web3.storage');
const IpfsStorageInterface = require('./interface');
const config = require('./../../../config');

class Web3StorageService {
class Web3StorageService extends IpfsStorageInterface {
constructor() {
super();
this.client = new Web3Storage({ token: config.WEB3STORAGE_TOKEN });
}

async upload(readableStreamForFile, { name }) {
async upload(readableStreamForFile, { name, attribute, filesize }) {
const cid = await this.client.put([
{ name, stream: () => readableStreamForFile },
]);
Expand All @@ -23,14 +25,13 @@ class Web3StorageService {
};
}

async get({ ipfsHash }) {
if (!ipfsHash) {
async get({ ipfsUrl }) {
if (!ipfsUrl) {
return null;
}
const ipfsUrl = `https://w3s.link/ipfs/${ipfsHash}`;
console.log(ipfsUrl);
const authenticUrl = `https://w3s.link/ipfs/${ipfsUrl}`;
const ipfsStream = new PassThrough();
request(ipfsUrl).pipe(ipfsStream);
request(authenticUrl).pipe(ipfsStream);
return ipfsStream;
}

Expand Down
6 changes: 3 additions & 3 deletions src/domain/upload.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
const { Readable } = require('stream');
const File = require('./file');
const Cache = require('./cache');
const IPFS = require('./ipfs');
const { GetIpfsService } = require('./ipfs');
const cache = new Cache();
const ipfs = new IPFS();

async function upload({ fileId, chainId, contractAddress, file, invokerAddress, tags }) {
const { name, mimetype, data } = file;
const stream = Readable.from(data);
stream.path = name;
const ipfsFile = await ipfs.upload(stream, { name });
const filesize = data.length;
const ipfsFile = await GetIpfsService().upload(stream, { name, filesize });
const cachedFile = await cache.queue(ipfsFile);
// add file to db
await File.create({
Expand Down