github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/kat/src/handlers/asset-definitions.ts (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 import Ajv from 'ajv'; 16 import * as utils from '../lib/utils'; 17 import * as ipfs from '../clients/ipfs'; 18 import * as apiGateway from '../clients/api-gateway'; 19 import * as database from '../clients/database'; 20 import RequestError from '../lib/request-handlers'; 21 import indexSchema from '../schemas/indexes.json' 22 import assetDefinitionSchema from '../schemas/asset-definition.json' 23 const log = utils.getLogger('handler/asset-definitions.ts'); 24 25 import { 26 IDBBlockchainData, 27 IDBAssetDefinition, 28 IEventAssetDefinitionCreated, 29 IAssetDefinitionRequest, 30 indexes 31 } from '../lib/interfaces'; 32 import { config } from '../lib/config'; 33 34 const ajv = new Ajv(); 35 36 export const handleGetAssetDefinitionsRequest = (query: object, skip: number, limit: number) => { 37 return database.retrieveAssetDefinitions(query, skip, limit); 38 }; 39 40 export const handleCountAssetDefinitionsRequest = async (query: object) => { 41 return { count: await database.countAssetDefinitions(query) }; 42 }; 43 44 export const handleGetAssetDefinitionRequest = async (assetDefinitionID: string) => { 45 const assetDefinition = await database.retrieveAssetDefinitionByID(assetDefinitionID); 46 if (assetDefinition === null) { 47 throw new RequestError('Asset definition not found', 404); 48 } 49 return assetDefinition; 50 }; 51 52 export const handleCreateAssetDefinitionRequest = async (assetDefinitionID: string, name: string, isContentPrivate: boolean, isContentUnique: boolean, 53 author: string, descriptionSchema: Object | undefined, contentSchema: Object | undefined, indexes: { fields: string[], unique?: boolean }[] | undefined, sync: boolean) => { 54 if (descriptionSchema !== undefined && !ajv.validateSchema(descriptionSchema)) { 55 throw new RequestError('Invalid description schema', 400); 56 } 57 if (contentSchema !== undefined && !ajv.validateSchema(contentSchema)) { 58 throw new RequestError('Invalid content schema', 400); 59 } 60 if (indexes !== undefined && !ajv.validate(indexSchema, indexes)) { 61 throw new RequestError('Indexes do not conform to index schema', 400); 62 } 63 if (await database.retrieveAssetDefinitionByName(name) !== null) { 64 throw new RequestError('Asset definition name conflict', 409); 65 } 66 const timestamp = utils.getTimestamp(); 67 const assetDefinition: IAssetDefinitionRequest = { 68 assetDefinitionID, 69 name, 70 isContentPrivate, 71 isContentUnique, 72 descriptionSchema, 73 contentSchema, 74 indexes 75 } 76 77 let assetDefinitionHash: string; 78 let receipt: string | undefined; 79 80 switch (config.protocol) { 81 case 'ethereum': 82 assetDefinitionHash = utils.ipfsHashToSha256(await ipfs.uploadString(JSON.stringify(assetDefinition))); 83 const apiGatewayResponse = await apiGateway.createAssetDefinition(author, assetDefinitionHash, sync); 84 if (apiGatewayResponse.type === 'async') { 85 receipt = apiGatewayResponse.id; 86 } 87 break; 88 case 'corda': 89 assetDefinitionHash = utils.getSha256(JSON.stringify(assetDefinition)); 90 await createCollection(assetDefinitionID, indexes); 91 break; 92 } 93 await database.upsertAssetDefinition({ 94 assetDefinitionID, 95 author, 96 name, 97 isContentPrivate, 98 isContentUnique, 99 descriptionSchema, 100 assetDefinitionHash, 101 contentSchema, 102 receipt, 103 indexes, 104 submitted: timestamp 105 }); 106 log.info(`New asset definition ${assetDefinitionID} from API request published to blockchain and added to local database`); 107 return assetDefinitionID; 108 }; 109 110 export const handleAssetDefinitionCreatedEvent = async (event: IEventAssetDefinitionCreated, { blockNumber, transactionHash }: IDBBlockchainData) => { 111 let assetDefinition = await ipfs.downloadJSON<IDBAssetDefinition>(utils.sha256ToIPFSHash(event.assetDefinitionHash)); 112 if (!ajv.validate(assetDefinitionSchema, assetDefinition)) { 113 throw new RequestError(`Invalid asset definition content ${JSON.stringify(ajv.errors)}`, 400); 114 } 115 const dbAssetDefinitionByID = await database.retrieveAssetDefinitionByID(assetDefinition.assetDefinitionID); 116 if (dbAssetDefinitionByID !== null) { 117 if (dbAssetDefinitionByID.transactionHash !== undefined) { 118 throw new Error(`Asset definition ID conflict ${assetDefinition.assetDefinitionID}`); 119 } 120 } else { 121 const dbAssetDefinitionByName = await database.retrieveAssetDefinitionByName(assetDefinition.name); 122 if (dbAssetDefinitionByName !== null) { 123 if (dbAssetDefinitionByName.transactionHash !== undefined) { 124 throw new Error(`Asset definition name conflict ${dbAssetDefinitionByName.name}`); 125 } else { 126 await database.markAssetDefinitionAsConflict(dbAssetDefinitionByName.assetDefinitionID, Number(event.timestamp)); 127 } 128 } 129 } 130 131 await database.upsertAssetDefinition({ 132 ...assetDefinition, 133 author: event.author, 134 assetDefinitionHash: event.assetDefinitionHash, 135 timestamp: Number(event.timestamp), 136 blockNumber, 137 transactionHash 138 }); 139 await createCollection(assetDefinition.assetDefinitionID, assetDefinition.indexes); 140 141 log.info(`New asset definition ${assetDefinition.assetDefinitionID} from blockchain event added to local database`); 142 }; 143 144 const createCollection = async (assetDefinitionID: string, assetDefinitionIndexes: indexes | undefined) => { 145 const collectionName = `asset-instance-${assetDefinitionID}`; 146 let indexes: indexes = [{ fields: ['assetInstanceID'], unique: true }, { fields: ['author'], unique: false }]; 147 if (assetDefinitionIndexes !== undefined) { 148 indexes = indexes.concat(assetDefinitionIndexes) 149 } 150 await database.createCollection(collectionName, indexes); 151 };