Skip to content
This repository was archived by the owner on May 19, 2023. It is now read-only.

Commit c861971

Browse files
authored
Implement backup per did feature (#53)
* Provider layer * Service layer * Client layer * Use authManager to getBackup
1 parent cc004e9 commit c861971

File tree

13 files changed

+231
-3
lines changed

13 files changed

+231
-3
lines changed

modules/ipfs-cpinner-client/README.md

+13
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,19 @@ console.log(`Used: ${storage.used}`)
7979
console.log(`Available: ${storage.available}`)
8080
```
8181

82+
### Get backup information
83+
84+
```typescript
85+
import DataVaultWebClient from '@rsksmart/ipfs-cpinner-client'
86+
87+
const client = new DataVaultWebClient({ serviceUrl, did, rpcPersonalSign, serviceDid })
88+
89+
const backup = await client.getBackup()
90+
91+
console.log('This is the keys and cids you have stored in the DV')
92+
console.log(backup)
93+
```
94+
8295
### Create
8396

8497
```typescript

modules/ipfs-cpinner-client/src/index.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { AUTHENTICATION_ERROR, MAX_STORAGE_REACHED, SERVICE_MAX_STORAGE_REACHED,
55
import {
66
CreateContentPayload, CreateContentResponse,
77
DeleteTokenPayload, GetContentPayload, Config,
8-
SwapContentPayload, SwapContentResponse, GetContentResponsePayload, StorageInformation
8+
SwapContentPayload, SwapContentResponse, GetContentResponsePayload, StorageInformation, Backup
99
} from './types'
1010

1111
export default class {
@@ -56,6 +56,18 @@ export default class {
5656
.catch(this.errorHandler)
5757
}
5858

59+
getBackup (): Promise<Backup> {
60+
const { serviceUrl } = this.config
61+
62+
return this.authManager.getAccessToken()
63+
.then(accessToken => axios.get(
64+
`${serviceUrl}/backup`,
65+
{ headers: { Authorization: `DIDAuth ${accessToken}` } })
66+
)
67+
.then(res => res.status === 200 && res.data)
68+
.catch(this.errorHandler)
69+
}
70+
5971
create (payload: CreateContentPayload): Promise<CreateContentResponse> {
6072
const { content, key } = payload
6173
const { serviceUrl } = this.config

modules/ipfs-cpinner-client/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type DeleteTokenPayload = { key: string, id?: string }
88
export type SwapContentPayload = { key: string, content: string, id?: string }
99
export type SwapContentResponse = { id: string }
1010
export type StorageInformation = { used: number, available: number }
11+
export type Backup = { key: string, id: string }[]
1112

1213
export type Config = {
1314
serviceUrl: string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { IpfsPinnerProvider } from '@rsksmart/ipfs-cpinner-provider'
2+
import { deleteDatabase, resetDatabase, setupDataVaultClient, startService, testTimestamp } from './util'
3+
import { Server } from 'http'
4+
import { Connection } from 'typeorm'
5+
import MockDate from 'mockdate'
6+
import localStorageMockFactory from './localStorageMockFactory'
7+
8+
describe('get backup', function (this: {
9+
server: Server,
10+
dbConnection: Connection,
11+
ipfsPinnerProvider: IpfsPinnerProvider,
12+
serviceUrl: string,
13+
serviceDid: string
14+
}) {
15+
const dbName = 'get-backup.sqlite'
16+
17+
beforeAll(async () => {
18+
const { server, serviceUrl, ipfsPinnerProvider, dbConnection, serviceDid } = await startService(dbName, 4608)
19+
this.server = server
20+
this.ipfsPinnerProvider = ipfsPinnerProvider
21+
this.dbConnection = dbConnection
22+
this.serviceUrl = serviceUrl
23+
this.serviceDid = serviceDid
24+
})
25+
26+
afterAll(async () => {
27+
this.server.close()
28+
await deleteDatabase(this.dbConnection, dbName)
29+
})
30+
31+
beforeEach(() => {
32+
MockDate.set(testTimestamp)
33+
global.localStorage = localStorageMockFactory()
34+
})
35+
36+
afterEach(async () => {
37+
MockDate.reset()
38+
await resetDatabase(this.dbConnection)
39+
})
40+
41+
test('should return an empty object if no data saved', async () => {
42+
const { dataVaultClient } = await setupDataVaultClient(this.serviceUrl, this.serviceDid)
43+
44+
const backup = await dataVaultClient.getBackup()
45+
46+
expect(backup).toEqual([])
47+
})
48+
49+
test('should return the saved data', async () => {
50+
const { dataVaultClient, did } = await setupDataVaultClient(this.serviceUrl, this.serviceDid)
51+
52+
const key1 = 'TheKey1'
53+
const key2 = 'TheKey2'
54+
const id1 = await this.ipfsPinnerProvider.create(did, key1, 'some content')
55+
const id2 = await this.ipfsPinnerProvider.create(did, key1, 'another content for same did')
56+
const id3 = await this.ipfsPinnerProvider.create(did, key2, 'another content for another did')
57+
58+
const backup = await dataVaultClient.getBackup()
59+
60+
expect(backup).toEqual([
61+
{ key: key1, id: id1 },
62+
{ key: key1, id: id2 },
63+
{ key: key2, id: id3 }
64+
])
65+
})
66+
})

modules/ipfs-cpinner-provider/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ const deleted: boolean = await ipfsPinnerProvider.delete(did, key, cid) // cid c
7373
const availableStorage: number = await ipfsPinnerProvider.getAvailableStorage(did) // return the amount of bytes available to store value associated to the given did
7474
7575
const usedStorage: number = await ipfsPinnerProvider.getUsedStorage(did) // return the amount of bytes used to store value associated to the given did
76+
77+
const didBackup: Backup = await ipfsPinnerProvider.getBackup(did) // return an array containing all the keys and cids created by the given did
7678
```
7779

7880
See our [documentation](https://developers.rsk.co/rif/identity/) for advanced usage.

modules/ipfs-cpinner-provider/src/IpfsPinnerProvider.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MAX_STORAGE_REACHED } from './constants'
22
import {
3-
Content, CID, DID, IpfsPinner, Key, MetadataManager, IpfsClient, SavedContent
3+
Content, CID, DID, IpfsPinner, Key, MetadataManager, IpfsClient, SavedContent, Backup
44
} from './types'
55

66
export default class IpfsPinnerProvider {
@@ -81,6 +81,10 @@ export default class IpfsPinnerProvider {
8181
return this.metadataManager.getUsedStorage(did)
8282
}
8383

84+
getBackup (did: DID): Promise<Backup> {
85+
return this.metadataManager.getBackupByDid(did)
86+
}
87+
8488
private async deleteByCid (did: DID, key: Key, cid: CID): Promise<boolean> {
8589
const removed = await this.metadataManager.delete(did, key, cid)
8690
if (removed) return this.ipfsPinner.unpin(cid)

modules/ipfs-cpinner-provider/src/MetadataManager.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Repository } from 'typeorm'
22
import IpfsMetadata from './entities/ipfs-metadata'
3-
import { CID, MetadataManager, DID, Key } from './types'
3+
import { CID, MetadataManager, DID, Key, Backup } from './types'
44

55
export default class implements MetadataManager {
66
// eslint-disable-next-line no-useless-constructor
@@ -53,4 +53,11 @@ export default class implements MetadataManager {
5353
select: ['contentSize']
5454
}).then((entries: { contentSize: number }[]) => entries.map(e => e.contentSize).reduce((a, b) => a + b, 0))
5555
}
56+
57+
getBackupByDid (did: string): Promise<Backup> {
58+
return this.repository.find({
59+
where: { did },
60+
select: ['key', 'cid']
61+
}).then((entries: IpfsMetadata[]) => entries.map(({ key, cid }) => ({ key, id: cid })))
62+
}
5663
}

modules/ipfs-cpinner-provider/src/types.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export type CID = string
66
export type Content = string
77

88
export type SavedContent = { id: CID, content: Content }
9+
export type Backup = { key: Key, id: CID }[]
910

1011
export interface MetadataManager {
1112
save(did: DID, key: Key, id: CID, contentSize: number): Promise<boolean>
@@ -14,6 +15,7 @@ export interface MetadataManager {
1415
getKeys (did: DID): Promise<Key[]>
1516
getUsedStorage (did: DID): Promise<number>
1617
getUsedStorageByDidKeyAndCid (did: DID, key: Key, cid: CID): Promise<number>
18+
getBackupByDid (did: DID): Promise<Backup>
1719
}
1820

1921
export interface IpfsClient {

modules/ipfs-cpinner-provider/test/ipfsPinnerProvider.test.ts

+11
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,15 @@ describe('ipfs pinner provider', function (this: {
224224

225225
expect(keys).toEqual([key, anotherKey])
226226
})
227+
228+
test('get backup with more than one key and content', async () => {
229+
const anotherKey = 'another key'
230+
231+
const id1 = await this.centralizedPinnerProvider.create(did, key, content)
232+
const id2 = await this.centralizedPinnerProvider.create(did, anotherKey, content)
233+
234+
const data = await this.centralizedPinnerProvider.getBackup(did)
235+
236+
expect(data).toEqual([{ key, id: id1 }, { key: anotherKey, id: id2 }])
237+
})
227238
})

modules/ipfs-cpinner-provider/test/metadataManager.test.ts

+21
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,25 @@ describe('metadata manager', function (this: {
170170
const totalUsedStorage = await this.metadataManager.getUsedStorage(did)
171171
expect(totalUsedStorage).toEqual(contentSize + anotherContentSize)
172172
})
173+
174+
test('should get empty array when getting a backup if no content created', async () => {
175+
const data = await this.metadataManager.getBackupByDid(did)
176+
177+
expect(data).toEqual([])
178+
})
179+
180+
test('should get an array containing created data when requesting the backup', async () => {
181+
await this.metadataManager.save(did, key, cid, contentSize)
182+
183+
const data = await this.metadataManager.getBackupByDid(did)
184+
expect(data).toEqual([{ key, id: cid }])
185+
})
186+
187+
test('should get an array with repeated data if it has been saved twice', async () => {
188+
await this.metadataManager.save(did, key, cid, contentSize)
189+
await this.metadataManager.save(did, key, cid, contentSize)
190+
191+
const data = await this.metadataManager.getBackupByDid(did)
192+
expect(data).toEqual([{ key, id: cid }, { key, id: cid }])
193+
})
173194
})

modules/ipfs-cpinner-service/README.md

+6
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ Get storage availability information
8686
GET /storage -> { used: number, available: number }
8787
```
8888

89+
Get `key: cid` backup of the logged did (needs authentication)
90+
91+
```
92+
GET /backup -> { key: string, id: string }[]
93+
```
94+
8995
## Advanced usage
9096

9197
See our [documentation](https://rsksmart.github.io/rif-identity-docs/data-vault/cpinner/cpinner-service)

modules/ipfs-cpinner-service/src/api.ts

+10
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ export function setupPermissionedApi (app: Express, provider: IpfsPinnerProvider
5353
.catch(err => errorHandler(err, req, res))
5454
})
5555

56+
app.get('/backup', async (req: AuthenticatedRequest, res) => {
57+
const { did } = req.user
58+
59+
logger.info(`Retrieving backup from ${did}`)
60+
61+
return provider.getBackup(did)
62+
.then(backup => res.status(200).json(backup))
63+
.catch(err => errorHandler(err, req, res))
64+
})
65+
5666
app.post('/content/:key', (req: AuthenticatedRequest, res: Response) => {
5767
const { did } = req.user
5868
const { key } = req.params
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { Connection } from 'typeorm'
2+
import express, { Express } from 'express'
3+
import bodyParser from 'body-parser'
4+
import { IpfsPinnerProvider, ipfsPinnerProviderFactory } from '@rsksmart/ipfs-cpinner-provider'
5+
import { createSqliteConnection, deleteDatabase, ipfsApiUrl, mockedLogger } from './util'
6+
import { setupPermissionedApi } from '../src/api'
7+
import request from 'supertest'
8+
9+
describe('backup', function (this: {
10+
app: Express,
11+
did: string
12+
dbConnection: Connection,
13+
dbName: string,
14+
provider: IpfsPinnerProvider
15+
}) {
16+
const maxStorage = 1000
17+
18+
const setup = async () => {
19+
this.dbConnection = await createSqliteConnection(this.dbName)
20+
this.provider = await ipfsPinnerProviderFactory({ dbConnection: this.dbConnection, ipfsApiUrl, maxStorage })
21+
22+
setupPermissionedApi(this.app, this.provider, mockedLogger)
23+
}
24+
25+
beforeEach(() => {
26+
this.did = 'did:ethr:rsk:testnet:0xce83da2a364f37e44ec1a17f7f453a5e24395c70'
27+
const middleware = (req, res, next) => {
28+
req.user = { did: this.did }
29+
next()
30+
}
31+
this.app = express()
32+
this.app.use(bodyParser.json())
33+
this.app.use(middleware)
34+
})
35+
36+
afterEach(() => deleteDatabase(this.dbConnection, this.dbName))
37+
38+
it('should return an empty array if nothing created', async () => {
39+
this.dbName = 'backup-1.sqlite'
40+
await setup()
41+
42+
const { body } = await request(this.app).get('/backup').expect(200)
43+
44+
expect(body).toEqual([])
45+
})
46+
47+
it('should return an array with the just created content', async () => {
48+
this.dbName = 'backup-2.sqlite'
49+
await setup()
50+
51+
const key = 'TheKey'
52+
const id = await this.provider.create(this.did, key, 'a content')
53+
54+
const { body } = await request(this.app).get('/backup').expect(200)
55+
56+
expect(body).toEqual([{ key, id }])
57+
})
58+
59+
it('should return information related to the logged did even if there is data related to other did', async () => {
60+
this.dbName = 'backup-3.sqlite'
61+
await setup()
62+
63+
const anotherDid = 'did:ethr:rsk:testnet:0xce83da2a364f37e44ec1a17f7f453a5e24395c65'
64+
await this.provider.create(anotherDid, 'AnotherKey', 'a content')
65+
66+
const key = 'TheKey'
67+
const id = await this.provider.create(this.did, key, 'another content')
68+
69+
const { body } = await request(this.app).get('/backup').expect(200)
70+
71+
expect(body).toEqual([{ key, id }])
72+
})
73+
})

0 commit comments

Comments
 (0)