UNPKG

@windingtree/wt-write-api

Version:

API to write data to the Winding Tree platform

378 lines (347 loc) 9.98 kB
const _ = require('lodash'); const Web3Utils = require('web3-utils'); const { validateDescription, validateRatePlans, validateAvailability, validateNotifications, validateBooking, validateDataUri, validateGuarantee, validateAddress, validateContacts, validateOrgName, } = require('./validators'); /* A declarative description of hotel data. */ const DATA_INDEX_FIELDS = [ { name: 'description', dataIndexName: 'descriptionUri', required: true, pointer: true, // Is this a pointer to another subdocument? validator: validateDescription, }, { name: 'ratePlans', dataIndexName: 'ratePlansUri', required: false, pointer: true, validator: validateRatePlans, }, { name: 'availability', dataIndexName: 'availabilityUri', required: false, pointer: true, validator: validateAvailability, }, { name: 'notifications', dataIndexName: 'notificationsUri', required: false, pointer: false, validator: validateNotifications, }, { name: 'booking', dataIndexName: 'bookingUri', required: false, pointer: false, validator: validateBooking, }, { name: 'defaultLocale', dataIndexName: 'defaultLocale', required: false, pointer: false, validator: () => {}, // TODO }, { name: 'guarantee', dataIndexName: 'guarantee', required: false, pointer: false, validator: validateGuarantee, }, ]; const NO_DATA_INDEX_FIELDS = [ { name: 'orgJson', required: true, pointer: false, validator: validateDataUri, }, { name: 'hash', required: true, pointer: false, validator: () => {}, // TODO }, ]; const LEGAL_ENTITY_FIELDS = [ { name: 'name', dataIndexName: 'name', required: true, pointer: false, validator: validateOrgName, }, { name: 'address', dataIndexName: 'address', required: true, pointer: false, validator: validateAddress, }, { name: 'contact', dataIndexName: 'contact', required: true, pointer: false, validator: validateContacts, }, ]; const DATA_INDEX_FIELD_NAMES = _.map(DATA_INDEX_FIELDS, 'name'); const NO_DATA_INDEX_FIELD_NAMES = _.map(NO_DATA_INDEX_FIELDS, 'name'); const LEGAL_ENTITY_FIELD_NAMES = _.map(LEGAL_ENTITY_FIELDS, 'name'); /** * Gateway to the WT platform. Wraps wtLibs. */ class WT { constructor (wtLibs, entrypointAddress) { this.wtLibs = wtLibs; this.entrypointAddress = entrypointAddress; } _getEntrypoint () { return this.wtLibs.getEntrypoint(this.entrypointAddress); } async _getFactory () { return this._getEntrypoint().getOrganizationFactory(); } async _getDirectory () { return this._getEntrypoint().getSegmentDirectory('hotels'); } _getOrganization (address) { return this.wtLibs.getUpdateableOrganization(address); } async getDirectoryAddress () { if (!this._directory) { this._directory = await this._getDirectory(); } return this._directory.address; } async getFactoryAddress () { if (!this._factory) { this._factory = await this._getFactory(); } return this._factory.address; } async _addHotel (withWallet, orgJsonUri, orgJsonHash) { const factory = await this._getFactory(); return withWallet(async (wallet) => { const { organization, transactionData, eventCallbacks } = await factory.createAndAddOrganization({ owner: wallet.getAddress(), orgJsonUri: orgJsonUri, orgJsonHash: orgJsonHash, }, await this.getDirectoryAddress()); await wallet.signAndSendTransaction(transactionData, eventCallbacks); // Once the transaction is mined, one of the callbacks // sets the address. return (await organization).address; }); } async _updateHotel (withWallet, orgJsonUri, orgJsonHash, hotelAddress) { const organization = this._getOrganization(hotelAddress); organization.orgJsonUri = orgJsonUri; organization.orgJsonHash = orgJsonHash; await withWallet(async (wallet) => { const transactionDataList = await organization.updateOnChainData({ from: wallet.getAddress(), }); const transactions = []; for (const { transactionData, eventCallbacks } of transactionDataList) { transactions.push(wallet.signAndSendTransaction(transactionData, eventCallbacks)); } await Promise.all(transactions); }); return hotelAddress; } /** * Return a wallet. * * @param {Object} walletData */ createWallet (walletData) { return this.wtLibs.createWallet(walletData); } /** * Create or update hotel with the given orgJsonUri. * * @param {Function} withWallet * @param {string} orgJsonUri * @param {string} orgJsonHash * @param {string} hotelAddress (optional) * @return {Promise<string>} Etherum address of the uploaded * data. */ upload (withWallet, orgJsonUri, orgJsonHash, hotelAddress) { if (hotelAddress) { return this._updateHotel(withWallet, orgJsonUri, orgJsonHash, hotelAddress); } return this._addHotel(withWallet, orgJsonUri, orgJsonHash); } getContentsHash (contents) { return Web3Utils.soliditySha3(contents); } /** * Remove the hotel from WT index. * * @param {Function} withWallet * @param {string} hotelAddress * @return {Promise<void>} */ async remove (withWallet, hotelAddress) { const directory = this._getDirectory(); await withWallet(async (wallet) => { const { transactionData, eventCallbacks } = await directory.remove({ address: hotelAddress, owner: wallet.getAddress(), }); await wallet.signAndSendTransaction(transactionData, eventCallbacks); }); } async _getRawOrgJson (hotelAddress) { const hotel = await this._getOrganization(hotelAddress); return hotel.orgJson; } async _formatOffChainDataIndex (rawIndex) { const contents = {}, indexContents = await rawIndex.contents; for (const field of DATA_INDEX_FIELDS) { const name = field.dataIndexName; if (field.pointer) { contents[name] = _.get(indexContents, [name, 'ref']); } else { contents[name] = _.get(indexContents, [name]); } } contents.dataFormatVersion = _.get(indexContents, 'dataFormatVersion'); return { ref: rawIndex.ref, contents: contents, }; } async _formatOffChainOrgJson (rawOrgJson) { return { ref: rawOrgJson.ref, contents: await rawOrgJson.contents, }; } async getOrgJson (hotelAddress) { return this._formatOffChainOrgJson(await this._getRawOrgJson(hotelAddress)); } async getOrgJsonHash (hotelAddress) { const hotel = await this._getOrganization(hotelAddress); return hotel.orgJsonHash; } /** * Return the hotel's data index. * * @param {string} hotelAddress * @return {Promise<Object>} */ async getDataIndex (hotelAddress) { const addr = await this._getRawDataIndex(hotelAddress); return addr ? this._formatOffChainDataIndex(addr) : {}; } async _getRawDataIndex (hotelAddress) { const hotel = this._getOrganization(hotelAddress); const apis = await hotel.getWindingTreeApi(); return apis.hotel[0] || {}; } async resolveUnregisteredDataIndex (orgJsonUri) { const hotel = this._getOrganization(); hotel.orgJsonUri = orgJsonUri; const apis = await hotel.getWindingTreeApi(); return apis.hotel[0] ? this._formatOffChainDataIndex(apis.hotel[0]) : {}; } /** * Return resolved hotel data. * * @param {string} hotelAddress * @param {Array} fieldNames (required) Only include these fields. * @return {Promise<Object>} */ async getDocuments (hotelAddress, fieldNames) { const orgJson = await this._getRawOrgJson(hotelAddress); const orgJsonContents = await orgJson.contents, rawIndex = await this._getRawDataIndex(hotelAddress), data = {}, contents = await rawIndex.contents; for (const fieldName of fieldNames) { const dataIndexName = (DATA_INDEX_FIELDS.find((f) => f.name === fieldName)).dataIndexName; const doc = contents[dataIndexName]; if (doc) { if (doc.toPlainObject) { data[`${fieldName}`] = (await doc.toPlainObject()).contents; } else { // No pointer. data[`${fieldName}`] = doc; } } } // TODO this is hacky and should be refactored in a similar way to hotel data return { legalEntity: orgJsonContents.legalEntity, guarantee: orgJsonContents.guarantee, hotel: data, }; } /** * Return true, if the provided ethereum address is non-zero * and valid, false otherwise. * * @param {string} address */ isValidAddress (address) { if (!address || !Web3Utils.isAddress(address)) { return false; } return String(address) !== '0x0000000000000000000000000000000000000000'; } /** * Transfer hotel to a different manager. * * @param {Function} withWallet * @param {string} hotelAddress * @param {string} managerAddress * @return {Promise<void>} */ async transferHotel (withWallet, hotelAddress, managerAddress) { const hotel = this._getOrganization(hotelAddress); await withWallet(async (wallet) => { const { transactionData, eventCallbacks } = await hotel.transferOnChainOwnership(managerAddress, { from: wallet.getAddress(), }); await wallet.signAndSendTransaction(transactionData, eventCallbacks); }); } }; let _WT; /** * Get the previously set WT instance. */ function get () { if (!_WT) { throw new Error('No WT instance has been set!'); } return _WT; } /** * Set WT instance during runtime. */ function set (wt) { _WT = wt; } module.exports = { WT, get, set, DATA_INDEX_FIELDS, DATA_INDEX_FIELD_NAMES, NO_DATA_INDEX_FIELDS, NO_DATA_INDEX_FIELD_NAMES, LEGAL_ENTITY_FIELDS, LEGAL_ENTITY_FIELD_NAMES, };