diff --git a/config/system-user-dev.json b/config/system-user-dev.json index 5eb21c7..53fd88a 100644 --- a/config/system-user-dev.json +++ b/config/system-user-dev.json @@ -1,6 +1,6 @@ { - "password": "jxR0kPRm6GI7RETZ36KW", + "password": "eYrzProqwsAyO3CT0Ftf", "email": "system@system.com", - "id": "6796f3e8e473169ab68aa9b5", - "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3OTZmM2U4ZTQ3MzE2OWFiNjhhYTliNSIsImlhdCI6MTczNzk0NjA4OH0.q1Dy0WXO2E2Q5y2eNB2INq_YD3AnNirgX1vrk9KxkH0" + "id": "67a8af30c958c587f890cd8f", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3YThhZjMwYzk1OGM1ODdmODkwY2Q4ZiIsImlhdCI6MTczOTEwODE0NH0.OFdVwH8ikbChHV0wjRpYvnL7vu46HDYS-od3FRtm9Nc" } \ No newline at end of file diff --git a/src/controllers/rest-api/ipfs/controller.js b/src/controllers/rest-api/ipfs/controller.js index af45bb8..0856026 100644 --- a/src/controllers/rest-api/ipfs/controller.js +++ b/src/controllers/rest-api/ipfs/controller.js @@ -39,6 +39,7 @@ class IpfsRESTControllerLib { this.getService = this.getService.bind(this) this.getFileInfo = this.getFileInfo.bind(this) this.getPins = this.getPins.bind(this) + this.cid2json = this.cid2json.bind(this) } /** @@ -220,7 +221,7 @@ class IpfsRESTControllerLib { ctx.body = metadata } catch (err) { // wlogger.error('Error in ipfs/controller.js/viewFile(): ', err) - console.log('Error in ipfs/controller.js/getService(): ', err) + console.log('Error in ipfs/controller.js/getFileInfo(): ', err) this.handleError(ctx, err) } } @@ -253,8 +254,37 @@ class IpfsRESTControllerLib { } } + /** + * @api {get} /ipfs/cid2json/:cid Given a CID, retrieve a JSON object + * @apiPermission public + * @apiName GetCid2Json + * @apiGroup REST IPFS + * @apiDescription Given a CID, retrieve a JSON object. + * If the CID does not resolves to a JSON file, then an error is thrown. + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5015/ipfs/cid2json/bafkreigbgrvpagnmrqz2vhofifrqobigsxkdvnvikf5iqrkrbwrzirazhm + * + */ + async cid2json (ctx) { + try { + const { cid } = ctx.params + + const json = await this.useCases.ipfs.cid2json({ cid }) + + ctx.body = json + } catch (err) { + // wlogger.error('Error in ipfs/controller.js/viewFile(): ', err) + console.log('Error in ipfs/controller.js/cid2json(): ', err.message) + this.handleError(ctx, err) + } + } + // DRY error handler handleError (ctx, err) { + // console.log('handleError() err.status: ', err.status) + // console.log('handleError() err.message: ', err.message) + // If an HTTP status is specified by the buisiness logic, use that. if (err.status) { if (err.message) { @@ -263,6 +293,7 @@ class IpfsRESTControllerLib { ctx.throw(err.status) } } else { + // console.log(`handleError() err.message: ${err.message}`) // By default use a 422 error if the HTTP status is not specified. ctx.throw(422, err.message) } diff --git a/src/controllers/rest-api/ipfs/index.js b/src/controllers/rest-api/ipfs/index.js index 645ffef..599e14f 100644 --- a/src/controllers/rest-api/ipfs/index.js +++ b/src/controllers/rest-api/ipfs/index.js @@ -60,6 +60,7 @@ class IpfsRouter { this.router.get('/service', this.ipfsRESTController.getService) this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo) this.router.get('/pins', this.ipfsRESTController.getPins) + this.router.get('/cid2json/:cid', this.ipfsRESTController.cid2json) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) diff --git a/src/use-cases/ipfs-use-cases.js b/src/use-cases/ipfs-use-cases.js index 35a032a..7497061 100644 --- a/src/use-cases/ipfs-use-cases.js +++ b/src/use-cases/ipfs-use-cases.js @@ -33,6 +33,7 @@ class IpfsUseCases { this.downloadCid = this.downloadCid.bind(this) // this.downloadCid2 = this.downloadCid2.bind(this) this.getWritePrice = this.getWritePrice.bind(this) + this.cid2json = this.cid2json.bind(this) // State this.lastWritePriceUpdate = null // Used to periodically update write price. @@ -151,6 +152,91 @@ class IpfsUseCases { } } + // Given a CID, this function will retrieve a JSON object from the + // ipfs-file-pin-service, using the IPFS JSON-RPC. + async cid2json (inObj = {}) { + try { + const { cid } = inObj + console.log('cid2json() cid: ', cid) + + // Throw an error if ipfs-bch-wallet-consumer has not yet connected to an instance of ipfs-file-pin-service. + const ipfsFileProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider + if (!ipfsFileProvider) { + throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.') + } + + // Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service. + const ipfsFiles = this.adapters.ipfsFiles + const metadata = await ipfsFiles.getFileMetadata({ cid }) + if (metadata.success === false) { + throw new Error(`Could not communicate with instance of ipfs-file-pin-service ${ipfsFileProvider}. Try again in a few seconds.`) + } + + // Throw an error if the file is not pinned. + const fileMetadata = metadata.fileMetadata + if (!fileMetadata.dataPinned) { + throw new Error(`CID ${cid} has not been pinned by ipfs-file-pin-service instance ${ipfsFileProvider}`) + } + // console.log('fileMetadata: ', fileMetadata) + + // Throw an error if this is not a JSON file + const filename = fileMetadata.filename + if (!filename.endsWith('.json')) { + throw new Error(`CID ${cid} does not resolve to a JSON file.`) + } + // Retrieve the CID content and store it in a Buffer. + const helia = this.adapters.ipfs.ipfs + const fileChunks = [] + + // list cid content. This is used to determine if the CID is a file or a directory. + const contentArray = [] + for await (const file of helia.fs.ls(cid)) { + contentArray.push(file) + } + // console.log('contentArray: ', contentArray) + + try { + // Handle CIDs that are files. + for await (const chunk of helia.fs.cat(cid)) { + fileChunks.push(chunk) + } + } catch (err) { + // Extra logic here to look at the filename of the first file and make sure + // it ends in .json. + const name = contentArray[0].name + if (!name.endsWith('.json')) { + throw new Error(`CID ${cid} is a directory and its first file does not resolve to a valid JSON file.`) + } + + // Handle CIDs that are directorys containing a JSON file. (TokenTiger.com tokens) + for await (const chunk of helia.fs.cat(`${cid}/${name}`)) { + fileChunks.push(chunk) + } + } + + const fileBuf = Buffer.concat(fileChunks) + + // Convert the Buffer into a string. + const jsonStr = fileBuf.toString() + + // Parse the string into a JSON object. + let json = null + try { + json = JSON.parse(jsonStr) + } catch (err) { + throw new Error(`CID ${cid} does not resolve to a valid JSON object.`) + } + + return { + success: true, + json + } + } catch (err) { + console.error('Error in ipfs-use-cases.js/cid2json(): ', err.message) + throw err + } + } + // async downloadCid2 (inObj = {}) { // try { // const { cid } = inObj