github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/kat/src/routers/asset-instances.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 { Router, Request } from 'express';
    16  import RequestError from '../lib/request-handlers';
    17  import * as assetInstancesHandler from '../handlers/asset-instances';
    18  import { constants, requestKeys, streamToString } from '../lib/utils';
    19  import Busboy from 'busboy';
    20  import * as utils from '../lib/utils';
    21  import { IRequestMultiPartContent } from '../lib/interfaces';
    22  import { config } from '../lib/config';
    23  
    24  const router = Router();
    25  
    26  router.get('/:assetDefinitionID', async (req, res, next) => {
    27    try {
    28      const skip = Number(req.query.skip || 0);
    29      const limit = Number(req.query.limit || constants.DEFAULT_PAGINATION_LIMIT);
    30      if (isNaN(skip) || isNaN(limit)) {
    31        throw new RequestError('Invalid skip / limit', 400);
    32      }
    33      res.send(await assetInstancesHandler.handleGetAssetInstancesRequest(req.params.assetDefinitionID, {}, {}, skip, limit));
    34    } catch (err) {
    35      next(err);
    36    }
    37  });
    38  
    39  router.get('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => {
    40    try {
    41      const content = req.query.content === 'true';
    42      res.send(await assetInstancesHandler.handleGetAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, content));
    43    } catch (err) {
    44      next(err);
    45    }
    46  });
    47  
    48  router.post('/search/:assetDefinitionID', async (req, res, next) => {
    49    try {
    50      const skip = Number(req.body.skip || 0);
    51      const limit = Number(req.body.limit || constants.DEFAULT_PAGINATION_LIMIT);
    52      if (req.body.count !== true && (isNaN(skip) || isNaN(limit))) {
    53        throw new RequestError('Invalid skip / limit', 400);
    54      }
    55      if (!req.body.query) {
    56        throw new RequestError('Missing search query', 400);
    57      }
    58      res.send(req.body.count === true ?
    59        await assetInstancesHandler.handleCountAssetInstancesRequest(req.params.assetDefinitionID, req.body.query) :
    60        await assetInstancesHandler.handleGetAssetInstancesRequest(req.params.assetDefinitionID, req.body.query, req.body.sort || {}, skip, limit)
    61      );
    62    } catch (err) {
    63      next(err);
    64    }
    65  });
    66  
    67  router.post('/:assetDefinitionID', async (req, res, next) => {
    68    try {
    69      let assetInstanceID: string;
    70      const sync = req.query.sync === 'true';
    71  
    72      if (req.headers["content-type"]?.startsWith('multipart/form-data')) {
    73        let description: Object | undefined;
    74        const formData = await extractDataFromMultipartForm(req);
    75        if (formData.description !== undefined) {
    76          try {
    77            description = JSON.parse(await formData.description);
    78          } catch (err) {
    79            throw new RequestError(`Invalid description. ${err}`, 400);
    80          }
    81        }
    82        if (!formData.author ||!utils.isAuthorValid(formData.author, config.protocol)) {
    83          throw new RequestError('Missing or invalid asset instance author', 400);
    84        }
    85        assetInstanceID = await assetInstancesHandler.handleCreateUnstructuredAssetInstanceRequest(formData.author, req.params.assetDefinitionID, description, formData.contentStream, formData.contentFileName, formData.isContentPrivate, req.body.participants, sync);
    86      } else {
    87        if (!utils.isAuthorValid(req.body.author, config.protocol)) {
    88          throw new RequestError('Missing or invalid asset instance author', 400);
    89        }
    90        if (!(typeof req.body.content === 'object' && req.body.content !== null)) {
    91          throw new RequestError('Missing or invalid asset content', 400);
    92        }
    93        if(req.body.isContentPrivate !== undefined && typeof req.body.isContentPrivate !== 'boolean') {
    94          throw new RequestError('Invalid isContentPrivate', 400);
    95        }
    96        assetInstanceID = await assetInstancesHandler.handleCreateStructuredAssetInstanceRequest(req.body.author, req.params.assetDefinitionID, req.body.description, req.body.content, req.body.isContentPrivate, req.body.participants, sync);
    97      }
    98      res.send({ status: sync ? 'success' : 'submitted', assetInstanceID });
    99    } catch (err) {
   100      next(err);
   101    }
   102  });
   103  
   104  router.put('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => {
   105    try {
   106      switch (req.body.action) {
   107        case 'set-property':
   108          if (!req.body.key) {
   109            throw new RequestError('Missing asset property key', 400);
   110          }
   111          if (!req.body.value) {
   112            throw new RequestError('Missing asset property value', 400);
   113          }
   114          if (!utils.isAuthorValid(req.body.author, config.protocol)) {
   115            throw new RequestError('Missing or invalid asset property author', 400);
   116          }
   117          const sync = req.query.sync === 'true';
   118          await assetInstancesHandler.handleSetAssetInstancePropertyRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.author, req.body.key, req.body.value, sync);
   119          res.send({ status: sync ? 'success' : 'submitted' });
   120          break;
   121        case 'push':
   122          if (!req.body.memberAddress) {
   123            throw new RequestError('Missing member address', 400);
   124          }
   125          await assetInstancesHandler.handlePushPrivateAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.memberAddress);
   126          res.send({ status: 'success' });
   127          break;
   128        default:
   129          throw new RequestError('Missing or invalid action');
   130      }
   131    } catch (err) {
   132      next(err);
   133    }
   134  });
   135  
   136  router.patch('/:assetDefinitionID/:assetInstanceID', async (req, res, next) => {
   137    try {
   138      if (!utils.isAuthorValid(req.body.requester, config.protocol)) {
   139        throw new RequestError(`Missing requester`);
   140      }
   141      await assetInstancesHandler.handleAssetInstanceTradeRequest(req.params.assetDefinitionID, req.body.requester, req.params.assetInstanceID, req.body.metadata);
   142      res.send({ status: 'success' });
   143    } catch (err) {
   144      next(err);
   145    }
   146  });
   147  
   148  router.post('/:assetDefinitionID/:assetInstanceID/push', async (req, res, next) => {
   149    try {
   150      if (!req.body.memberAddress) {
   151        throw new RequestError('Missing member address', 400);
   152      }
   153      await assetInstancesHandler.handlePushPrivateAssetInstanceRequest(req.params.assetDefinitionID, req.params.assetInstanceID, req.body.memberAddress);
   154      res.send({ status: 'success' });
   155    } catch (err) {
   156      next(err);
   157    }
   158  });
   159  
   160  const extractDataFromMultipartForm = (req: Request): Promise<IRequestMultiPartContent> => {
   161    return new Promise(async (resolve, reject) => {
   162      let author: string | undefined;
   163      let assetDefinitionID: string | undefined;
   164      let description: Promise<string> | undefined;
   165      let isContentPrivate: boolean | undefined = undefined;
   166      req.pipe(new Busboy({ headers: req.headers })
   167        .on('field', (fieldname, value) => {
   168          switch (fieldname) {
   169            case requestKeys.ASSET_AUTHOR: author = value; break;
   170            case requestKeys.ASSET_DEFINITION_ID: assetDefinitionID = value; break;
   171            case requestKeys.ASSET_IS_CONTENT_PRIVATE: isContentPrivate = value === 'true'; break;
   172          }
   173        }).on('file', (fieldname, readableStream, fileName) => {
   174          switch (fieldname) {
   175            case requestKeys.ASSET_DESCRIPTION: description = streamToString(readableStream); break;
   176            case requestKeys.ASSET_CONTENT: resolve({ author, assetDefinitionID, description, contentStream: readableStream, contentFileName: fileName, isContentPrivate }); break;
   177            default: readableStream.resume();
   178          }
   179        })).on('finish', () => {
   180          reject(new RequestError('Missing content', 400));
   181        });
   182    });
   183  };
   184  
   185  export default router;