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  };