diff --git a/src/aquarius/Aquarius.ts b/src/aquarius/Aquarius.ts index e082394..d69b046 100644 --- a/src/aquarius/Aquarius.ts +++ b/src/aquarius/Aquarius.ts @@ -1,6 +1,7 @@ import { URL } from 'whatwg-url' import { DDO } from '../ddo/DDO' import DID from '../ocean/DID' +import { EditableMetaData } from '../ddo/MetaData' import { Logger } from '../utils' import { WebServiceConnector } from '../ocean/utils/WebServiceConnector' @@ -260,6 +261,90 @@ export class Aquarius { return result } + /** + * Edit Metadata for a DDO. + * @param {did} string DID. + * @param {newMetadata} EditableMetaData Metadata fields & new values. + * @param {String} updated Updated field of the DDO + * @param {String} signature Signature using updated field to verify that the consumer has rights + * @return {Promise} Result. + */ + public async editMetadata( + did: DID | string, + newMetadata: EditableMetaData, + updated: string, + signature: string + ): Promise { + did = did && DID.parse(did) + const fullUrl = `${this.url}${apiPath}/metadata/${did.getDid()}` + const data = Object() + if (newMetadata.description != null) data.description = newMetadata.description + if (newMetadata.title != null) data.title = newMetadata.title + if (newMetadata.servicePrices != null) + data.servicePrices = newMetadata.servicePrices + if (newMetadata.links != null) data.links = newMetadata.links + data.updated = updated + data.signature = signature + const result = await this.fetch + .put(fullUrl, JSON.stringify(data)) + .then((response: any) => { + if (response.ok) { + return response.text + } + this.logger.log( + 'editMetaData failed:', + response.status, + response.statusText + ) + return null + }) + + .catch(error => { + this.logger.error('Error transfering ownership metadata: ', error) + return null + }) + + return result + } + + /** + * Retire a DDO (Delete) + * @param {DID | string} did DID of the asset to update. + * @param {String} updated Updated field of the DDO + * @param {String} signature Signature using updated field to verify that the consumer has rights + * @return {Promise} Result. + */ + public async retire( + did: DID | string, + updated: string, + signature: string + ): Promise { + did = did && DID.parse(did) + const fullUrl = `${this.url}${apiPath}/${did.getDid()}` + const result = await this.fetch + .delete( + fullUrl, + JSON.stringify({ + signature: signature, + updated: updated + }) + ) + .then((response: any) => { + if (response.ok) { + return response.text + } + this.logger.log('retire failed:', response.status, response.statusText) + return null + }) + + .catch(error => { + this.logger.error('Error transfering ownership metadata: ', error) + return null + }) + + return result + } + public getServiceEndpoint(did: DID) { return `${this.url}/api/v1/aquarius/assets/ddo/did:op:${did.getId()}` } diff --git a/src/ddo/MetaData.ts b/src/ddo/MetaData.ts index 34aceb7..f2690d8 100644 --- a/src/ddo/MetaData.ts +++ b/src/ddo/MetaData.ts @@ -276,3 +276,28 @@ export interface MetaData { additionalInformation?: AdditionalInformation curation?: Curation } +/** Warning. serviceIndex is the index of a services in Services array, and not service.index attribute. +Let's assume that you have the following services array: +[ + {"index":1,"type":"access","main":{"price":3}}, + {"index":0,"type":"compute","main":{"price":1}} +] +then calling update with { serviceIndex:1,price:2} will update the 'compute' service, and not the access one +**/ +export interface ServicePrices { + serviceIndex: number + price: string +} + +export interface EditableMetaDataLinks { + name: string + url: string + type: string +} + +export interface EditableMetaData { + description?: string + title?: string + links?: EditableMetaDataLinks[] + servicePrices?: ServicePrices[] +} diff --git a/src/ocean/OceanAssets.ts b/src/ocean/OceanAssets.ts index d3cd89a..9e0b9d5 100644 --- a/src/ocean/OceanAssets.ts +++ b/src/ocean/OceanAssets.ts @@ -1,7 +1,7 @@ import { TransactionReceipt } from 'web3-core' import { SearchQuery } from '../aquarius/Aquarius' import { DDO } from '../ddo/DDO' -import { MetaData } from '../ddo/MetaData' +import { MetaData, EditableMetaData } from '../ddo/MetaData' import { Service, ServiceAccess } from '../ddo/Service' import Account from './Account' import DID from './DID' @@ -384,6 +384,55 @@ export class OceanAssets extends Instantiable { return txReceipt } + /** + * Edit Metadata for a DDO. + * @param {did} string DID. + * @param {newMetadata} EditableMetaData Metadata fields & new values. + * @param {Account} account Ethereum account of owner to sign and prove the ownership. + * @return {Promise} + */ + public async editMetadata( + did: string, + newMetadata: EditableMetaData, + account: Account + ): Promise { + const oldDdo = await this.ocean.aquarius.retrieveDDO(did) + // get a signature + const signature = await this.ocean.utils.signature.signForAquarius( + oldDdo.updated, + account + ) + let result = null + if (signature != null) + result = await this.ocean.aquarius.editMetadata( + did, + newMetadata, + oldDdo.updated, + signature + ) + + return result + } + + /** + * Retire a DDO (Delete) + * @param {did} string DID. + * @param {Account} account Ethereum account of owner to sign and prove the ownership. + * @return {Promise} + */ + public async retire(did: string, account: Account): Promise { + const oldDdo = await this.ocean.aquarius.retrieveDDO(did) + // get a signature + const signature = await this.ocean.utils.signature.signForAquarius( + oldDdo.updated, + account + ) + let result = null + if (signature != null) + result = await this.ocean.aquarius.retire(did, oldDdo.updated, signature) + return result + } + /** * Returns the assets of a consumer. * @param {string} consumer Consumer address. diff --git a/src/ocean/utils/WebServiceConnector.ts b/src/ocean/utils/WebServiceConnector.ts index d34e6fe..49d9137 100644 --- a/src/ocean/utils/WebServiceConnector.ts +++ b/src/ocean/utils/WebServiceConnector.ts @@ -43,13 +43,23 @@ export class WebServiceConnector { }) } - public delete(url: string): Promise { - return this.fetch(url, { - method: 'DELETE', - headers: { - 'Content-type': 'application/json' - } - }) + public delete(url: string, payload?: BodyInit): Promise { + if (payload != null) { + return this.fetch(url, { + method: 'DELETE', + body: payload, + headers: { + 'Content-type': 'application/json' + } + }) + } else { + return this.fetch(url, { + method: 'DELETE', + headers: { + 'Content-type': 'application/json' + } + }) + } } public async downloadFile( diff --git a/test/integration/ocean/AssetOwners.test.ts b/test/integration/ocean/AssetOwners.test.ts index 5df02a8..3f1af67 100644 --- a/test/integration/ocean/AssetOwners.test.ts +++ b/test/integration/ocean/AssetOwners.test.ts @@ -1,7 +1,7 @@ import { assert } from 'chai' import { config } from '../config' import { getMetadata } from '../utils' -import { Ocean, Account } from '../../../src' // @oceanprotocol/squid +import { Ocean, Account, EditableMetaData } from '../../../src' // @oceanprotocol/squid describe('Asset Owners', () => { let ocean: Ocean @@ -84,8 +84,8 @@ describe('Asset Owners', () => { const { length: finalLength1 } = await ocean.assets.consumerAssets( account2.getId() ) - assert.equal(finalLength1 - initialLength, 0) + assert.equal(finalLength1 - initialLength, 0) // Granting access try { await account2.requestTokens( @@ -116,6 +116,76 @@ describe('Asset Owners', () => { assert.equal(aquariusOwner, account2.getId()) }) + it('should be able to update metadata', async () => { + const { id } = await ocean.assets.create(metadata as any, account1) + const links = [ + { + name: 'Sample1', + url: 'http://www.example.com', + type: 'sample' + }, + { + name: 'Sample2', + url: 'http://www.example.net', + type: 'sample' + } + ] + const newPrices = [ + { + serviceIndex: 0, + price: '31000000000000000000' + }, + { + serviceIndex: 2, + price: '31000000000000000000' + } + ] + + const newMetaData = { + title: 'New title', + description: 'New description', + links: links, + servicePrices: newPrices + } + await ocean.assets.editMetadata(id, newMetaData, account1) + + const newDDO = await ocean.assets.resolve(id) + + assert.equal(newDDO.service[0].attributes.main.name, 'New title') + assert.equal( + newDDO.service[0].attributes.additionalInformation.description, + 'New description' + ) + assert.equal(newDDO.service[0].attributes.main.price, '31000000000000000000') + assert.equal(newDDO.service[2].attributes.main.price, '31000000000000000000') + assert.equal( + newDDO.service[0].attributes.additionalInformation.links[0].name, + 'Sample1' + ) + assert.equal( + newDDO.service[0].attributes.additionalInformation.links[0].url, + 'http://www.example.com' + ) + assert.equal( + newDDO.service[0].attributes.additionalInformation.links[1].name, + 'Sample2' + ) + assert.equal( + newDDO.service[0].attributes.additionalInformation.links[1].url, + 'http://www.example.net' + ) + }) + + it('should be able to retire metadata', async () => { + const { id } = await ocean.assets.create(metadata as any, account1) + + await ocean.assets.retire(id, account1) + + const newDDO = await ocean.assets.resolve(id) + + assert.equal(newDDO, null) + }) + it('should add and remove correctly an address to/from FreeWhiteList on an asset', async () => { const ddo = await ocean.assets.create(metadata as any, account1) await ocean.assets.addConsumerToFreeWhiteList(ddo.id, consumer1.getId(), account1)