How to Use IPFS in Forge
What is IPFS?
Interplanetary File System (IPFS) is a peer-to-peer hypermedia protocol, can be used for file sharing, which is popularly used in the blockchains ecosystem. Because of decentralized infrastructure, files can be stored permanently (theoretically). It is an open source with a MIT license, so that developers are widely used for dapps, self-sovereign identity resolvers, NFT storage, and so on.
For more details of IPFS, you can refer to their git repo, https://github.com/ipfs/ipfs.
How do we backup JIRA issues to IPFS?
In this Codegeist 2021 hackathon, our project, “Ethereum & IPFS toolkits for JIRA” (https://devpost.com/software/ethereum-and-ipfs-toolkits) did try to backup Jira issues to IPFS. Not surprising, it works. This is the first time we use Forge to develop Jira applications. We learned a lot from the documents on https://developer.atlassian.com/ page and got help from the Atlassian support team.
It is not possible to create an IPFS node in the Atlassian cloud, so we implemented a REST API server as a proxy to save the POST data to IPFS storage as a file. Therefore, we need to config Jira app for a external fetch permission in manifast.yml as follow
...permissions: external: fetch: backend: - ‘https://ipfs-proxy-server.muzamint.com/'
where https://ipfs-proxy-server.muzamint.com/ is a proxy server for IPFS access.
When we want to backup a Jira issue content (in the JSON format) to IPFS, we can simply POST the data to the RESTful proxy server, as the code snippet in the src/index.jsx file as follows; And you may got the URL for the data in return.
...const fetchPOSTcontent = async (json) => { const data = await postData('https://ipfs-proxy-server.muzamint.com/', json) JSON.stringify(data) setLogSend(data.ipfs_url) await storage.set('ipfs_url', logSend) return logSend}
But, how can we get the current Jira issue data in JSON, we need to call a JIRA API, called api.asApp().requestJira(), as follows;
import api from '@forge/api'...const context = useProductContext()const data = await fetchCommentsForContent(context)const json = JSON.stringify(data)
where
const fetchCommentsForContent = async (contentId) => {console.log(`issueKey: ${contentId.platformContext.issueKey}`)const response = await api.asApp().requestJira( route`/rest/api/3/issue/${contentId.platformContext.issueKey}`, { headers: { Accept: 'application/json', }, }, ) console.log(`Response: ${response.status} ${response.statusText}`) const data = await response.json() return data}
also, need to add permission in manifest.yml as follow;
permissions: scopes: - 'read:jira-work'...
You can get the source code from git repo, https://github.com/MS-hack/ipfs-backup-jira-issue.
How about the server side?
So far, we have not explained how the server side really implements the IPFS add. We design a POST API to do the work. For example, if you POST a JSON format data to the proxy server we deploy on the endpoint, https://ipfs-proxy-server.muzamint.com/. You may receive a JSON with an ipfs_url, which is a HTTPS gateway links to the ipfs endpoint, ipfs/bafkreicdduefbzrunia33fjka2izesofktlaq3yzq6nz7kzxykxsy5fon4 as bellow;
curl -XPOST 'https://ipfs-proxy-server.muzamint.com/' -d '{ "age" : "10"}'
will return
{"ipfs_url":"https://dweb.link/ipfs/bafkreicdduefbzrunia33fjka2izesofktlaq3yzq6nz7kzxykxsy5fon4"}
After that, you can retrieve the data from this URL anytime and available forever, for example to use curl as follow;
curl https://dweb.link/ipfs/bafkreicdduefbzrunia33fjka2izesofktlaq3yzq6nz7kzxykxsy5fon4
will return
{ age: "10"}
BTW, because the Brave web browser supports ipfs URL natively, you can also surf
ipfs://bafkreicdduefbzrunia33fjka2izesofktlaq3yzq6nz7kzxykxsy5fon4
will result the same.
There are many way to implement a IPFS access, either to use ipfs-http-client for Javascript or to request a service from a IPFS service provider, such as nfs.storage or pinata.cloud. We used the second way with nfs.storage.
We use micro for the RESTful API server and use nft.storage to make an IPFS service request. To add a json data to the IPFS network, simply call storeBlob() of a nft.storage client as follow;
const { text } = require('micro')const post = require('micro-post')const { toGatewayURL, NFTStorage, Blob } = require('nft.storage')const apiKey = 'bring your own key'const client = new NFTStorage({ token: apiKey })module.exports = post(options, async (req, res) => {console.log('\n 🔥 Usage: curl -X POST https://ipfs-proxy-server.muzamint.com/ -d @data.json -H “Content-Type: application/json”\n' )const jsonData = await text(req)var blob = new Blob([jsonData], { type: 'application/json' })const cid = await client.storeBlob(blob)const forwardURL = toGatewayURL('ipfs://' + cid).hrefreturn { "ipfs_url": forwardURL }})
The complete code is in this repo, https://github.com/MS-hack/ipfs-back-end-forge
Conclusion
It’s a great experience to use Forge in the hackathon. We learn a lot of new skills in Forge to develop JIRA and Confluence apps, which includes
- fetch — to fetch an external REST API server.
- UI kit — to create UI in Jira or Confluence apps.
- Jira API — to get Jira contents asApp or asUser. We can also call Jira API with curl.
- Custom UI — to invoke a static page embedded in Jira. It also makes our Simple Ethereum wallet possible.
- manifest.yml — to config functions and permissions for Forge apps.
- config page — to develop config UI in Forge.
- forge tunnel — to debug and re-deploy automatically.
- Jira panel — to add Jira apps for panels with Forge.
- Confluence macro — to add macros in Confluence makes it easy with Forge.
After this hackathon, we will add these apps to Atlassian marketplace and add more toolkits related to blockchains and IPFS.
Also thanks to Atlassian support team, who help a lot.