├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README.old.md ├── package.json ├── src ├── apiGenerator.ts ├── arrayTypeProperty.json ├── command.ts ├── decorators │ ├── create.decorator.ts │ ├── getAll.decorator.ts │ ├── getById.decorator.ts │ ├── index.ts │ └── service.decorator.ts ├── generators │ ├── _.env.ts.ejs │ ├── _app.ts.ejs │ ├── _package.json.ejs │ ├── _selfgenfabriccontext.ts.ejs │ ├── _smartContractControllers.ts.ejs │ ├── _smartContractModels.ts.ejs │ ├── _smartRoutes.ts.ejs │ ├── _tsconfig-api.json.ejs │ ├── _utils.ts.ejs │ ├── app.ts.ts │ ├── base.ts │ ├── controller.ts.ts │ ├── convector.ts.ts │ ├── env.ts.ts │ ├── package.json.ts │ ├── pm2config.json.ts │ ├── router.ts.ts │ ├── swagger.json.ts │ └── tsconfig.json.ts ├── index.ts ├── models │ ├── apiConfigurationFile.ts │ ├── env.smart-model.ts │ ├── index.ts │ ├── readme.model.ts │ ├── smartApiController.smart-model.ts │ ├── smartApiSwaggerYaml.smart-model.ts │ ├── smartContractControllers.smart-model.ts │ ├── smartContractModels.smart-model.ts │ ├── smartModel.ts │ ├── smartRouter.smart-model.ts │ └── smartRoutes.smart-model.ts ├── restApi.ts └── utils │ ├── analytics.ts │ ├── apiConfig.ts │ ├── apiEnvironment.ts │ ├── debug.ts │ ├── index.ts │ ├── reflectionUtils.ts │ ├── sysWrapper.ts │ └── utils.ts ├── templates ├── _smartApiController.ts.ejs ├── _smartApiSwagger.yaml.ejs └── _smartRouter.ts.ejs ├── templates_scripts └── generate_api_template.bash ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directories 7 | **/node_modules 8 | node_modules 9 | 10 | # Mac 11 | .DS_Store 12 | 13 | dist 14 | docs 15 | .convector-dev-env 16 | chaincode 17 | package-lock.json 18 | 19 | .history -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.1.4 4 | 5 | - Fix: verb added in the name of the functions broke API 6 | 7 | ## 0.1.3 8 | 9 | - Feat: version exposing `conv-rest-api --version` 10 | 11 | ## 0.1.2 12 | 13 | - Fix: DELETE verb body controller generation 14 | - Feat: prevent functions names collision 15 | 16 | ## 0.1.0-0.1.1 17 | 18 | This project is derived from the work of Luca Tamburrano but simplified in this version in terms of features and configurations. Existing features in previous version will be added to the roadmap. 19 | 20 | - Auto-generator for ExpressJS backend 21 | - Routes 22 | - Controllers 23 | - Simplified configuration through a JSON file 24 | - Extract parameters from smart contract and dynamically build them 25 | - Support basic verbs 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # conv-rest-api 2 | 3 | This project is under development and will soon be integrated into the [Convector CLI](https://github.com/worldsibu/convector-cli). 4 | 5 | This project aims to provide an easy way for generating automatically client API for Convector projects. 6 | 7 | ## How to use 8 | 9 | To generate an API, you first need a Convector CLI generated project. Since the `conv-rest-api` is based on the Convector CLI standards and conventions, so manually projects generated may not work with it. 10 | 11 | In the **root of your project** create a json file called `api.json`. This structure will map the smart contract to the Express API. 12 | 13 | ```json 14 | [ 15 | { 16 | "function": "", 17 | "controller": "", 18 | "verb": "get|post|put|delete" 19 | }, 20 | { 21 | ... 22 | } 23 | ] 24 | ``` 25 | 26 | ### Example 27 | 28 | If your smart contract project has the following structure: 29 | 30 | * `./packages/participants/src/participant.controller.ts` 31 | * `./packages/person/src/person.controller.ts` 32 | 33 | And the controller files look like this: 34 | 35 | #### participant.controller.ts 36 | 37 | ```ts 38 | @Controller('participant') 39 | export class ParticipantController extends ConvectorController { 40 | get fullIdentity(): ClientIdentity { 41 | const stub = (BaseStorage.current as any).stubHelper; 42 | return new ClientIdentity(stub.getStub()); 43 | }; 44 | 45 | @Invokable() 46 | public async register( 47 | @Param(yup.string()) 48 | id: string, 49 | @Param(yup.string()) 50 | name: string, 51 | ) { 52 | //... 53 | } 54 | } 55 | ``` 56 | 57 | #### person.controller.ts 58 | 59 | ```ts 60 | @Controller('person') 61 | export class PersonController extends ConvectorController { 62 | @GetAll('Person') 63 | @Invokable() 64 | public async getone( 65 | @Param(Person) 66 | id: string) { 67 | // ... 68 | } 69 | @Create('Person') 70 | @Invokable() 71 | public async create( 72 | @Param(Person) 73 | person: Person 74 | ) { 75 | // ... 76 | } 77 | } 78 | ``` 79 | 80 | Then your `api.json` 81 | 82 | ```json 83 | [ 84 | { 85 | "function": "register", 86 | "controller": "ParticipantController" 87 | }, 88 | { 89 | "function": "create", 90 | "verb": "post", 91 | "controller": "PersonController" 92 | }, 93 | { 94 | "function": "getone", 95 | "verb": "get", 96 | "controller": "PersonController" 97 | } 98 | ] 99 | ``` 100 | 101 | Once that's ready, globally install the `conv-rest-api` util and generate your API! 102 | 103 | > Beware that it will *remove* and recreate a folder in your root `./packages/` folder called `server`. 104 | 105 | ```bash 106 | npm i -g @worldsibu/conv-rest-api 107 | 108 | # Inside your Convector CLI generated project's root 109 | conv-rest-api generate api -c -f ./ 110 | # I.e.: conv-rest-api generate api -c person -f ./org1.person.config.json 111 | 112 | # Compile everything 113 | [npx] lerna bootstrap 114 | 115 | # Start the server 116 | [npx] lerna run start --scope server --stream 117 | ``` 118 | 119 | ## Support 120 | 121 | * For recommendations, feature requests, or bugs go to our [issues section](/issues). 122 | * News on Convector, subscribe to our [Newsletter](https://worldsibu.tech/subscribe/). 123 | * Need support? Chat directly with our team and the growing community, join our [Discord](https://discord.gg/twRwpWt). 124 | 125 | ## Contributions 126 | 127 | Special thanks to Luca Tamburrano for starting this amazingly useful project for the community. 128 | -------------------------------------------------------------------------------- /README.old.md: -------------------------------------------------------------------------------- 1 | # convector-rest-api 2 | 3 | This project aims to provide an easy way for generating automatically client API for Convector projects. 4 | 5 | For showing and explaining how it works we'll start from the SupplyChain example that you can find here: https://github.com/xcottos/convector-example-supplychain-master and we'll generate automatically the client API. 6 | 7 | First clone the SupplyChain project: 8 | 9 | ``` 10 | git clone https://github.com/xcottos/convector-example-supplychain-master 11 | ``` 12 | 13 | I am not explaining again here how the project works and its internals (you can find it in the project docs) 14 | 15 | ## Dependencies 16 | 17 | The first thing we need to do is to change the **lerna.json** of the project in order to exclude from the hoisting the @types/bytebuffer. This will prevent exceptions in the future compilation of the API application. 18 | The lerna.json should look like: 19 | 20 | ``` 21 | { 22 | "packages": [ 23 | "packages/*" 24 | ], 25 | "version": "0.1.0", 26 | "command": { 27 | "bootstrap": { 28 | "hoist": true, 29 | "nohoist":[ 30 | "@types/bytebuffer" 31 | ] 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | The next step is adding as a **dependency** the package **convector-rest-api-decorators** in the **package.json** of the chaincode that is located in **packages/< chaincode name >-cc/**. In the supplychain example this is located in the **convector-example-supplychain-master/packages/supplychainchaincode-cc** folder that contains the code of our chaincode. 38 | 39 | So the **package.json** will be: 40 | 41 | ```javascript 42 | { 43 | "name": "supplychainchaincode-cc", 44 | "version": "0.1.0", 45 | "description": "Chaincodes package for testnewchaincode", 46 | "main": "./dist/src/index.js", 47 | "typings": "./dist/src/index.d.ts", 48 | "files": [ 49 | "dist/*" 50 | ], 51 | "scripts": { 52 | "clean": "rimraf dist client", 53 | "build": "npm run clean && tsc", 54 | "prepare": "npm run build", 55 | "test": "npm run build && mocha -r ts-node/register tests/*.spec.ts --reporter spec" 56 | }, 57 | "dependencies": { 58 | "yup": "^0.26.6", 59 | "reflect-metadata": "^0.1.12", 60 | "@worldsibu/convector-core": "~1.3.0", 61 | "@worldsibu/convector-platform-fabric": "~1.3.0", 62 | "@worldsibu/convector-rest-api-decorators": "1.0.5" 63 | }, 64 | "devDependencies": { 65 | "@types/node": "^10.12.5", 66 | "@worldsibu/convector-storage-couchdb": "~1.3.0", 67 | "rimraf": "^2.6.2", 68 | "ts-node": "^8.0.2", 69 | "mocha": "^5.0.3", 70 | "chai": "^4.1.2", 71 | "@types/mocha": "^5.2.5", 72 | "@types/chai": "^4.1.4" 73 | } 74 | } 75 | 76 | ``` 77 | 78 | Now in the root of the project (in our scenario is **convector-example-supplychain-master**) you can run the command: 79 | 80 | ``` 81 | npm i 82 | ``` 83 | that will install all the dependencies (included the new one just introduced in the package.json) that we'll use in the next steps) 84 | 85 | Now that the dependencies are installed we can pass to the configuration of the api generator that can be logically divided in: 86 | 87 | + Infrastructure configuration 88 | + API configuration 89 | 90 | ## Infrastructure configuration 91 | 92 | For the **infrastructure configuration** you need to create a file in the root of the project (in our scenario is **convector-example-supplychain-master**) called **api.json** that contains the infrastructure parameters. 93 | 94 | ```javascript 95 | { 96 | "selected":"dev", 97 | "environments": [ 98 | { 99 | "name":"dev", 100 | "PORT":"3000", 101 | "LOG_LEVEL":"debug", 102 | "REQUEST_LIMIT":"100kb", 103 | "SESSION_SECRET":"mySecret", 104 | "SWAGGER_API_SPEC":"/spec", 105 | "KEYSTORE":"../../../fabric-hurl/.hfc-org1", 106 | "USERCERT":"admin", 107 | "ORGCERT":"org1", 108 | "NETWORKPROFILE":"../../../fabric-hurl/network-profiles/org1.network-profile.yaml", 109 | "CHANNEL":"ch1", 110 | "CHAINCODE":"supplychainchaincode", 111 | "COUCHDBVIEW":"ch1_supplychainchaincode", 112 | "COUCHDB_PORT":"5984", 113 | "COUCHDB_HOST":"localhost", 114 | "COUCHDB_PROTOCOL":"http" 115 | }, 116 | { 117 | "name":"prod", 118 | "PORT":"3000", 119 | "LOG_LEVEL":"error", 120 | "REQUEST_LIMIT":"100kb", 121 | "SESSION_SECRET":"mySecret", 122 | "SWAGGER_API_SPEC":"/spec", 123 | "KEYSTORE":"../../../fabric-hurl/.hfc-org1", 124 | "USERCERT":"admin", 125 | "ORGCERT":"org1", 126 | "NETWORKPROFILE":"../../../fabric-hurl/network-profiles/org1.network-profile.yaml", 127 | "CHANNEL":"ch1", 128 | "CHAINCODE":"supplychainchaincode", 129 | "COUCHDBVIEW":"ch1_supplychainchaincode", 130 | "COUCHDB_PORT":"5984", 131 | "COUCHDB_HOST":"localhost", 132 | "COUCHDB_PROTOCOL":"http" 133 | } 134 | ] 135 | } 136 | ``` 137 | 138 | In this file we defined 2 configurations that we named **dev** and **prod** and that represents respectively, the configuration for the development and the production environments. 139 | 140 | The **selected** parameter contains the name of the environment that will be used during the API generation. 141 | 142 | The other parameters are: 143 | 144 | + **PORT**: the port number of the server that will answer to the API requests. 145 | + **LOG_LEVEL**: the log level of the app. The app uses **pino** for the logging 146 | + **REQUEST_LIMIT**: the limit in kb of the request that can reach the API when invoked. 147 | + **SESSION_SECRET**: used to parse and match session cookies 148 | + **SWAGGER_API_SPEC**: `apiPath ` property of the Swagger. Location of the swagger docs 149 | + **KEYSTORE**: The folder that contains the hurl (Hyperledger Fabric) keystore. 150 | + **USERCERT**: the name of the hurl (Hyperledger Fabric) identity that will perform the API calls 151 | + **ORGCERT**: The organization of the **USERCERT** identity 152 | + **NETWORKPROFILE**: Location of the yaml file that contains the hurl (Hyperledger Fabric) network definition 153 | + **CHANNEL**: the channel of the peer the chaincode invoked is installed in 154 | + **CHAINCODE**: chaincode name 155 | + **COUCHDBVIEW**: name of the couchdb view 156 | + **COUCHDB_PORT**: the port where couhdb is in listening 157 | + **COUCHDB_HOST**: the host where couchdb is installed 158 | + **COUCHDB_PROTOCOL**: the protocol used by couchdb 159 | 160 | **Attention:** for the **KEYSTORE** and the **NETWORKPROFILE** variables the paths you see above are not the most common paths since the supplychain example uses custom paths for hurley (passing the parameter **-p** during the invoke). So adjust these paths accordingly with your usage of hurley. 161 | 162 | These last COUCHDB variables are not used yet. 163 | 164 | ## API configuration 165 | 166 | The **API configuration** instead is achieved **annotating** the methods in the chaincode controller (usually located in **packages/< chaincode name >-cc/src folder**) with the following possibilities: 167 | 168 | + **@Create(< model class >)**: It tells the generator to consider this method as a **post** method for generating instances of the model which class is ****. For example in the controller file **packages/supplychainchaincode-cc/src/supplychainchaincode.controller.ts**: 169 | 170 | ```javascript 171 | @Create('Supplier') 172 | @Invokable() 173 | public async createSupplier( 174 | @Param(Supplier) 175 | supplier: Supplier 176 | ) { 177 | await supplier.save(); 178 | } 179 | ``` 180 | 181 | The annotation tells the generator that the generated API method that will wrap the invocation of this method, will be a post method that will have as parameter and object of type **Supplier** and will create an instance of it once invoked 182 | 183 | + **@GetById(< model class >)** It tells the generator to consider this method as a **get** method for retrieving instances of the model which class is ****, and passing as argument its **id**. For example: 184 | 185 | ```javascript 186 | @GetById('Supplier') 187 | @Invokable() 188 | public async getSupplierById( 189 | @Param(yup.string()) 190 | supplierId: string 191 | ) 192 | { 193 | const supplier = await Supplier.getOne(supplierId); 194 | return supplier; 195 | } 196 | ``` 197 | 198 | + **@GetAll(< model class >)** It tells the generator to consider this method as a **get** method for retrieving all the instances of the model which class is **** For example: 199 | 200 | ```javascript 201 | @GetAll('Supplier') 202 | @Invokable() 203 | public async getAllSuppliers() 204 | { 205 | const storedSuppliers = await Supplier.getAll('io.worldsibu.Supplier'); 206 | return storedSuppliers; 207 | } 208 | ``` 209 | 210 | + **@Service()** It tells the generator to consider this method as a **post** method that doesn't return any result and will have as parameter an object that will contain as properties the parameters to be passed to the chaincode controller function. It will generate an API wrapper accordingly (it will be better explained once described the **router.ts** generated file and the **API.yaml** file). 211 | For example: 212 | 213 | ```javascript 214 | @Service() 215 | @Invokable() 216 | public async fetchRawMaterial( 217 | @Param(yup.string()) 218 | supplierId: string, 219 | @Param(yup.number()) 220 | rawMaterialSupply: number 221 | ) { 222 | const supplier = await Supplier.getOne(supplierId); 223 | supplier.rawMaterialAvailable = supplier.rawMaterialAvailable + rawMaterialSupply; 224 | await supplier.save(); 225 | } 226 | ``` 227 | 228 | In our supplychain scenario the resulting **supplychainchaincode.controller.ts** file will be: 229 | 230 | ```javascript 231 | import * as yup from 'yup'; 232 | import { 233 | Controller, 234 | ConvectorController, 235 | Invokable, 236 | Param 237 | } from '@worldsibu/convector-core-controller'; 238 | 239 | import { Supplier } from './Supplier.model'; 240 | import { Manufacturer } from './Manufacturer.model'; 241 | import { Distributor } from './Distributor.model'; 242 | import { Retailer } from './Retailer.model'; 243 | import { Customer } from './Customer.model'; 244 | 245 | import { GetById, GetAll, Create, Service } from '@worldsibu/convector-rest-api-decorators'; 246 | 247 | @Controller('supplychainchaincode') 248 | export class SupplychainchaincodeController extends ConvectorController { 249 | 250 | @Create('Supplier') 251 | @Invokable() 252 | public async createSupplier( 253 | @Param(Supplier) 254 | supplier: Supplier 255 | ) { 256 | await supplier.save(); 257 | } 258 | 259 | @Create('Manufacturer') 260 | @Invokable() 261 | public async createManufacturer( 262 | @Param(Manufacturer) 263 | manufacturer: Manufacturer 264 | ) { 265 | await manufacturer.save(); 266 | } 267 | 268 | @Create('Distributor') 269 | @Invokable() 270 | public async createDistributor( 271 | @Param(Distributor) 272 | distributor: Distributor 273 | ) { 274 | await distributor.save(); 275 | } 276 | 277 | @Create('Retailer') 278 | @Invokable() 279 | public async createRetailer( 280 | @Param(Retailer) 281 | retailer: Retailer 282 | ) { 283 | await retailer.save(); 284 | } 285 | 286 | @Create('Customer') 287 | @Invokable() 288 | public async createCustomer( 289 | @Param(Customer) 290 | customer: Customer 291 | ) { 292 | await customer.save(); 293 | } 294 | 295 | @GetAll('Supplier') 296 | @Invokable() 297 | public async getAllSuppliers() 298 | { 299 | const storedSuppliers = await Supplier.getAll(); 300 | return storedSuppliers; 301 | } 302 | 303 | @GetById('Supplier') 304 | @Invokable() 305 | public async getSupplierById( 306 | @Param(yup.string()) 307 | supplierId: string 308 | ) 309 | { 310 | const supplier = await Supplier.getOne(supplierId); 311 | return supplier; 312 | } 313 | 314 | @GetAll('Manufacturer') 315 | @Invokable() 316 | public async getAllManufacturers() 317 | { 318 | const storedManufacturers = await Manufacturer.getAll(); 319 | return storedManufacturers; 320 | } 321 | 322 | @GetById('Manufacturer') 323 | @Invokable() 324 | public async getManufacturerById( 325 | @Param(yup.string()) 326 | manufacturerId: string 327 | ) 328 | { 329 | const manufacturer = await Manufacturer.getOne(manufacturerId); 330 | return manufacturer; 331 | } 332 | 333 | @GetAll('Distributor') 334 | @Invokable() 335 | public async getAllDistributors() 336 | { 337 | const storedDistributors = await Distributor.getAll(); 338 | return storedDistributors 339 | } 340 | 341 | @GetById('Distributor') 342 | @Invokable() 343 | public async getDistributorById( 344 | @Param(yup.string()) 345 | distributorId: string 346 | ) 347 | { 348 | const distributor = await Distributor.getOne(distributorId); 349 | return distributor; 350 | } 351 | 352 | @GetAll('Retailer') 353 | @Invokable() 354 | public async getAllRetailers() 355 | { 356 | const storedRetailers = await Retailer.getAll(); 357 | return storedRetailers; 358 | } 359 | 360 | @GetById('Retailer') 361 | @Invokable() 362 | public async getRetailerById( 363 | @Param(yup.string()) 364 | retailerId: string 365 | ) 366 | { 367 | const retailer = await Retailer.getOne(retailerId); 368 | return retailer; 369 | } 370 | 371 | @GetAll('Customer') 372 | @Invokable() 373 | public async getAllCustomers() 374 | { 375 | const storedCustomers = await Customer.getAll(); 376 | return storedCustomers; 377 | } 378 | 379 | @GetById('Customer') 380 | @Invokable() 381 | public async getCustomerById( 382 | @Param(yup.string()) 383 | customerId: string 384 | ) 385 | { 386 | const customer = await Customer.getOne(customerId); 387 | return customer; 388 | } 389 | 390 | @Invokable() 391 | public async getAllModels() 392 | { 393 | const storedCustomers = await Customer.getAll(); 394 | d(storedCustomers); 395 | 396 | const storedRetailers = await Retailer.getAll(); 397 | d(storedRetailers); 398 | 399 | const storedDistributors = await Distributor.getAll(); 400 | d(storedDistributors); 401 | 402 | const storedManufacturers = await Manufacturer.getAll(); 403 | d(storedManufacturers); 404 | 405 | const storedSuppliers = await Supplier.getAll(); 406 | d(storedSuppliers); 407 | } 408 | 409 | @Service() 410 | @Invokable() 411 | public async fetchRawMaterial( 412 | @Param(yup.string()) 413 | supplierId: string, 414 | @Param(yup.number()) 415 | rawMaterialSupply: number 416 | ) { 417 | const supplier = await Supplier.getOne(supplierId); 418 | supplier.rawMaterialAvailable = supplier.rawMaterialAvailable + rawMaterialSupply; 419 | await supplier.save(); 420 | } 421 | 422 | @Service() 423 | @Invokable() 424 | public async getRawMaterialFromSupplier( 425 | @Param(yup.string()) 426 | manufacturerId: string, 427 | @Param(yup.string()) 428 | supplierId: string, 429 | @Param(yup.number()) 430 | rawMaterialSupply: number 431 | ) { 432 | const supplier = await Supplier.getOne(supplierId); 433 | supplier.rawMaterialAvailable = supplier.rawMaterialAvailable - rawMaterialSupply; 434 | const manufacturer = await Manufacturer.getOne(manufacturerId); 435 | manufacturer.rawMaterialAvailable = rawMaterialSupply + manufacturer.rawMaterialAvailable; 436 | 437 | await supplier.save(); 438 | await manufacturer.save(); 439 | } 440 | 441 | @Service() 442 | @Invokable() 443 | public async createProducts( 444 | @Param(yup.string()) 445 | manufacturerId: string, 446 | @Param(yup.number()) 447 | rawMaterialConsumed: number, 448 | @Param(yup.number()) 449 | productsCreated: number 450 | ) { 451 | const manufacturer = await Manufacturer.getOne(manufacturerId); 452 | manufacturer.rawMaterialAvailable = manufacturer.rawMaterialAvailable - rawMaterialConsumed; 453 | manufacturer.productsAvailable = manufacturer.productsAvailable + productsCreated; 454 | await manufacturer.save(); 455 | } 456 | 457 | @Service() 458 | @Invokable() 459 | public async sendProductsToDistribution( 460 | @Param(yup.string()) 461 | manufacturerId: string, 462 | @Param(yup.string()) 463 | distributorId: string, 464 | @Param(yup.number()) 465 | sentProducts: number 466 | ) { 467 | const distributor = await Distributor.getOne(distributorId); 468 | distributor.productsToBeShipped = distributor.productsToBeShipped + sentProducts; 469 | const manufacturer = await Manufacturer.getOne(manufacturerId); 470 | manufacturer.productsAvailable = manufacturer.productsAvailable - sentProducts; 471 | 472 | await distributor.save(); 473 | await manufacturer.save(); 474 | } 475 | 476 | @Service() 477 | @Invokable() 478 | public async orderProductsFromDistributor( 479 | @Param(yup.string()) 480 | retailerId: string, 481 | @Param(yup.string()) 482 | distributorId: string, 483 | @Param(yup.number()) 484 | orderedProducts: number 485 | ) { 486 | const retailer = await Retailer.getOne(retailerId); 487 | retailer.productsOrdered = retailer.productsOrdered + orderedProducts; 488 | const distributor = await Distributor.getOne(distributorId); 489 | distributor.productsToBeShipped = distributor.productsToBeShipped - orderedProducts; 490 | distributor.productsShipped = distributor.productsShipped + orderedProducts; 491 | 492 | await retailer.save(); 493 | await distributor.save(); 494 | } 495 | 496 | @Service() 497 | @Invokable() 498 | public async receiveProductsFromDistributor( 499 | @Param(yup.string()) 500 | retailerId: string, 501 | @Param(yup.string()) 502 | distributorId: string, 503 | @Param(yup.number()) 504 | receivedProducts: number 505 | ) { 506 | const retailer = await Retailer.getOne(retailerId); 507 | retailer.productsAvailable = retailer.productsAvailable + receivedProducts; 508 | const distributor = await Distributor.getOne(distributorId); 509 | distributor.productsReceived = distributor.productsReceived + receivedProducts; 510 | 511 | await retailer.save(); 512 | await distributor.save(); 513 | } 514 | 515 | @Service() 516 | @Invokable() 517 | public async buyProductsFromRetailer( 518 | @Param(yup.string()) 519 | retailerId: string, 520 | @Param(yup.string()) 521 | customerId: string, 522 | @Param(yup.number()) 523 | boughtProducts: number 524 | ) { 525 | const retailer = await Retailer.getOne(retailerId); 526 | retailer.productsAvailable = retailer.productsAvailable - boughtProducts; 527 | retailer.productsSold = retailer.productsSold + boughtProducts; 528 | const customer = await Customer.getOne(customerId); 529 | customer.productsBought = customer.productsBought + boughtProducts; 530 | 531 | await retailer.save(); 532 | await customer.save(); 533 | } 534 | } 535 | ``` 536 | 537 | ## API generation 538 | 539 | Once defined the infrastructure and once annotated the controller methods, we need to install the yeoman (https://yeoman.io) generator that will be used for creating the skeleton of our backend: 540 | 541 | ``` 542 | npm install -g generator-express-no-stress-typescript 543 | ``` 544 | 545 | Then we can install the **convector-rest-api** npm package (you can install it also directly from this project if you have in mind to develop and to change it) 546 | 547 | ``` 548 | npm install -g @worldsibu/convector-rest-api 549 | ``` 550 | 551 | This will install in your PATH an executable called **conv-rest-api**. 552 | 553 | Now to generate the API application you just need to go in the root of your project and run: 554 | 555 | ``` 556 | conv-rest-api generate api -c -p -f 557 | ``` 558 | 559 | While the chaincode name parameter will tell the generator where to look for the methods to be wrapped by APIs, the project name will be used only for defining the name of the project swagger and to give to the Router class a proper name. The chaincode config file is optional and defaults to the file in the root folder called **org1.< chaincode name >.config.json** 560 | 561 | For our supply chain example we can run in the folder **convector-example-supplychain-master**: 562 | 563 | ``` 564 | conv-rest-api generate api -c supplychainchaincode -p supplychain -f ./org1.supplychainchaincode.config.json 565 | ``` 566 | 567 | What this command will do is: 568 | 569 | + Removing the previously generated app (if exists). 570 | + It invokes the yeoman generator for generating in the **packages** folder the stub of the api application in a folder called -app. In our supply chain scenario the folder will be called supplychainchaincode-app 571 | + It installs some external dependencies 572 | + It installs the chaincode as dependency of the project 573 | + It copies a **tsconfig.ts** file bundled in the **convector-rest-api** package in the root folder of the api project (**packages/supplychainchaincode-app**). This file is identical to the file that the **generator-express-no-stress-typescript** yeoman generator generates, with the difference that we added the **experimental decorators**: 574 | ```javascript 575 | { 576 | "compileOnSave": false, 577 | "compilerOptions": { 578 | "target": "es6", 579 | "module": "commonjs", 580 | "esModuleInterop": true, 581 | "sourceMap": true, 582 | "moduleResolution": "node", 583 | "experimentalDecorators": true, 584 | "outDir": "dist", 585 | "typeRoots": ["node_modules/@types"] 586 | }, 587 | "include": ["typings.d.ts", "server/**/*.ts"], 588 | "exclude": ["node_modules"] 589 | } 590 | ``` 591 | + It copies a file called **selfgenfabriccontext.ts** bundled in the **convector-rest-api** package, in **packages/-app/server/selfgenfabriccontext.ts**. This file contains the environment variables and helper that will be used to interact with the fabric-client and the function getClient() that instantiates the Client object from the fabric-client library and configures it reading the files which names are specified using a variable present in the .env file we created above. 592 | 593 | We read 3 variables that in the supplychain example are: 594 | ``` 595 | KEYSTORE=../../../fabric-hurl/.hfc-org1 596 | USERCERT=admin 597 | ORGCERT=org1 598 | ``` 599 | 600 | the complete generated file is: 601 | 602 | ```javascript 603 | /** Referenced from: https://github.com/ksachdeva/hyperledger-fabric-example/blob/c41fcaa352e78cbf3c7cfb210338ac0f20b8357e/src/client.ts */ 604 | import * as fs from 'fs'; 605 | import { join } from 'path'; 606 | import Client from 'fabric-client'; 607 | 608 | import { IEnrollmentRequest, IRegisterRequest } from 'fabric-ca-client'; 609 | 610 | export type UserParams = IRegisterRequest; 611 | export type AdminParams = IEnrollmentRequest; 612 | 613 | export namespace SelfGenContext { 614 | 615 | interface IdentityFiles { 616 | privateKey: string; 617 | signedCert: string; 618 | } 619 | 620 | export async function getClient() { 621 | // Check if needed 622 | let contextPath = ''; 623 | if (process.env.KEYSTORE[0] == '/') { 624 | contextPath = join(process.env.KEYSTORE + '/' + process.env.USERCERT); 625 | } 626 | else { 627 | contextPath = join(__dirname, process.env.KEYSTORE + '/' + process.env.USERCERT); 628 | } 629 | 630 | fs.readFile(contextPath, 'utf8', async function (err, data) { 631 | if (err) { 632 | // doesnt exist! Create it. 633 | const client = new Client(); 634 | 635 | d('Setting up the cryptoSuite ..'); 636 | 637 | // ## Setup the cryptosuite (we are using the built in default s/w based implementation) 638 | const cryptoSuite = Client.newCryptoSuite(); 639 | cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({ 640 | path: process.env.KEYSTORE 641 | })); 642 | 643 | client.setCryptoSuite(cryptoSuite); 644 | 645 | d('Setting up the keyvalue store ..'); 646 | 647 | // ## Setup the default keyvalue store where the state will be stored 648 | const store = await Client.newDefaultKeyValueStore({ 649 | path: process.env.KEYSTORE 650 | }); 651 | 652 | client.setStateStore(store); 653 | 654 | d('Creating the admin user context ..'); 655 | 656 | const privateKeyFile = fs.readdirSync(process.env.KEYSTORE + '/keystore')[0]; 657 | 658 | // ### GET THE NECESSRY KEY MATERIAL FOR THE ADMIN OF THE SPECIFIED ORG ## 659 | const cryptoContentOrgAdmin: IdentityFiles = { 660 | privateKey: process.env.KEYSTORE + '/keystore/' + privateKeyFile, 661 | signedCert: process.env.KEYSTORE + '/signcerts/cert.pem' 662 | }; 663 | 664 | await client.createUser({ 665 | username: process.env.USERCERT, 666 | mspid: `${process.env.ORGCERT}MSP`, 667 | cryptoContent: cryptoContentOrgAdmin, 668 | skipPersistence: false 669 | }); 670 | 671 | return client; 672 | } else { 673 | d('Context exists'); 674 | } 675 | }); 676 | 677 | } 678 | 679 | } 680 | ``` 681 | 682 | + It generates the file **packages/< chaincode name >-app/server/smartContractControllers.ts** where we the fabric adapter is configured and then, to call the blockchain, it's reused the SupplychainchaincodeControllerClient that was created automatically before when the convictor project was generated. 683 | In the supplychain scenario the generated file will be: 684 | 685 | ```javascript 686 | import { resolve } from "path"; 687 | import { ClientFactory } from "@worldsibu/convector-core-adapter"; 688 | import { SelfGenContext } from "./selfgenfabriccontext"; 689 | import { SupplychainchaincodeController } from "supplychainchaincode-cc/dist/src"; 690 | import { FabricControllerAdapter } from '@worldsibu/convector-adapter-fabric'; 691 | 692 | export namespace SupplychainchaincodeControllerClient { 693 | export async function init(): Promise { 694 | const user = process.env.USERCERT || 'user1'; 695 | await SelfGenContext.getClient(); 696 | // Inject a Adapter of type *Fabric Controller* 697 | // Setup accordingly to the 698 | const adapter = new FabricControllerAdapter({ 699 | txTimeout: 300000, 700 | user: user, 701 | channel: process.env.CHANNEL, 702 | chaincode: process.env.CHAINCODE, 703 | keyStore: resolve(__dirname, process.env.KEYSTORE), 704 | networkProfile: resolve(__dirname, process.env.NETWORKPROFILE), 705 | userMspPath: resolve(__dirname, process.env.KEYSTORE), 706 | }); 707 | await adapter.init(); 708 | // Return your own implementation of the controller 709 | 710 | return ClientFactory(SupplychainchaincodeController, adapter); 711 | } 712 | } 713 | ``` 714 | 715 | **NOTE:** Here you can note the usage of the new pattern that uses the **ClientFactory** that finally removes the need of generating a client class since it allows to invoke directly the chaincode controller methods. 716 | 717 | What we did here is defining an object called SupplyChainController that has a function init() that reads the USERCERT variable from the .env file, and creates the client that we defined above. Then it creates a FabricControllerAdapter reading the CHANNEL, the CHAINCODE and other parameters read from .env. Once configured the adapter ini initiated with the init() invocation. 718 | 719 | + It generates the file **packages/< chaincode name >-app/server/smartContractModels.ts** hat will export the names of the Models using the ones defined in the client directory. This class will be used in the controller client that will be described shortly: 720 | 721 | In out supplychain scenario the generated file will be: 722 | ```javascript 723 | import { BaseStorage } from '@worldsibu/convector-core-storage'; 724 | import { CouchDBStorage } from '@worldsibu/convector-storage-couchdb'; 725 | 726 | import { Customer as CustomerModel } from 'supplychainchaincode-cc/dist/src'; 727 | import { Distributor as DistributorModel } from 'supplychainchaincode-cc/dist/src'; 728 | import { Manufacturer as ManufacturerModel } from 'supplychainchaincode-cc/dist/src'; 729 | import { Retailer as RetailerModel } from 'supplychainchaincode-cc/dist/src'; 730 | import { Supplier as SupplierModel } from 'supplychainchaincode-cc/dist/src'; 731 | 732 | export namespace Models { 733 | export const Customer = CustomerModel; 734 | export const Distributor = DistributorModel; 735 | export const Manufacturer = ManufacturerModel; 736 | export const Retailer = RetailerModel; 737 | export const Supplier = SupplierModel; 738 | } 739 | 740 | ``` 741 | 742 | + It generates the controller client in **packages/< chaincode name >-app/server/api/controllers/examples/controller.ts** based on how the methods have been annotated in the chaincode controller 743 | In the supplychain scenario the generated file is: 744 | 745 | ```javascript 746 | import { Request, Response } from 'express'; 747 | import { SupplychainchaincodeControllerClient } from '../../../smartContractControllers'; 748 | import { Models } from '../../../smartContractModels'; 749 | 750 | export class Controller { 751 | 752 | async supplychainchaincode_getAllSuppliers(req: Request, res: Response): Promise { 753 | let cntrl = await SupplychainchaincodeControllerClient.init(); 754 | let result = await cntrl.getAllSuppliers(); 755 | res.json(result); 756 | } 757 | 758 | async supplychainchaincode_getAllManufacturers(req: Request, res: Response): Promise { 759 | let cntrl = await SupplychainchaincodeControllerClient.init(); 760 | let result = await cntrl.getAllManufacturers(); 761 | res.json(result); 762 | } 763 | 764 | async supplychainchaincode_getAllDistributors(req: Request, res: Response): Promise { 765 | let cntrl = await SupplychainchaincodeControllerClient.init(); 766 | let result = await cntrl.getAllDistributors(); 767 | res.json(result); 768 | } 769 | 770 | async supplychainchaincode_getAllRetailers(req: Request, res: Response): Promise { 771 | let cntrl = await SupplychainchaincodeControllerClient.init(); 772 | let result = await cntrl.getAllRetailers(); 773 | res.json(result); 774 | } 775 | 776 | async supplychainchaincode_getAllCustomers(req: Request, res: Response): Promise { 777 | let cntrl = await SupplychainchaincodeControllerClient.init(); 778 | let result = await cntrl.getAllCustomers(); 779 | res.json(result); 780 | } 781 | 782 | 783 | async supplychainchaincode_getSupplierById(req: Request, res: Response) { 784 | let cntrl = await SupplychainchaincodeControllerClient.init(); 785 | let result = await cntrl.getSupplierById(req.params.id); 786 | if (!result) { 787 | return res.status(404); 788 | } 789 | res.json(result); 790 | } 791 | 792 | async supplychainchaincode_getManufacturerById(req: Request, res: Response) { 793 | let cntrl = await SupplychainchaincodeControllerClient.init(); 794 | let result = await cntrl.getManufacturerById(req.params.id); 795 | if (!result) { 796 | return res.status(404); 797 | } 798 | res.json(result); 799 | } 800 | 801 | async supplychainchaincode_getDistributorById(req: Request, res: Response) { 802 | let cntrl = await SupplychainchaincodeControllerClient.init(); 803 | let result = await cntrl.getDistributorById(req.params.id); 804 | if (!result) { 805 | return res.status(404); 806 | } 807 | res.json(result); 808 | } 809 | 810 | async supplychainchaincode_getRetailerById(req: Request, res: Response) { 811 | let cntrl = await SupplychainchaincodeControllerClient.init(); 812 | let result = await cntrl.getRetailerById(req.params.id); 813 | if (!result) { 814 | return res.status(404); 815 | } 816 | res.json(result); 817 | } 818 | 819 | async supplychainchaincode_getCustomerById(req: Request, res: Response) { 820 | let cntrl = await SupplychainchaincodeControllerClient.init(); 821 | let result = await cntrl.getCustomerById(req.params.id); 822 | if (!result) { 823 | return res.status(404); 824 | } 825 | res.json(result); 826 | } 827 | 828 | async supplychainchaincode_createSupplier(req: Request, res: Response) { 829 | try { 830 | let cntrl = await SupplychainchaincodeControllerClient.init(); 831 | let modelRaw = req.body; 832 | let model = new Models.Supplier(modelRaw); 833 | await cntrl.createSupplier(model); 834 | res.sendStatus(201); 835 | } catch (ex) { 836 | d(ex.message, ex.stack); 837 | res.status(500).send(ex); 838 | } 839 | } 840 | 841 | async supplychainchaincode_createManufacturer(req: Request, res: Response) { 842 | try { 843 | let cntrl = await SupplychainchaincodeControllerClient.init(); 844 | let modelRaw = req.body; 845 | let model = new Models.Manufacturer(modelRaw); 846 | await cntrl.createManufacturer(model); 847 | res.sendStatus(201); 848 | } catch (ex) { 849 | d(ex.message, ex.stack); 850 | res.status(500).send(ex); 851 | } 852 | } 853 | 854 | async supplychainchaincode_createDistributor(req: Request, res: Response) { 855 | try { 856 | let cntrl = await SupplychainchaincodeControllerClient.init(); 857 | let modelRaw = req.body; 858 | let model = new Models.Distributor(modelRaw); 859 | await cntrl.createDistributor(model); 860 | res.sendStatus(201); 861 | } catch (ex) { 862 | d(ex.message, ex.stack); 863 | res.status(500).send(ex); 864 | } 865 | } 866 | 867 | async supplychainchaincode_createRetailer(req: Request, res: Response) { 868 | try { 869 | let cntrl = await SupplychainchaincodeControllerClient.init(); 870 | let modelRaw = req.body; 871 | let model = new Models.Retailer(modelRaw); 872 | await cntrl.createRetailer(model); 873 | res.sendStatus(201); 874 | } catch (ex) { 875 | d(ex.message, ex.stack); 876 | res.status(500).send(ex); 877 | } 878 | } 879 | 880 | async supplychainchaincode_createCustomer(req: Request, res: Response) { 881 | try { 882 | let cntrl = await SupplychainchaincodeControllerClient.init(); 883 | let modelRaw = req.body; 884 | let model = new Models.Customer(modelRaw); 885 | await cntrl.createCustomer(model); 886 | res.sendStatus(201); 887 | } catch (ex) { 888 | d(ex.message, ex.stack); 889 | res.status(500).send(ex); 890 | } 891 | } 892 | 893 | async supplychainchaincode_fetchRawMaterial(req: Request, res: Response) { 894 | try { 895 | let cntrl = await SupplychainchaincodeControllerClient.init(); 896 | let params = req.body; 897 | 898 | await cntrl.fetchRawMaterial(params.supplierId,params.rawMaterialSupply); 899 | res.sendStatus(201); 900 | } catch (ex) { 901 | d(ex.message, ex.stack); 902 | res.status(500).send(ex); 903 | } 904 | } 905 | 906 | async supplychainchaincode_getRawMaterialFromSupplier(req: Request, res: Response) { 907 | try { 908 | let cntrl = await SupplychainchaincodeControllerClient.init(); 909 | let params = req.body; 910 | 911 | await cntrl.getRawMaterialFromSupplier(params.manufacturerId,params.supplierId,params.rawMaterialSupply); 912 | res.sendStatus(201); 913 | } catch (ex) { 914 | d(ex.message, ex.stack); 915 | res.status(500).send(ex); 916 | } 917 | } 918 | 919 | async supplychainchaincode_createProducts(req: Request, res: Response) { 920 | try { 921 | let cntrl = await SupplychainchaincodeControllerClient.init(); 922 | let params = req.body; 923 | 924 | await cntrl.createProducts(params.manufacturerId,params.rawMaterialConsumed,params.productsCreated); 925 | res.sendStatus(201); 926 | } catch (ex) { 927 | d(ex.message, ex.stack); 928 | res.status(500).send(ex); 929 | } 930 | } 931 | 932 | async supplychainchaincode_sendProductsToDistribution(req: Request, res: Response) { 933 | try { 934 | let cntrl = await SupplychainchaincodeControllerClient.init(); 935 | let params = req.body; 936 | 937 | await cntrl.sendProductsToDistribution(params.manufacturerId,params.distributorId,params.sentProducts); 938 | res.sendStatus(201); 939 | } catch (ex) { 940 | d(ex.message, ex.stack); 941 | res.status(500).send(ex); 942 | } 943 | } 944 | 945 | async supplychainchaincode_orderProductsFromDistributor(req: Request, res: Response) { 946 | try { 947 | let cntrl = await SupplychainchaincodeControllerClient.init(); 948 | let params = req.body; 949 | 950 | await cntrl.orderProductsFromDistributor(params.retailerId,params.distributorId,params.orderedProducts); 951 | res.sendStatus(201); 952 | } catch (ex) { 953 | d(ex.message, ex.stack); 954 | res.status(500).send(ex); 955 | } 956 | } 957 | 958 | async supplychainchaincode_receiveProductsFromDistributor(req: Request, res: Response) { 959 | try { 960 | let cntrl = await SupplychainchaincodeControllerClient.init(); 961 | let params = req.body; 962 | 963 | await cntrl.receiveProductsFromDistributor(params.retailerId,params.distributorId,params.receivedProducts); 964 | res.sendStatus(201); 965 | } catch (ex) { 966 | d(ex.message, ex.stack); 967 | res.status(500).send(ex); 968 | } 969 | } 970 | 971 | async supplychainchaincode_buyProductsFromRetailer(req: Request, res: Response) { 972 | try { 973 | let cntrl = await SupplychainchaincodeControllerClient.init(); 974 | let params = req.body; 975 | 976 | await cntrl.buyProductsFromRetailer(params.retailerId,params.customerId,params.boughtProducts); 977 | res.sendStatus(201); 978 | } catch (ex) { 979 | d(ex.message, ex.stack); 980 | res.status(500).send(ex); 981 | } 982 | } 983 | 984 | 985 | } 986 | export default new Controller(); 987 | 988 | ``` 989 | 990 | + It generates the file **packages/< chaincode name >-app/server/routes.ts** where the base route for our API is defined. 991 | In our supplychain case is /api/v1/supplychain: 992 | 993 | ```javascript 994 | import { Application } from 'express'; 995 | import supplychainRouter from './api/controllers/examples/router' 996 | export default function routes(app: Application): void { 997 | app.use('/api/v1/supplychain', supplychainRouter); 998 | }; 999 | ``` 1000 | 1001 | + It generates the **packages/< chainchode name >-app/server/api/controllers/examples/router.ts** that contains the **routes** of all the API. It's generated accordingly to the annotations put in the chaincode controller. 1002 | 1003 | That means that: 1004 | 1005 | + **@Create** methods: will be mapped to POST methods where as convention the endpoint will be the name of the model class of the model that will be created with the first letter lowercase and with an 's' at the end; for example ```.post('/suppliers/', controller.createSupplier)``` 1006 | + **@GetAll** methods: will be mapped to GET methods where, as the previous one, the endpoint will be the name of the model class of all the instances that will be retrieved with the first letter lowercase and with an 's' at the end; for example ```.get('/suppliers/', controller.getAllSuppliers)``` 1007 | + **@GetById** methods: will be mapped to GET methods where, as the previous ones, the endpoint will be the name of the model class with the first letter lowercase and with an 's' at the end and the parameter will be the id of the model to be retrieved; for example ```.get('/suppliers/:id', controller.getSupplierById)``` 1008 | + **@Service** methods: will be mapped to POST methods where as convention the endpoint will be the name of the methods and all the parameters will be passed inside an object; For example: ```.post('/fetchRawMaterial', controller.fetchRawMaterial)``` 1009 | And a sample object to be passed will be: 1010 | ``` 1011 | { 1012 | "supplierId": "SPL_1", 1013 | "rawMaterialSupply": 12345555 1014 | } 1015 | ``` 1016 | 1017 | In the supplychain example the complete file will be: 1018 | 1019 | ```javascript 1020 | import express from 'express'; 1021 | import controller from './controller' 1022 | export default express.Router() 1023 | 1024 | .post('/suppliers/', controller.supplychainchaincode_createSupplier) 1025 | .post('/manufacturers/', controller.supplychainchaincode_createManufacturer) 1026 | .post('/distributors/', controller.supplychainchaincode_createDistributor) 1027 | .post('/retailers/', controller.supplychainchaincode_createRetailer) 1028 | .post('/customers/', controller.supplychainchaincode_createCustomer) 1029 | .get('/suppliers/', controller.supplychainchaincode_getAllSuppliers) 1030 | .get('/manufacturers/', controller.supplychainchaincode_getAllManufacturers) 1031 | .get('/distributors/', controller.supplychainchaincode_getAllDistributors) 1032 | .get('/retailers/', controller.supplychainchaincode_getAllRetailers) 1033 | .get('/customers/', controller.supplychainchaincode_getAllCustomers) 1034 | .get('/suppliers/:id', controller.supplychainchaincode_getSupplierById) 1035 | .get('/manufacturers/:id', controller.supplychainchaincode_getManufacturerById) 1036 | .get('/distributors/:id', controller.supplychainchaincode_getDistributorById) 1037 | .get('/retailers/:id', controller.supplychainchaincode_getRetailerById) 1038 | .get('/customers/:id', controller.supplychainchaincode_getCustomerById) 1039 | .post('/fetchRawMaterial', controller.supplychainchaincode_fetchRawMaterial) 1040 | .post('/getRawMaterialFromSupplier', controller.supplychainchaincode_getRawMaterialFromSupplier) 1041 | .post('/createProducts', controller.supplychainchaincode_createProducts) 1042 | .post('/sendProductsToDistribution', controller.supplychainchaincode_sendProductsToDistribution) 1043 | .post('/orderProductsFromDistributor', controller.supplychainchaincode_orderProductsFromDistributor) 1044 | .post('/receiveProductsFromDistributor', controller.supplychainchaincode_receiveProductsFromDistributor) 1045 | .post('/buyProductsFromRetailer', controller.supplychainchaincode_buyProductsFromRetailer) 1046 | 1047 | ; 1048 | 1049 | 1050 | ``` 1051 | 1052 | + Then it generates the **packages/< chaincode name >-app/server/common/swagger/Api.yaml** in order to use swagger for interacting with the APIs in a graphical way. 1053 | 1054 | In our supplychain scenario the file generated will be the following: 1055 | 1056 | ``` 1057 | swagger: "2.0" 1058 | info: 1059 | version: 1.0.0 1060 | title: supplychain 1061 | description: supplychain REST API Application 1062 | basePath: /api/v1/supplychain 1063 | 1064 | tags: 1065 | 1066 | - name: Customers 1067 | description: Simple customer endpoints 1068 | 1069 | - name: Distributors 1070 | description: Simple distributor endpoints 1071 | 1072 | - name: Manufacturers 1073 | description: Simple manufacturer endpoints 1074 | 1075 | - name: Retailers 1076 | description: Simple retailer endpoints 1077 | 1078 | - name: Suppliers 1079 | description: Simple supplier endpoints 1080 | 1081 | 1082 | consumes: 1083 | - application/json 1084 | produces: 1085 | - application/json 1086 | 1087 | definitions: 1088 | 1089 | CustomerBody: 1090 | type: object 1091 | title: Customer 1092 | required: 1093 | - id 1094 | - name 1095 | - productsBought 1096 | properties: 1097 | id: 1098 | type: string 1099 | example: a_text 1100 | name: 1101 | type: string 1102 | example: a_text 1103 | productsBought: 1104 | type: number 1105 | example: 123 1106 | DistributorBody: 1107 | type: object 1108 | title: Distributor 1109 | required: 1110 | - id 1111 | - name 1112 | - productsToBeShipped 1113 | - productsShipped 1114 | - productsReceived 1115 | properties: 1116 | id: 1117 | type: string 1118 | example: a_text 1119 | name: 1120 | type: string 1121 | example: a_text 1122 | productsToBeShipped: 1123 | type: number 1124 | example: 123 1125 | productsShipped: 1126 | type: number 1127 | example: 123 1128 | productsReceived: 1129 | type: number 1130 | example: 123 1131 | ManufacturerBody: 1132 | type: object 1133 | title: Manufacturer 1134 | required: 1135 | - id 1136 | - name 1137 | - productsAvailable 1138 | - rawMaterialAvailable 1139 | properties: 1140 | id: 1141 | type: string 1142 | example: a_text 1143 | name: 1144 | type: string 1145 | example: a_text 1146 | productsAvailable: 1147 | type: number 1148 | example: 123 1149 | rawMaterialAvailable: 1150 | type: number 1151 | example: 123 1152 | RetailerBody: 1153 | type: object 1154 | title: Retailer 1155 | required: 1156 | - id 1157 | - name 1158 | - productsOrdered 1159 | - productsAvailable 1160 | - productsSold 1161 | properties: 1162 | id: 1163 | type: string 1164 | example: a_text 1165 | name: 1166 | type: string 1167 | example: a_text 1168 | productsOrdered: 1169 | type: number 1170 | example: 123 1171 | productsAvailable: 1172 | type: number 1173 | example: 123 1174 | productsSold: 1175 | type: number 1176 | example: 123 1177 | SupplierBody: 1178 | type: object 1179 | title: Supplier 1180 | required: 1181 | - id 1182 | - name 1183 | - rawMaterialAvailable 1184 | properties: 1185 | id: 1186 | type: string 1187 | example: a_text 1188 | name: 1189 | type: string 1190 | example: a_text 1191 | rawMaterialAvailable: 1192 | type: number 1193 | example: 123 1194 | fetchRawMaterialBody: 1195 | type: object 1196 | title: fetchRawMaterialParams 1197 | required: 1198 | - supplierId 1199 | - rawMaterialSupply 1200 | properties: 1201 | supplierId: 1202 | type: string 1203 | example: a_text 1204 | rawMaterialSupply: 1205 | type: number 1206 | example: 123 1207 | getRawMaterialFromSupplierBody: 1208 | type: object 1209 | title: getRawMaterialFromSupplierParams 1210 | required: 1211 | - manufacturerId 1212 | - supplierId 1213 | - rawMaterialSupply 1214 | properties: 1215 | manufacturerId: 1216 | type: string 1217 | example: a_text 1218 | supplierId: 1219 | type: string 1220 | example: a_text 1221 | rawMaterialSupply: 1222 | type: number 1223 | example: 123 1224 | createProductsBody: 1225 | type: object 1226 | title: createProductsParams 1227 | required: 1228 | - manufacturerId 1229 | - rawMaterialConsumed 1230 | - productsCreated 1231 | properties: 1232 | manufacturerId: 1233 | type: string 1234 | example: a_text 1235 | rawMaterialConsumed: 1236 | type: number 1237 | example: 123 1238 | productsCreated: 1239 | type: number 1240 | example: 123 1241 | sendProductsToDistributionBody: 1242 | type: object 1243 | title: sendProductsToDistributionParams 1244 | required: 1245 | - manufacturerId 1246 | - distributorId 1247 | - sentProducts 1248 | properties: 1249 | manufacturerId: 1250 | type: string 1251 | example: a_text 1252 | distributorId: 1253 | type: string 1254 | example: a_text 1255 | sentProducts: 1256 | type: number 1257 | example: 123 1258 | orderProductsFromDistributorBody: 1259 | type: object 1260 | title: orderProductsFromDistributorParams 1261 | required: 1262 | - retailerId 1263 | - distributorId 1264 | - orderedProducts 1265 | properties: 1266 | retailerId: 1267 | type: string 1268 | example: a_text 1269 | distributorId: 1270 | type: string 1271 | example: a_text 1272 | orderedProducts: 1273 | type: number 1274 | example: 123 1275 | receiveProductsFromDistributorBody: 1276 | type: object 1277 | title: receiveProductsFromDistributorParams 1278 | required: 1279 | - retailerId 1280 | - distributorId 1281 | - receivedProducts 1282 | properties: 1283 | retailerId: 1284 | type: string 1285 | example: a_text 1286 | distributorId: 1287 | type: string 1288 | example: a_text 1289 | receivedProducts: 1290 | type: number 1291 | example: 123 1292 | buyProductsFromRetailerBody: 1293 | type: object 1294 | title: buyProductsFromRetailerParams 1295 | required: 1296 | - retailerId 1297 | - customerId 1298 | - boughtProducts 1299 | properties: 1300 | retailerId: 1301 | type: string 1302 | example: a_text 1303 | customerId: 1304 | type: string 1305 | example: a_text 1306 | boughtProducts: 1307 | type: number 1308 | example: 123 1309 | 1310 | paths: 1311 | 1312 | /customers: 1313 | get: 1314 | tags: 1315 | - Customers 1316 | description: Fetch all customers 1317 | responses: 1318 | 200: 1319 | description: Returns all customers 1320 | post: 1321 | tags: 1322 | - Customers 1323 | description: Create a new customer 1324 | parameters: 1325 | - name: customer 1326 | in: body 1327 | description: a customer 1328 | required: true 1329 | schema: 1330 | $ref: "#/definitions/CustomerBody" 1331 | responses: 1332 | 201: 1333 | description: Successful insertion of customers 1334 | 1335 | /customers/{id}: 1336 | get: 1337 | tags: 1338 | - Customers 1339 | parameters: 1340 | - name: id 1341 | in: path 1342 | required: true 1343 | description: The id of the customer to retrieve 1344 | type: string 1345 | responses: 1346 | 200: 1347 | description: Return the customer with the specified id 1348 | 404: 1349 | description: Customer not found 1350 | /distributors: 1351 | get: 1352 | tags: 1353 | - Distributors 1354 | description: Fetch all distributors 1355 | responses: 1356 | 200: 1357 | description: Returns all distributors 1358 | post: 1359 | tags: 1360 | - Distributors 1361 | description: Create a new distributor 1362 | parameters: 1363 | - name: distributor 1364 | in: body 1365 | description: a distributor 1366 | required: true 1367 | schema: 1368 | $ref: "#/definitions/DistributorBody" 1369 | responses: 1370 | 201: 1371 | description: Successful insertion of distributors 1372 | 1373 | /distributors/{id}: 1374 | get: 1375 | tags: 1376 | - Distributors 1377 | parameters: 1378 | - name: id 1379 | in: path 1380 | required: true 1381 | description: The id of the distributor to retrieve 1382 | type: string 1383 | responses: 1384 | 200: 1385 | description: Return the distributor with the specified id 1386 | 404: 1387 | description: Distributor not found 1388 | /manufacturers: 1389 | get: 1390 | tags: 1391 | - Manufacturers 1392 | description: Fetch all manufacturers 1393 | responses: 1394 | 200: 1395 | description: Returns all manufacturers 1396 | post: 1397 | tags: 1398 | - Manufacturers 1399 | description: Create a new manufacturer 1400 | parameters: 1401 | - name: manufacturer 1402 | in: body 1403 | description: a manufacturer 1404 | required: true 1405 | schema: 1406 | $ref: "#/definitions/ManufacturerBody" 1407 | responses: 1408 | 201: 1409 | description: Successful insertion of manufacturers 1410 | 1411 | /manufacturers/{id}: 1412 | get: 1413 | tags: 1414 | - Manufacturers 1415 | parameters: 1416 | - name: id 1417 | in: path 1418 | required: true 1419 | description: The id of the manufacturer to retrieve 1420 | type: string 1421 | responses: 1422 | 200: 1423 | description: Return the manufacturer with the specified id 1424 | 404: 1425 | description: Manufacturer not found 1426 | /retailers: 1427 | get: 1428 | tags: 1429 | - Retailers 1430 | description: Fetch all retailers 1431 | responses: 1432 | 200: 1433 | description: Returns all retailers 1434 | post: 1435 | tags: 1436 | - Retailers 1437 | description: Create a new retailer 1438 | parameters: 1439 | - name: retailer 1440 | in: body 1441 | description: a retailer 1442 | required: true 1443 | schema: 1444 | $ref: "#/definitions/RetailerBody" 1445 | responses: 1446 | 201: 1447 | description: Successful insertion of retailers 1448 | 1449 | /retailers/{id}: 1450 | get: 1451 | tags: 1452 | - Retailers 1453 | parameters: 1454 | - name: id 1455 | in: path 1456 | required: true 1457 | description: The id of the retailer to retrieve 1458 | type: string 1459 | responses: 1460 | 200: 1461 | description: Return the retailer with the specified id 1462 | 404: 1463 | description: Retailer not found 1464 | /suppliers: 1465 | get: 1466 | tags: 1467 | - Suppliers 1468 | description: Fetch all suppliers 1469 | responses: 1470 | 200: 1471 | description: Returns all suppliers 1472 | post: 1473 | tags: 1474 | - Suppliers 1475 | description: Create a new supplier 1476 | parameters: 1477 | - name: supplier 1478 | in: body 1479 | description: a supplier 1480 | required: true 1481 | schema: 1482 | $ref: "#/definitions/SupplierBody" 1483 | responses: 1484 | 201: 1485 | description: Successful insertion of suppliers 1486 | 1487 | /suppliers/{id}: 1488 | get: 1489 | tags: 1490 | - Suppliers 1491 | parameters: 1492 | - name: id 1493 | in: path 1494 | required: true 1495 | description: The id of the supplier to retrieve 1496 | type: string 1497 | responses: 1498 | 200: 1499 | description: Return the supplier with the specified id 1500 | 404: 1501 | description: Supplier not found 1502 | 1503 | /fetchRawMaterial: 1504 | post: 1505 | tags: 1506 | - fetchRawMaterial 1507 | description: fetchRawMaterial 1508 | parameters: 1509 | - name: fetchRawMaterialParams 1510 | in: body 1511 | required: true 1512 | schema: 1513 | $ref: "#/definitions/fetchRawMaterialBody" 1514 | responses: 1515 | 201: 1516 | description: fetchRawMaterial executed correctly 1517 | 500: 1518 | description: fetchRawMaterial raised an exception 1519 | 1520 | /getRawMaterialFromSupplier: 1521 | post: 1522 | tags: 1523 | - getRawMaterialFromSupplier 1524 | description: getRawMaterialFromSupplier 1525 | parameters: 1526 | - name: getRawMaterialFromSupplierParams 1527 | in: body 1528 | required: true 1529 | schema: 1530 | $ref: "#/definitions/getRawMaterialFromSupplierBody" 1531 | responses: 1532 | 201: 1533 | description: getRawMaterialFromSupplier executed correctly 1534 | 500: 1535 | description: getRawMaterialFromSupplier raised an exception 1536 | 1537 | /createProducts: 1538 | post: 1539 | tags: 1540 | - createProducts 1541 | description: createProducts 1542 | parameters: 1543 | - name: createProductsParams 1544 | in: body 1545 | required: true 1546 | schema: 1547 | $ref: "#/definitions/createProductsBody" 1548 | responses: 1549 | 201: 1550 | description: createProducts executed correctly 1551 | 500: 1552 | description: createProducts raised an exception 1553 | 1554 | /sendProductsToDistribution: 1555 | post: 1556 | tags: 1557 | - sendProductsToDistribution 1558 | description: sendProductsToDistribution 1559 | parameters: 1560 | - name: sendProductsToDistributionParams 1561 | in: body 1562 | required: true 1563 | schema: 1564 | $ref: "#/definitions/sendProductsToDistributionBody" 1565 | responses: 1566 | 201: 1567 | description: sendProductsToDistribution executed correctly 1568 | 500: 1569 | description: sendProductsToDistribution raised an exception 1570 | 1571 | /orderProductsFromDistributor: 1572 | post: 1573 | tags: 1574 | - orderProductsFromDistributor 1575 | description: orderProductsFromDistributor 1576 | parameters: 1577 | - name: orderProductsFromDistributorParams 1578 | in: body 1579 | required: true 1580 | schema: 1581 | $ref: "#/definitions/orderProductsFromDistributorBody" 1582 | responses: 1583 | 201: 1584 | description: orderProductsFromDistributor executed correctly 1585 | 500: 1586 | description: orderProductsFromDistributor raised an exception 1587 | 1588 | /receiveProductsFromDistributor: 1589 | post: 1590 | tags: 1591 | - receiveProductsFromDistributor 1592 | description: receiveProductsFromDistributor 1593 | parameters: 1594 | - name: receiveProductsFromDistributorParams 1595 | in: body 1596 | required: true 1597 | schema: 1598 | $ref: "#/definitions/receiveProductsFromDistributorBody" 1599 | responses: 1600 | 201: 1601 | description: receiveProductsFromDistributor executed correctly 1602 | 500: 1603 | description: receiveProductsFromDistributor raised an exception 1604 | 1605 | /buyProductsFromRetailer: 1606 | post: 1607 | tags: 1608 | - buyProductsFromRetailer 1609 | description: buyProductsFromRetailer 1610 | parameters: 1611 | - name: buyProductsFromRetailerParams 1612 | in: body 1613 | required: true 1614 | schema: 1615 | $ref: "#/definitions/buyProductsFromRetailerBody" 1616 | responses: 1617 | 201: 1618 | description: buyProductsFromRetailer executed correctly 1619 | 500: 1620 | description: buyProductsFromRetailer raised an exception 1621 | ``` 1622 | 1623 | Once all these files are generated the next step is to compile the just created app with the command: 1624 | 1625 | ``` 1626 | npx lerna run compile --scope < chaincode name >-app 1627 | ``` 1628 | 1629 | In our supplychain scenario is: 1630 | 1631 | ``` 1632 | npx lerna run compile --scope supplychainchaincode-app 1633 | ``` 1634 | 1635 | The output should look something like: 1636 | 1637 | ``` 1638 | in compileApiApplication in command.ts chaincode=supplychainchaincode 1639 | lerna notice cli v3.13.0 1640 | lerna info filter [ 'supplychainchaincode-app' ] 1641 | lerna info Executing command in 1 package: "npm run compile" 1642 | lerna info run Ran npm script 'compile' in 'supplychainchaincode-app' in 3.8s: 1643 | 1644 | > supplychainchaincode-app@1.0.0 compile /Users/luca/Projects/GitHubProjects/convector-example-supplychain-master/packages/supplychainchaincode-app 1645 | > ts-node build.ts && tsc 1646 | 1647 | lerna success run Ran npm script 'compile' in 1 package in 3.8s: 1648 | lerna success - supplychainchaincode-app 1649 | ``` 1650 | 1651 | Then we can finally start the application with 1652 | ``` 1653 | npx lerna run start --scope < chaincode name >-app --stream 1654 | ``` 1655 | 1656 | In our supplychain scenario is: 1657 | 1658 | ``` 1659 | npx lerna run start --scope supplychainchaincode-app --stream 1660 | ``` 1661 | 1662 | The output should look something like: 1663 | 1664 | ``` 1665 | in startApiApplication in command.ts chaincode=supplychainchaincode 1666 | lerna notice cli v3.13.0 1667 | lerna info filter [ 'supplychainchaincode-app' ] 1668 | lerna info Executing command in 1 package: "npm run dev" 1669 | supplychainchaincode-app: > supplychainchaincode-app@1.0.0 dev /Users/luca/Projects/GitHubProjects/convector-example-supplychain-master/packages/supplychainchaincode-app 1670 | supplychainchaincode-app: > nodemon server/index.ts | pino-pretty 1671 | supplychainchaincode-app: [nodemon] 1.18.10 1672 | supplychainchaincode-app: [nodemon] to restart at any time, enter `rs` 1673 | supplychainchaincode-app: [nodemon] watching: /Users/luca/Projects/GitHubProjects/convector-example-supplychain-master/packages/supplychainchaincode-app/server/**/* 1674 | supplychainchaincode-app: [nodemon] starting `ts-node server/index.ts` 1675 | supplychainchaincode-app: (node:1541) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead 1676 | supplychainchaincode-app: (node:1541) DeprecationWarning: grpc.load: Use the @grpc/proto-loader module with grpc.loadPackageDefinition instead 1677 | supplychainchaincode-app: (node:1541) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added. Use emitter.setMaxListeners() to increase limit 1678 | supplychainchaincode-app: [1550919167863] INFO (supplychainchaincode-app/1541 on lucas-MacBook-Pro.local): up and running in development @: lucas-MacBook-Pro.local on port: 3000} 1679 | ``` 1680 | 1681 | That means that the server is up and running listening on the port 3000. 1682 | 1683 | you can reach it with the browser on http://localhost:3000 and you will find a simple webapp that with swagger gives you a simple web interface to invoke the API. 1684 | 1685 | You can also test them with **curl** 1686 | 1687 | + creating a Distributor: 1688 | ``` 1689 | curl -X POST "http://localhost:3000/api/v1/supplychain/distributors" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"id\": \"DST3\", \"name\": \"Distributor3\", \"productsToBeShipped\": 123, \"productsShipped\": 123, \"productsReceived\": 123}" 1690 | ``` 1691 | + getting all Manufacturers: 1692 | ``` 1693 | curl -X GET "http://localhost:3000/api/v1/supplychain/manufacturers" -H "accept: application/json" 1694 | ``` 1695 | + getting a specific Retailer: 1696 | ``` 1697 | curl -X GET "http://localhost:3000/api/v1/supplychain/retailers/RTL_2" -H "accept: application/json" 1698 | ``` 1699 | 1700 | + fetching new material: 1701 | ``` 1702 | curl -X POST "http://localhost:3000/api/v1/supplychain/fetchRawMaterial" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"supplierId\": \"SPL_1\", \"rawMaterialSupply\": 12345555}" 1703 | ``` 1704 | 1705 | ## Multicontroller Chaincodes 1706 | Multicontroller chaincodes are now supported. You can take as example the https://github.com/worldsibu/convector-identity-patterns applying the decorators to the 2 controllers (in participant-cc and in product-cc) and adding the api.json file. 1707 | 1708 | Then running the command 1709 | ``` 1710 | conv-rest-api generate api -c identities -p identitiesproject 1711 | ``` 1712 | 1713 | It will generate the APIs according to how you decorated the methods but in general it will create separate endpoints for the 2 controllers. For example: 1714 | 1715 | ``` 1716 | http://localhost:3000/api/v1/identitiesproject/participant/register 1717 | ``` 1718 | 1719 | In case of multicontroller projects the URL is composed by: 1720 | + root endpoint: http://localhost:3000/api/v1/ 1721 | + project name : identitiesproject 1722 | + controller name: participant 1723 | + method name: register 1724 | 1725 | Can be invoked like this: 1726 | 1727 | ``` 1728 | curl -X POST "http://localhost:3000/api/v1/identitiesproject/participant/register" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"id\": \"luca\"}" 1729 | ``` 1730 | 1731 | Another example: 1732 | 1733 | ``` 1734 | curl -X POST "http://localhost:3000/api/v1/identitiesproject/product/create" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"id\": \"pro_1\", \"name\": \"mela\", \"ownerID\": \"luca\"}" 1735 | ``` 1736 | **Important**: The tool relies its work on the presence, in the root folder of your project, of a file called: 1737 | ``` 1738 | org1..config.json 1739 | ``` 1740 | 1741 | That contains the list of controllers of the chaincode. If you want the tool to read from another file you can specify it with the **-f** parameter described already. 1742 | 1743 | ## Actual Known Limitations: 1744 | 1745 | + Generating code for infinite Hierarchies of Models (now only supports one ancestor) 1746 | + Handling complex return types from Controller functions (like arrays of custom objects) 1747 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@worldsibu/conv-rest-api", 3 | "version": "0.1.5", 4 | "description": "Rest API generator for the Convector Framework", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "files": [ 8 | "bin/*", 9 | "dist/*", 10 | "templates/*", 11 | "templates_scripts/*", 12 | "package.json" 13 | ], 14 | "scripts": { 15 | "clean": "rimraf dist", 16 | "build": "npm run clean && tsc", 17 | "prepare": "npm run build", 18 | "lint": "tslint --fix -c './tslint.json' -p -p './tsconfig.json'", 19 | "test": "mocha -r ts-node/register tests/**/*.spec.ts --reporter spec" 20 | }, 21 | "bin": { 22 | "conv-rest-api": "./dist/command.js" 23 | }, 24 | "dependencies": { 25 | "async": "^2.1.4", 26 | "basic": "^2.0.3", 27 | "chai": "^4.2.0", 28 | "chalk": "^1.1.3", 29 | "commander": "^2.9.0", 30 | "ejs": "^2.6.1", 31 | "fs-extra": "^7.0.1", 32 | "inquirer": "^2.0.0", 33 | "insight": "^0.10.1", 34 | "mem-fs": "^1.1.3", 35 | "mem-fs-editor": "^5.1.0", 36 | "mocha": "^5.2.0", 37 | "path": "^0.12.7", 38 | "reflect-metadata": "^0.1.13", 39 | "safe-json-stringify": "^1.2.0", 40 | "shelljs": "^0.8.3", 41 | "to-absolute-glob": "^2.0.2", 42 | "ts-simple-ast": "^16.0.4", 43 | "tslib": "^1.9.0", 44 | "update-notifier": "^2.5.0" 45 | }, 46 | "devDependencies": { 47 | "@types/chai": "^4.1.7", 48 | "@types/fs-extra": "^5.0.4", 49 | "@types/mocha": "^5.2.5", 50 | "@types/node": "^10.12.20", 51 | "lerna": "^3.10.7", 52 | "rimraf": "^2.6.3", 53 | "ts-node": "^6.0.3", 54 | "tsc": "^1.20150623.0", 55 | "tslint": "^5.12.1", 56 | "typescript": "^2.9.2" 57 | }, 58 | "author": "WorldSibu", 59 | "license": "Apache-2.0", 60 | "repository": { 61 | "type": "git", 62 | "url": "git+https://github.com/worldsibu/convector-rest-api.git" 63 | }, 64 | "bugs": { 65 | "url": "https://github.com/worldsibu/convector-rest-api/issues" 66 | }, 67 | "homepage": "https://github.com/worldsibu/convector-rest-api#readme" 68 | } 69 | -------------------------------------------------------------------------------- /src/apiGenerator.ts: -------------------------------------------------------------------------------- 1 | import { ReflectionUtils } from './utils/reflectionUtils'; 2 | import { join } from 'path'; 3 | import { SysWrapper } from './utils/sysWrapper'; 4 | import { 5 | EnvModel, SmartContractControllers, 6 | SmartContractModels, SmartApiController, SmartRoutesModels, 7 | SmartRouterModels, SmartApiSwaggerYamlModels 8 | } from './models'; 9 | 10 | //const { spawn } = require('child_process'); 11 | 12 | import * as child from 'child_process'; 13 | import { d } from './utils/debug'; 14 | import { TsConfigJsonGenerator } from './generators/tsconfig.json'; 15 | import { PackageJsonGenerator } from './generators/package.json'; 16 | import { Pm2ConfigJsonGenerator } from './generators/pm2config.json'; 17 | import { AppTsGenerator } from './generators/app.ts'; 18 | import { EnvTsGenerator } from './generators/env.ts'; 19 | import { ConvectorTsGenerator } from './generators/convector.ts'; 20 | import { ControllerTsGenerator } from './generators/controller.ts'; 21 | import { RouterTsOptions, RouterTsGenerator } from './generators/router.ts'; 22 | import { ApiConfigurationList, ApiConfigurationItem } from './models/apiConfigurationFile'; 23 | import { SwaggerJsonGenerator } from './generators/swagger.json'; 24 | 25 | /** Model compiler object. */ 26 | export class ApiGenerator { 27 | 28 | get controllers(): { [k: string]: any }[] { 29 | let chaincodeConfig: { [k: string]: any }; 30 | if (this.chaincodeConfigFile[0] != '/') { 31 | chaincodeConfig = require(join(process.cwd(), `/`) + this.chaincodeConfigFile); 32 | } else { 33 | chaincodeConfig = require(this.chaincodeConfigFile); 34 | } 35 | //d(chaincodeConfig); 36 | let controllers: { [k: string]: any }[]; 37 | controllers = chaincodeConfig.controllers; 38 | return controllers; 39 | } 40 | 41 | root = 'packages/server'; 42 | 43 | constructor( 44 | public name: string, 45 | public chaincode: string, 46 | public chaincodeConfigFile: string) { } 47 | 48 | public async generate() { 49 | let chaincodePackages = '( '; 50 | let first = true; 51 | for (let controller of this.controllers) { 52 | if (!first) { 53 | chaincodePackages += ' '; 54 | } else { 55 | first = false; 56 | } 57 | chaincodePackages += controller.name; 58 | } 59 | chaincodePackages += ' )'; 60 | 61 | let apiGen = child.spawn('bash', 62 | [join(__dirname, '../templates_scripts/generate_api_template.bash'), 63 | this.chaincode, chaincodePackages], { 64 | stdio: [process.stdin, process.stdout, process.stderr] 65 | }); 66 | apiGen.on('close', async (code) => { 67 | // let ctrl = new EnvModel(this.name, this.chaincode, null, false); 68 | // await ctrl.save(); 69 | d('generating tsConfig...'); 70 | 71 | const tsConfig = new TsConfigJsonGenerator('tsconfig.json', 72 | this.root); 73 | await tsConfig.save(); 74 | const app = new AppTsGenerator('app.ts', 75 | `${this.root}/src`); 76 | await app.save(); 77 | const packageJson = new PackageJsonGenerator('package.json', 78 | this.root, { controllers: this.controllers }); 79 | await packageJson.save(); 80 | const pm2ConfigJson = new Pm2ConfigJsonGenerator('pm2.config.json', 81 | this.root); 82 | await pm2ConfigJson.save(); 83 | const envTs = new EnvTsGenerator('env.ts', 84 | `${this.root}/src`, { chaincodeName: this.chaincode }); 85 | await envTs.save(); 86 | const convectorTs = new ConvectorTsGenerator('convector.ts', 87 | `${this.root}/src`, { controllers: this.controllers }); 88 | await convectorTs.save(); 89 | 90 | // d('generating SmartContractModels..'); 91 | // await this.generateSmartContractModels(); 92 | // d('generating Controller..'); 93 | 94 | let apiOptions = JSON.parse(await SysWrapper.getFile('api.json')).map(item => { 95 | return new ApiConfigurationItem(item); 96 | }); 97 | 98 | apiOptions = await this.enrichApiOptionsWithParams(apiOptions); 99 | 100 | const controllersTs = new ControllerTsGenerator('controllers.ts', 101 | `${this.root}/src/controllers`, { controllers: this.controllers, config: apiOptions }); 102 | await controllersTs.save(); 103 | const routerTs = new RouterTsGenerator('router.ts', 104 | `${this.root}/src/controllers`, { config: apiOptions }); 105 | await routerTs.save(); 106 | 107 | // const swaggerTs = new SwaggerJsonGenerator('swagger.json', 108 | // `${this.root}/src/common/open-api/`, { config: apiOptions }); 109 | // await swaggerTs.save(); 110 | 111 | // await this.generateController(); 112 | // d('generating Routes..'); 113 | // d('generating Router..'); 114 | // await this.generateRouter(); 115 | // d('generating SwaggerYaml..'); 116 | 117 | d('finished'); 118 | }); 119 | } 120 | 121 | async enrichApiOptionsWithParams(config) { 122 | for (let item of config) { 123 | item.params = await this.getParams(item.controller, item.function); 124 | } 125 | return config; 126 | } 127 | private async getParams(controllerName: string, functionName: string) { 128 | const plainName = controllerName.replace('Controller', '').toLowerCase(); 129 | 130 | let controllersPattern = join(process.cwd(), `.`) + 131 | `/packages/**-cc/src/${plainName}.controller.ts`; 132 | let methods = await ReflectionUtils.getClassMethods(controllersPattern, controllerName); 133 | 134 | let method = methods.find(item => item.getName() === functionName); 135 | let res = []; 136 | res = method.getParameters().map(item => { 137 | let type = ''; 138 | // if (item.getType().getArrayType() != undefined) { 139 | 140 | // } 141 | 142 | return { 143 | name: item.getName(), 144 | type: type 145 | }; 146 | }); 147 | return res; 148 | } 149 | 150 | private async copyTsConfig() { 151 | await SysWrapper.copyFile(join(__dirname, 152 | '../templates/_tsconfig-api.json.ejs'), join(process.cwd(), `.`) + 153 | `/packages/server/tsconfig.json`); 154 | } 155 | 156 | private async copyAppTs() { 157 | await SysWrapper.copyFile(join(__dirname, 158 | '../templates/_app.ts.ejs'), join(process.cwd(), `.`) + 159 | `/packages/server/src/app.ts`); 160 | } 161 | private async copyPackageJson(chaincodes: string[]) { 162 | await SysWrapper.createFile( 163 | join(process.cwd(), `.`) + 164 | `/packages/server/package.json`, `{ 165 | "name": "server", 166 | "version": "1.0.0", 167 | "description": "", 168 | "main": "index.js", 169 | "scripts": { 170 | "start": "npm run build && pm2-runtime pm2.config.json", 171 | "start:daemon": "pm2 startOrRestart pm2.config.json --no-daemon", 172 | "stop": "pm2 stop pm2.config.json", 173 | "tsc": "tsc", 174 | "clean": "rimraf dist client", 175 | "refresh": "./node_modules/pm2/bin/pm2 stop 0 && ./node_modules/pm2/bin/pm2 start 0", 176 | "build": "npm run clean && tsc", 177 | "prepare": "npm run build", 178 | "test": "mocha -r ts-node/register test/*.spec.ts --reporter spec" 179 | }, 180 | "author": "", 181 | "license": "ISC", 182 | "dependencies": { 183 | "@types/bytebuffer": "^5.0.40", 184 | "@types/node": "^12.0.8", 185 | "@worldsibu/convector-adapter-fabric": "^1.3.4", 186 | "@worldsibu/convector-storage-couchdb": "^1.3.4", 187 | "fabric-ca-client": "^1.4.1", 188 | "fabric-client": "^1.4.1" 189 | } 190 | } 191 | `); 192 | 193 | } 194 | private async copyPm2ConfigJson(chaincodes: string[]) { 195 | await SysWrapper.createFile( 196 | join(process.cwd(), `.`) + 197 | `/packages/server/pm2.config.json`, `{ 198 | "apps": [ 199 | { 200 | "name": "Convector Autogenerated Server", 201 | "script": "./dist/app.js", 202 | "node_args": "--inspect=0.0.0.0:8888", 203 | "error_file": "../log/error.log", 204 | "out_file": "../log/access.log", 205 | "env": {}, 206 | "watch": [ 207 | "src", 208 | "dist", 209 | "node_modules", 210 | ".env" 211 | ] 212 | } 213 | ] 214 | }`); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/arrayTypeProperty.json: -------------------------------------------------------------------------------- 1 | Type { 2 | context: 3 | ProjectContext { 4 | _manipulationSettings: ManipulationSettingsContainer { defaultSettings: [Object], settings: [Object] }, 5 | _compilerOptions: 6 | CompilerOptionsContainer { 7 | defaultSettings: {}, 8 | settings: {}, 9 | _modifiedEventContainer: [Object] }, 10 | _logger: ConsoleLogger { enabled: false }, 11 | _fileSystemWrapper: 12 | FileSystemWrapper { 13 | fileSystem: [Object], 14 | directories: [Object], 15 | operationIndex: 0 }, 16 | _compilerFactory: 17 | CompilerFactory { 18 | context: [Circular], 19 | sourceFileCacheByFilePath: [Object], 20 | diagnosticCache: [Object], 21 | definitionInfoCache: [Object], 22 | documentSpanCache: [Object], 23 | diagnosticMessageChainCache: [Object], 24 | jsDocTagInfoCache: [Object], 25 | signatureCache: [Object], 26 | symbolCache: [Object], 27 | symbolDisplayPartCache: [Object], 28 | referenceEntryCache: [Object], 29 | referencedSymbolCache: [Object], 30 | referencedSymbolDefinitionInfoCache: [Object], 31 | typeCache: [Object], 32 | typeParameterCache: [Object], 33 | nodeCache: [Object], 34 | sourceFileAddedEventContainer: [Object], 35 | sourceFileMovedEventContainer: [Object], 36 | sourceFileRemovedEventContainer: [Object], 37 | documentRegistry: [Object], 38 | directoryCache: [Object] }, 39 | _structurePrinterFactory: StructurePrinterFactory { _getFormatCodeSettings: [Function] }, 40 | _lazyReferenceCoordinator: LazyReferenceCoordinator { dirtySourceFiles: [Object] }, 41 | _directoryCoordinator: DirectoryCoordinator { compilerFactory: [Object], fileSystemWrapper: [Object] }, 42 | _languageService: 43 | LanguageService { 44 | context: [Circular], 45 | compilerHost: [Object], 46 | _compilerObject: [Object], 47 | program: [Object] } }, 48 | _compilerType: 49 | TypeObject { 50 | checker: 51 | { getNodeCount: [Function: getNodeCount], 52 | getIdentifierCount: [Function: getIdentifierCount], 53 | getSymbolCount: [Function: getSymbolCount], 54 | getTypeCount: [Function: getTypeCount], 55 | isUndefinedSymbol: [Function: isUndefinedSymbol], 56 | isArgumentsSymbol: [Function: isArgumentsSymbol], 57 | isUnknownSymbol: [Function: isUnknownSymbol], 58 | getMergedSymbol: [Function: getMergedSymbol], 59 | getDiagnostics: [Function: getDiagnostics], 60 | getGlobalDiagnostics: [Function: getGlobalDiagnostics], 61 | getTypeOfSymbolAtLocation: [Function: getTypeOfSymbolAtLocation], 62 | getSymbolsOfParameterPropertyDeclaration: [Function: getSymbolsOfParameterPropertyDeclaration], 63 | getDeclaredTypeOfSymbol: [Function: getDeclaredTypeOfSymbol], 64 | getPropertiesOfType: [Function: getPropertiesOfType], 65 | getPropertyOfType: [Function: getPropertyOfType], 66 | getTypeOfPropertyOfType: [Function: getTypeOfPropertyOfType], 67 | getIndexInfoOfType: [Function: getIndexInfoOfType], 68 | getSignaturesOfType: [Function: getSignaturesOfType], 69 | getIndexTypeOfType: [Function: getIndexTypeOfType], 70 | getBaseTypes: [Function: getBaseTypes], 71 | getBaseTypeOfLiteralType: [Function: getBaseTypeOfLiteralType], 72 | getWidenedType: [Function: getWidenedType], 73 | getTypeFromTypeNode: [Function: getTypeFromTypeNode], 74 | getParameterType: [Function: getTypeAtPosition], 75 | getReturnTypeOfSignature: [Function: getReturnTypeOfSignature], 76 | getNullableType: [Function: getNullableType], 77 | getNonNullableType: [Function: getNonNullableType], 78 | typeToTypeNode: [Function: typeToTypeNode], 79 | indexInfoToIndexSignatureDeclaration: [Function: indexInfoToIndexSignatureDeclaration], 80 | signatureToSignatureDeclaration: [Function: signatureToSignatureDeclaration], 81 | symbolToEntityName: [Function: symbolToEntityName], 82 | symbolToExpression: [Function: symbolToExpression], 83 | symbolToTypeParameterDeclarations: [Function: symbolToTypeParameterDeclarations], 84 | symbolToParameterDeclaration: [Function: symbolToParameterDeclaration], 85 | typeParameterToDeclaration: [Function: typeParameterToDeclaration], 86 | getSymbolsInScope: [Function: getSymbolsInScope], 87 | getSymbolAtLocation: [Function: getSymbolAtLocation], 88 | getShorthandAssignmentValueSymbol: [Function: getShorthandAssignmentValueSymbol], 89 | getExportSpecifierLocalTargetSymbol: [Function: getExportSpecifierLocalTargetSymbol], 90 | getExportSymbolOfSymbol: [Function: getExportSymbolOfSymbol], 91 | getTypeAtLocation: [Function: getTypeAtLocation], 92 | getPropertySymbolOfDestructuringAssignment: [Function: getPropertySymbolOfDestructuringAssignment], 93 | signatureToString: [Function: signatureToString], 94 | typeToString: [Function: typeToString], 95 | symbolToString: [Function: symbolToString], 96 | typePredicateToString: [Function: typePredicateToString], 97 | writeSignature: [Function: writeSignature], 98 | writeType: [Function: writeType], 99 | writeSymbol: [Function: writeSymbol], 100 | writeTypePredicate: [Function: writeTypePredicate], 101 | getAugmentedPropertiesOfType: [Function: getAugmentedPropertiesOfType], 102 | getRootSymbols: [Function: getRootSymbols], 103 | getContextualType: [Function: getContextualType], 104 | getContextualTypeForArgumentAtIndex: [Function: getContextualTypeForArgumentAtIndex], 105 | getContextualTypeForJsxAttribute: [Function: getContextualTypeForJsxAttribute], 106 | isContextSensitive: [Function: isContextSensitive], 107 | getFullyQualifiedName: [Function: getFullyQualifiedName], 108 | getResolvedSignature: [Function: getResolvedSignature], 109 | getConstantValue: [Function: getConstantValue], 110 | isValidPropertyAccess: [Function: isValidPropertyAccess], 111 | isValidPropertyAccessForCompletions: [Function: isValidPropertyAccessForCompletions], 112 | getSignatureFromDeclaration: [Function: getSignatureFromDeclaration], 113 | isImplementationOfOverload: [Function: isImplementationOfOverload], 114 | getImmediateAliasedSymbol: [Function: getImmediateAliasedSymbol], 115 | getAliasedSymbol: [Function: resolveAlias], 116 | getEmitResolver: [Function: getEmitResolver], 117 | getExportsOfModule: [Function: getExportsOfModuleAsArray], 118 | getExportsAndPropertiesOfModule: [Function: getExportsAndPropertiesOfModule], 119 | getSymbolWalker: [Function: getSymbolWalker], 120 | getAmbientModules: [Function: getAmbientModules], 121 | getAllAttributesTypeFromJsxOpeningLikeElement: [Function: getAllAttributesTypeFromJsxOpeningLikeElement], 122 | getJsxIntrinsicTagNamesAt: [Function: getJsxIntrinsicTagNamesAt], 123 | isOptionalParameter: [Function: isOptionalParameter], 124 | tryGetMemberInModuleExports: [Function: tryGetMemberInModuleExports], 125 | tryGetMemberInModuleExportsAndProperties: [Function: tryGetMemberInModuleExportsAndProperties], 126 | tryFindAmbientModuleWithoutAugmentations: [Function: tryFindAmbientModuleWithoutAugmentations], 127 | getApparentType: [Function: getApparentType], 128 | getUnionType: [Function: getUnionType], 129 | createAnonymousType: [Function: createAnonymousType], 130 | createSignature: [Function: createSignature], 131 | createSymbol: [Function: createSymbol], 132 | createIndexInfo: [Function: createIndexInfo], 133 | getAnyType: [Function: getAnyType], 134 | getStringType: [Function: getStringType], 135 | getNumberType: [Function: getNumberType], 136 | createPromiseType: [Function: createPromiseType], 137 | createArrayType: [Function: createArrayType], 138 | getBooleanType: [Function: getBooleanType], 139 | getFalseType: [Function: getFalseType], 140 | getTrueType: [Function: getTrueType], 141 | getVoidType: [Function: getVoidType], 142 | getUndefinedType: [Function: getUndefinedType], 143 | getNullType: [Function: getNullType], 144 | getESSymbolType: [Function: getESSymbolType], 145 | getNeverType: [Function: getNeverType], 146 | isSymbolAccessible: [Function: isSymbolAccessible], 147 | isArrayLikeType: [Function: isArrayLikeType], 148 | isTypeInvalidDueToUnionDiscriminant: [Function: isTypeInvalidDueToUnionDiscriminant], 149 | getAllPossiblePropertiesOfTypes: [Function: getAllPossiblePropertiesOfTypes], 150 | getSuggestionForNonexistentProperty: [Function: getSuggestionForNonexistentProperty], 151 | getSuggestionForNonexistentSymbol: [Function: getSuggestionForNonexistentSymbol], 152 | getSuggestionForNonexistentExport: [Function: getSuggestionForNonexistentExport], 153 | getBaseConstraintOfType: [Function: getBaseConstraintOfType], 154 | getDefaultFromTypeParameter: [Function: getDefaultFromTypeParameter], 155 | resolveName: [Function: resolveName], 156 | getJsxNamespace: [Function: getJsxNamespace], 157 | getAccessibleSymbolChain: [Function: getAccessibleSymbolChain], 158 | getTypePredicateOfSignature: [Function: getTypePredicateOfSignature], 159 | resolveExternalModuleSymbol: [Function: resolveExternalModuleSymbol], 160 | tryGetThisTypeAt: [Function: tryGetThisTypeAt], 161 | getTypeArgumentConstraint: [Function: getTypeArgumentConstraint], 162 | getSuggestionDiagnostics: [Function: getSuggestionDiagnostics], 163 | runWithCancellationToken: [Function: runWithCancellationToken] }, 164 | flags: 4, 165 | id: 10, 166 | intrinsicName: 'string' } } 167 | -------------------------------------------------------------------------------- /src/command.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import * as program from 'commander'; 3 | import { RestApi } from './restApi'; 4 | import * as updateNotifier from 'update-notifier'; 5 | import { join } from 'path'; 6 | import { d } from './utils/debug'; 7 | const pkg = require('../package.json'); 8 | 9 | const tasks = { 10 | async generateApi(chaincode: string, chaincodeConfigFile:string) { 11 | return await RestApi.generateApi(chaincode, chaincodeConfigFile); 12 | } 13 | }; 14 | 15 | program 16 | .command('generate ') 17 | .option('-c, --chaincode ', 'Chaincode project name') 18 | .option('-f, --chaincodeConfigFile ', 'name of the chaincode configuration file') 19 | .action(async (object: string, cmd: any) => { 20 | if ((!cmd || !cmd.chaincode) && object == 'api') { 21 | throw new Error('Please specify the chaincode project with the parameter -c'); 22 | } 23 | if ((!cmd || !cmd.chaincodeConfigFile) && object == 'api') { 24 | cmd.chaincodeConfigFile = 'org1.' + cmd.chaincode + '.config.json'; 25 | d('cmd.chaincodeConfigFile=='+cmd.chaincodeConfigFile); 26 | } 27 | switch (object) { 28 | case 'api': 29 | return await tasks.generateApi( 30 | cmd.chaincode, 31 | cmd.chaincodeConfigFile); 32 | default: 33 | // tslint:disable-next-line:max-line-length 34 | throw new Error(`Option ${object} is not a valid generator. Try with 'api'.`); 35 | } 36 | }); 37 | 38 | updateNotifier({ 39 | pkg, 40 | updateCheckInterval: 1000 * 60 41 | }).notify(); 42 | 43 | program.version(pkg.version); 44 | 45 | program.parse(process.argv); 46 | -------------------------------------------------------------------------------- /src/decorators/create.decorator.ts: -------------------------------------------------------------------------------- 1 | /** @module convector-core-controller */ 2 | /** @hidden */ 3 | export const createMetadataKey = Symbol('create'); 4 | 5 | /** 6 | * The controller decorator is used to pass the namespace context 7 | * to the [[Chaincode]] class. 8 | * 9 | * It's used at chaincode initialization to declare all the methods and avoid 10 | * method collision between controllers 11 | * 12 | * @decorator 13 | */ 14 | export function Create(className:string) { 15 | return function (target, propertyKey, descriptor) { 16 | return descriptor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/decorators/getAll.decorator.ts: -------------------------------------------------------------------------------- 1 | /** @module convector-core-controller */ 2 | /** @hidden */ 3 | export const getAllMetadataKey = Symbol('getAll'); 4 | 5 | /** 6 | * The controller decorator is used to pass the namespace context 7 | * to the [[Chaincode]] class. 8 | * 9 | * It's used at chaincode initialization to declare all the methods and avoid 10 | * method collision between controllers 11 | * 12 | * @decorator 13 | */ 14 | export function GetAll(className:string) { 15 | return function (target, propertyKey, descriptor) { 16 | return descriptor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/decorators/getById.decorator.ts: -------------------------------------------------------------------------------- 1 | /** @module convector-core-controller */ 2 | /** @hidden */ 3 | export const getByIdMetadataKey = Symbol('getById'); 4 | 5 | /** 6 | * The controller decorator is used to pass the namespace context 7 | * to the [[Chaincode]] class. 8 | * 9 | * It's used at chaincode initialization to declare all the methods and avoid 10 | * method collision between controllers 11 | * 12 | * @decorator 13 | */ 14 | export function GetById(className:string) { 15 | return function (target, propertyKey, descriptor) { 16 | return descriptor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './getById.decorator'; 2 | export * from './getAll.decorator'; 3 | export * from './create.decorator'; 4 | export * from './service.decorator'; 5 | -------------------------------------------------------------------------------- /src/decorators/service.decorator.ts: -------------------------------------------------------------------------------- 1 | /** @module convector-core-controller */ 2 | /** @hidden */ 3 | export const serviceMetadataKey = Symbol('service'); 4 | 5 | /** 6 | * The controller decorator is used to pass the namespace context 7 | * to the [[Chaincode]] class. 8 | * 9 | * It's used at chaincode initialization to declare all the methods and avoid 10 | * method collision between controllers 11 | * 12 | * @decorator 13 | */ 14 | export function Service() { 15 | return function (target, propertyKey, descriptor) { 16 | return descriptor; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/generators/_.env.ts.ejs: -------------------------------------------------------------------------------- 1 | APP_ID=<%= chaincodeName %>-app 2 | PORT=<%= apiEnvironment.PORT %> 3 | LOG_LEVEL=<%= apiEnvironment.LOG_LEVEL %> 4 | REQUEST_LIMIT=<%= apiEnvironment.REQUEST_LIMIT %> 5 | SESSION_SECRET=<%= apiEnvironment.SESSION_SECRET %> 6 | 7 | #Swagger 8 | SWAGGER_API_SPEC=<%= apiEnvironment.SWAGGER_API_SPEC %> 9 | 10 | KEYSTORE=<%= apiEnvironment.KEYSTORE %> 11 | USERCERT=<%= apiEnvironment.USERCERT %> 12 | ORGCERT=<%= apiEnvironment.ORGCERT %> 13 | NETWORKPROFILE=<%= apiEnvironment.NETWORKPROFILE %> 14 | CHANNEL=<%= apiEnvironment.CHANNEL %> 15 | CHAINCODE=<%= apiEnvironment.CHAINCODE %> 16 | COUCHDBVIEW=<%= apiEnvironment.COUCHDBVIEW %> 17 | COUCHDB_PORT=<%= apiEnvironment.COUCHDB_PORT %> 18 | COUCHDB_HOST=<%= apiEnvironment.COUCHDB_HOST %> 19 | COUCHDB_PROTOCOL=<%= apiEnvironment.COUCHDB_PROTOCOL %> 20 | -------------------------------------------------------------------------------- /src/generators/_app.ts.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worldsibu/convector-rest-api/d108e4d7970261dada25f68153919cbdd47f4087/src/generators/_app.ts.ejs -------------------------------------------------------------------------------- /src/generators/_package.json.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/worldsibu/convector-rest-api/d108e4d7970261dada25f68153919cbdd47f4087/src/generators/_package.json.ejs -------------------------------------------------------------------------------- /src/generators/_selfgenfabriccontext.ts.ejs: -------------------------------------------------------------------------------- 1 | /** Referenced from: https://github.com/ksachdeva/hyperledger-fabric-example/blob/c41fcaa352e78cbf3c7cfb210338ac0f20b8357e/src/client.ts */ 2 | import * as fs from 'fs'; 3 | import { join } from 'path'; 4 | import Client from 'fabric-client'; 5 | 6 | import { IEnrollmentRequest, IRegisterRequest } from 'fabric-ca-client'; 7 | 8 | export type UserParams = IRegisterRequest; 9 | export type AdminParams = IEnrollmentRequest; 10 | 11 | export namespace SelfGenContext { 12 | 13 | interface IdentityFiles { 14 | privateKey: string; 15 | signedCert: string; 16 | } 17 | 18 | export async function getClient() { 19 | // Check if needed 20 | let contextPath = ''; 21 | if (process.env.KEYSTORE[0] == '/') { 22 | contextPath = join(process.env.KEYSTORE + '/' + process.env.USERCERT); 23 | } 24 | else { 25 | contextPath = join(__dirname, process.env.KEYSTORE + '/' + process.env.USERCERT); 26 | } 27 | 28 | fs.readFile(contextPath, 'utf8', async function (err, data) { 29 | if (err) { 30 | // doesnt exist! Create it. 31 | const client = new Client(); 32 | 33 | d('Setting up the cryptoSuite ..'); 34 | 35 | // ## Setup the cryptosuite (we are using the built in default s/w based implementation) 36 | const cryptoSuite = Client.newCryptoSuite(); 37 | cryptoSuite.setCryptoKeyStore(Client.newCryptoKeyStore({ 38 | path: process.env.KEYSTORE 39 | })); 40 | 41 | client.setCryptoSuite(cryptoSuite); 42 | 43 | d('Setting up the keyvalue store ..'); 44 | 45 | // ## Setup the default keyvalue store where the state will be stored 46 | const store = await Client.newDefaultKeyValueStore({ 47 | path: process.env.KEYSTORE 48 | }); 49 | 50 | client.setStateStore(store); 51 | 52 | d('Creating the admin user context ..'); 53 | 54 | const privateKeyFile = fs.readdirSync(process.env.KEYSTORE + '/keystore')[0]; 55 | 56 | // ### GET THE NECESSRY KEY MATERIAL FOR THE ADMIN OF THE SPECIFIED ORG ## 57 | const cryptoContentOrgAdmin: IdentityFiles = { 58 | privateKey: process.env.KEYSTORE + '/keystore/' + privateKeyFile, 59 | signedCert: process.env.KEYSTORE + '/signcerts/cert.pem' 60 | }; 61 | 62 | await client.createUser({ 63 | username: process.env.USERCERT, 64 | mspid: `${process.env.ORGCERT}MSP`, 65 | cryptoContent: cryptoContentOrgAdmin, 66 | skipPersistence: false 67 | }); 68 | 69 | return client; 70 | } else { 71 | d('Context exists'); 72 | } 73 | }); 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/generators/_smartContractControllers.ts.ejs: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { ClientFactory } from "@worldsibu/convector-core-adapter"; 3 | import { SelfGenContext } from "./selfgenfabriccontext";<% dto.forEach(function(innerDto){ %> 4 | import { <%= innerDto.controllerName %> } from "<%= innerDto.chaincodeControllerFolder %>"; <% }) %> 5 | import { FabricControllerAdapter } from '@worldsibu/convector-adapter-fabric'; 6 | <% dto.forEach(function(innerDto){ %> 7 | export namespace <%= innerDto.controllerClient %> { 8 | export async function init(): Promise<<%= innerDto.controllerName %>> { 9 | const user = process.env.USERCERT || 'user1'; 10 | await SelfGenContext.getClient(); 11 | // Inject a Adapter of type *Fabric Controller* 12 | // Setup accordingly to the 13 | const adapter = new FabricControllerAdapter({ 14 | txTimeout: 300000, 15 | user: user, 16 | channel: process.env.CHANNEL, 17 | chaincode: process.env.CHAINCODE, 18 | keyStore: resolve(__dirname, process.env.KEYSTORE), 19 | networkProfile: resolve(__dirname, process.env.NETWORKPROFILE), 20 | userMspPath: resolve(__dirname, process.env.KEYSTORE), 21 | }); 22 | await adapter.init(); 23 | // Return your own implementation of the controller 24 | 25 | return ClientFactory(<%= innerDto.controllerName %>, adapter); 26 | } 27 | } 28 | <% }) %> 29 | -------------------------------------------------------------------------------- /src/generators/_smartContractModels.ts.ejs: -------------------------------------------------------------------------------- 1 | import { BaseStorage } from '@worldsibu/convector-core-storage'; 2 | import { CouchDBStorage } from '@worldsibu/convector-storage-couchdb'; 3 | <% dto.forEach(function(innerDto){ %><% innerDto.models.forEach(function(model){ %> 4 | import { <%= model %> as <%= model %>Model } from '<%= innerDto.chaincodeClientFolder %>';<% }); %><% }); %> 5 | 6 | export namespace Models {<% dto.forEach(function(innerDto){ %><% innerDto.models.forEach(function(model){ %> 7 | export const <%= model %> = <%= model %>Model;<% }); %><% }); %> 8 | } 9 | -------------------------------------------------------------------------------- /src/generators/_smartRoutes.ts.ejs: -------------------------------------------------------------------------------- 1 | import { Application } from 'express'; 2 | import <%= projectName %>Router from './api/controllers/examples/router' 3 | export default function routes(app: Application): void { 4 | app.use('/api/v1/<%= projectName %>', <%= projectName %>Router); 5 | }; 6 | -------------------------------------------------------------------------------- /src/generators/_tsconfig-api.json.ejs: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "target": "es6", 5 | "module": "commonjs", 6 | "esModuleInterop": true, 7 | "sourceMap": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "outDir": "dist", 11 | "typeRoots": ["node_modules/@types"] 12 | }, 13 | "include": ["typings.d.ts", "server/**/*.ts"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /src/generators/_utils.ts.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | getPriceChgArrow = function(value) { 3 | arrow_img_name = ""; 4 | if (value < 0) { 5 | arrow_img_name = "arrow_down12x13.gif"; 6 | } 7 | else { 8 | arrow_img_name = "arrow_up12x13.gif"; 9 | } 10 | return arrow_img_name; 11 | } 12 | %> 13 | -------------------------------------------------------------------------------- /src/generators/app.ts.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class AppTsGenerator extends BaseGenerator { 4 | contents = `import * as express from 'express'; 5 | import * as bodyParser from 'body-parser'; 6 | import { port as serverPort } from './env'; 7 | import router from './controllers/router'; 8 | 9 | const app: express.Application = express(); 10 | const port = serverPort; 11 | 12 | app.use(bodyParser.urlencoded({ 13 | extended: true, 14 | limit: '40mb' 15 | })); 16 | 17 | app.use(bodyParser.json({ limit: '40mb' })); 18 | 19 | app.use((req, res, next) => { 20 | res.header('Access-Control-Allow-Origin', '*'); 21 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); 22 | res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); 23 | next(); 24 | }); 25 | 26 | app.use('/', router); 27 | 28 | app.listen(port, () => 29 | console.log('Server started in port' + port)); 30 | 31 | module.exports = app;`; 32 | 33 | constructor(filename: string, path: string) { 34 | super(filename, path); 35 | } 36 | } -------------------------------------------------------------------------------- /src/generators/base.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | 4 | export abstract class BaseGenerator { 5 | contents: string; 6 | /** Breadcrumb */ 7 | success: string; 8 | 9 | constructor(public filename: string, public path: string) { 10 | } 11 | 12 | get filePath(): string { 13 | return join(this.path, this.filename); 14 | } 15 | run() { 16 | return SysWrapper.execContent(this.contents); 17 | } 18 | check() { 19 | return SysWrapper.existsPath(this.success); 20 | } 21 | save() { 22 | return SysWrapper.createFile(this.filePath, this.contents); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/generators/controller.ts.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | import { ApiConfigurationItem, HTTP_VERBS } from '../models/apiConfigurationFile'; 3 | import { join } from 'path'; 4 | import { ReflectionUtils } from '../utils'; 5 | 6 | export class ControllerTsOptions { 7 | controllers: { [k: string]: any }[]; 8 | config: ApiConfigurationItem[]; 9 | } 10 | export class ControllerTsGenerator extends BaseGenerator { 11 | contents = `import { Request, Response } from 'express'; 12 | ${this.options.controllers.map(controller => `import { ${controller.controller}BackEnd } from '../convector'; 13 | `).join('')} 14 | ${this.options.config.map(item => ` 15 | export async function ${item.controller}_${item.function}_${item.verb}(req: Request, res: Response): Promise{ 16 | try{ 17 | ${item.verb === HTTP_VERBS.GET ? `let params = req.params; 18 | res.status(200).send(await ${item.controller}BackEnd${item.query ? '.$query()' : ''} 19 | .${item.function}(${item.params ? item.params.map(param => `params.` + param.name).join(',') : ''})); 20 | ` : 21 | !item.verb || item.verb === HTTP_VERBS.POST || item.verb === HTTP_VERBS.PUT 22 | || item.verb === HTTP_VERBS.DELETE ? `let params = req.body; 23 | res.status(200).send(await ${item.controller}BackEnd${item.query ? '.$query()' : ''} 24 | .${item.function}(${item.params ? item.params.map(param => `params.` + param.name).join(',') : ''})); 25 | `: ''} 26 | } catch(ex) { 27 | console.log('Error ${item.verb} ${item.controller}_${item.function}', ex.stack); 28 | res.status(500).send(ex); 29 | } 30 | }`).join('')}`; 31 | 32 | constructor(filename: string, path: string, private options: ControllerTsOptions) { 33 | super(filename, path); 34 | } 35 | } -------------------------------------------------------------------------------- /src/generators/convector.ts.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class ConvectorOptions { 4 | controllers: { [k: string]: any }[]; 5 | } 6 | 7 | export class ConvectorTsGenerator extends BaseGenerator { 8 | contents = `import { join, resolve } from "path"; 9 | import { keyStore, identityName, channel, chaincode, networkProfile, identityId } from './env'; 10 | import * as fs from 'fs'; 11 | import { FabricControllerAdapter } from '@worldsibu/convector-adapter-fabric'; 12 | import { ClientFactory } from '@worldsibu/convector-core'; 13 | ${this.options.controllers.map(controller => `import { ${controller.controller} } from '${controller.name}'; 14 | `).join('')} 15 | 16 | const adapter = new FabricControllerAdapter({ 17 | txTimeout: 300000, 18 | user: identityName, 19 | channel, 20 | chaincode, 21 | keyStore: resolve(__dirname, keyStore), 22 | networkProfile: resolve(__dirname, networkProfile) 23 | // userMspPath: keyStore 24 | }); 25 | 26 | export const initAdapter = adapter.init(); 27 | 28 | ${this.options.controllers.map(controller => ` 29 | export const ${controller.controller}BackEnd = 30 | ClientFactory(${controller.controller}, adapter); 31 | `).join('')} 32 | 33 | const contextPath = join(keyStore + '/' + identityName); 34 | fs.readFile(contextPath, 'utf8', async function (err, data) { 35 | if (err) { 36 | throw new Error('Context in ' + contextPath 37 | + ' does not exist. Make sure that path resolves to your key stores folder'); 38 | } else { 39 | console.log('Context path with cryptographic materials exists'); 40 | } 41 | }); 42 | 43 | `; 44 | 45 | constructor(filename: string, path: string, private options: ConvectorOptions) { 46 | super(filename, path); 47 | } 48 | } -------------------------------------------------------------------------------- /src/generators/env.ts.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class EnvTsOptions { 4 | chaincodeName: string; 5 | } 6 | export class EnvTsGenerator extends BaseGenerator { 7 | 8 | contents = `import * as dotenv from 'dotenv'; 9 | dotenv.config(); 10 | 11 | const homedir = require('os').homedir(); 12 | 13 | export const chaincode = process.env.CHAINCODE || '${this.options.chaincodeName}'; 14 | export const channel = process.env.CHANNEL || 'ch1'; 15 | 16 | // Automatically extract credentials by the user id 17 | // If no .env config is found, fallback to Hurley defaults 18 | export const identityId = process.env.IDENTITYID || 'admin'; 19 | export const identityName = process.env.IDENTITY || 'admin'; 20 | export const identityOrg = process.env.ORG || 'org1'; 21 | 22 | export const keyStore = process.env.KEYSTORE || '/' + homedir + '/hyperledger-fabric-network/.hfc-' + identityOrg; 23 | export const networkProfile = process.env.NETWORKPROFILE || '/'+ homedir + 24 | '/hyperledger-fabric-network/network-profiles/' + 25 | identityOrg + '.network-profile.yaml'; 26 | 27 | export const port = process.env.PORT || 8000; 28 | 29 | // Default to common values 30 | export const couchDBView = process.env.COUCHDBVIEW || 'ch1_${this.options.chaincodeName}'; 31 | export const couchDBProtocol = process.env.COUCHDB_PROTOCOL || 'http'; 32 | export const couchDBHost = process.env.COUCHDB_HOST || 'localhost'; 33 | export const couchDBPort = process.env.COUCHDB_PORT || 5084;`; 34 | 35 | constructor(filename: string, path: string, private options: EnvTsOptions) { 36 | super(filename, path); 37 | } 38 | } -------------------------------------------------------------------------------- /src/generators/package.json.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class PackageJsonOptions { 4 | controllers: { [k: string]: any }[]; 5 | } 6 | export class PackageJsonGenerator extends BaseGenerator { 7 | contents = `{ 8 | "name": "server", 9 | "version": "1.0.0", 10 | "description": "Convector Autogenerated Server", 11 | "main": "index.js", 12 | "scripts": { 13 | "start": "npm run build && pm2-runtime pm2.config.json", 14 | "start:daemon": "pm2 startOrRestart pm2.config.json --no-daemon", 15 | "stop": "pm2 stop pm2.config.json", 16 | "tsc": "tsc", 17 | "clean": "rimraf dist client", 18 | "refresh": "./node_modules/pm2/bin/pm2 stop 0 && ./node_modules/pm2/bin/pm2 start 0", 19 | "build": "npm run clean && tsc", 20 | "prepare": "npm run build", 21 | "test": "mocha -r ts-node/register test/*.spec.ts --reporter spec" 22 | }, 23 | "author": "", 24 | "license": "ISC", 25 | "dependencies": { 26 | "body-parser": "^1.18.3", 27 | "config": "^1.30.0", 28 | "dotenv": "^6.0.0", 29 | "express": "^4.16.3", 30 | "node-couchdb": "^1.3.0", 31 | "x509": "^0.3.3", 32 | ${this.options.controllers.map(controller => 33 | `"${controller.name}": "^0.1.0", 34 | `).join('')} 35 | "@worldsibu/convector-adapter-fabric": "~1.3.0", 36 | "@worldsibu/convector-storage-couchdb": "~1.3.0", 37 | "swagger-ui-express": "^4.0.6" 38 | }, 39 | "devDependencies":{ 40 | "@types/bytebuffer": "^5.0.40", 41 | "@types/node": "^12.0.8", 42 | "mocha": "^5.2.0", 43 | "pm2": "^3.4.1", 44 | "rimraf": "^2.6.2", 45 | "ts-node": "^7.0.0", 46 | "typescript": "2.9.2" 47 | } 48 | }`; 49 | 50 | constructor(filename: string, path: string, private options: PackageJsonOptions) { 51 | super(filename, path); 52 | } 53 | } -------------------------------------------------------------------------------- /src/generators/pm2config.json.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class Pm2ConfigJsonGenerator extends BaseGenerator { 4 | contents = `{ 5 | "apps": [ 6 | { 7 | "name": "Convector Autogenerated Server", 8 | "script": "./dist/app.js", 9 | "node_args": "--inspect=0.0.0.0:8888", 10 | "error_file": "../log/error.log", 11 | "out_file": "../log/access.log", 12 | "env": {}, 13 | "watch": [ 14 | "src", 15 | "dist", 16 | "node_modules", 17 | ".env" 18 | ] 19 | } 20 | ] 21 | }`; 22 | 23 | constructor(filename: string, path: string) { 24 | super(filename, path); 25 | } 26 | } -------------------------------------------------------------------------------- /src/generators/router.ts.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | import { ApiConfigurationItem, HTTP_VERBS } from '../models/apiConfigurationFile'; 3 | import { join } from 'path'; 4 | import { ReflectionUtils } from '../utils'; 5 | 6 | export class RouterTsOptions { 7 | config: ApiConfigurationItem[]; 8 | } 9 | export class RouterTsGenerator extends BaseGenerator { 10 | contents = `import * as express from 'express'; 11 | import { ${this.options.config.map(item => ` 12 | ${item.controller}_${item.function}_${item.verb}`).join(',')} } from './controllers' 13 | export default express.Router()${this.options.config.map(item => ` 14 | .${item.verb}('/${item.plainController}/${item.function}${item.params && item.verb === HTTP_VERBS.GET ? 15 | item.params.map(param => `/:` + param.name).join('') 16 | : ''}', ${item.controller}_${item.function}_${item.verb})`).join('')} 17 | `; 18 | 19 | constructor(filename: string, path: string, private options: RouterTsOptions) { 20 | super(filename, path); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/generators/swagger.json.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | import { ApiConfigurationItem } from '../models/apiConfigurationFile'; 3 | 4 | export class SwaggerJsonOptions { 5 | config: ApiConfigurationItem[]; 6 | } 7 | export class SwaggerJsonGenerator extends BaseGenerator { 8 | contents = `{ 9 | "swagger": "2.0", 10 | "info": { 11 | "version": "0.1.0", 12 | "title": "Swagger Great Convector Solution", 13 | "description": "Autogenerated backed for a Convector solution" 14 | }, 15 | "basePath": "/api", 16 | "schemes": [ 17 | "http" 18 | ], 19 | "consumes": [ 20 | "application/json" 21 | ], 22 | "produces": [ 23 | "application/json" 24 | ], 25 | "paths": { 26 | ${this.options.config.map(item => `"/${item.plainController}/${item.function}": { 27 | "${item.verb}": { 28 | "description": "${item.verb} for ${item.plainController} ${item.function}", 29 | "operationId": "${item.plainController}_${item.function}", 30 | 31 | } 32 | }`).join(',')} 33 | } 34 | 35 | 36 | import * as express from 'express'; 37 | import { ${this.options.config.map(item => ` 38 | ${item.controller}_${item.function}`).join(',')} } from './controllers' 39 | export default express.Router()${this.options.config.map(item => ` 40 | .${item.verb}('/${item.plainController}/${item.function}', ${item.controller}_${item.function})`).join('')} 41 | `; 42 | 43 | constructor(filename: string, path: string, private options: SwaggerJsonOptions) { 44 | super(filename, path); 45 | } 46 | } -------------------------------------------------------------------------------- /src/generators/tsconfig.json.ts: -------------------------------------------------------------------------------- 1 | import { BaseGenerator } from './base'; 2 | 3 | export class TsConfigJsonGenerator extends BaseGenerator { 4 | contents = `{ 5 | "extends": "../../tsconfig.json", 6 | "compilerOptions": { 7 | "outDir": "./dist", 8 | "rootDir": "./src", 9 | "experimentalDecorators": true 10 | }, 11 | "include": [ 12 | "./src" 13 | ] 14 | }`; 15 | 16 | constructor(filename: string, path: string) { 17 | super(filename, path); 18 | } 19 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './restApi'; 2 | export * from './decorators/create.decorator'; 3 | export * from './decorators/getAll.decorator'; 4 | export * from './decorators/getById.decorator'; 5 | export * from './decorators/service.decorator'; 6 | -------------------------------------------------------------------------------- /src/models/apiConfigurationFile.ts: -------------------------------------------------------------------------------- 1 | export class ApiConfigurationList { 2 | items: ApiConfigurationItem[]; 3 | } 4 | export class ApiConfigurationItem { 5 | function: string; 6 | verb: HTTP_VERBS = HTTP_VERBS.POST; 7 | returns = false; 8 | controller: string; 9 | query = false; 10 | params: { 11 | name: string, 12 | type: string 13 | }[] = []; 14 | 15 | get plainController() { 16 | return this.controller.replace('Controller', '').toLowerCase(); 17 | } 18 | constructor(params: { function: string, verb: HTTP_VERBS, returns: boolean, controller: string, query?: boolean }) { 19 | this.function = params.function; 20 | this.verb = params.verb || HTTP_VERBS.POST; 21 | this.returns = params.returns || false; 22 | this.controller = params.controller; 23 | this.query = params.query || false; 24 | } 25 | } 26 | 27 | export enum HTTP_VERBS { 28 | GET = 'get', 29 | POST = 'post', 30 | PUT = 'put', 31 | DELETE = 'delete' 32 | } -------------------------------------------------------------------------------- /src/models/env.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | import { ApiConfig } from "../utils/apiConfig"; 5 | import { ApiEnvironment } from "../utils/apiEnvironment"; 6 | 7 | 8 | /** Model compiler object. */ 9 | export class EnvModel extends SmartModel { 10 | 11 | /** 12 | * 13 | * @param name File name 14 | * @param chaincodeName Chaincode Name 15 | * @param projectName Chaincode project name 16 | * @param ignoreConvention Save right here 17 | */ 18 | constructor( 19 | public name: string, 20 | public chaincodeName: string, 21 | public projectName: string, 22 | public ignoreConvention?: boolean) { 23 | super(name, projectName); 24 | //d("************apiConfig***********" + apiConfig); 25 | } 26 | 27 | recompile() { 28 | throw new Error('Method not implemented.'); 29 | } 30 | 31 | async save() { 32 | await SysWrapper.createFileFromTemplate( 33 | this.filePath, 34 | { 35 | apiEnvironment: this.selectedApiConfig, 36 | chaincodeName: this.chaincodeName 37 | }, this.templateFile); 38 | } 39 | 40 | /** TypeScript classs. */ 41 | get applicationName() { 42 | return this.chaincodeName.match(/[a-z]+/gi) 43 | .map(function (word) { 44 | return word + '-app'; 45 | }) 46 | .join(''); 47 | } 48 | 49 | /** TypeScript classs. */ 50 | get selectedApiConfig() { 51 | let apiConfig: ApiConfig = require(`${this.projectRoot}/` + 'api.json'); 52 | let selectedEnv = apiConfig.selected; 53 | let currentEnvObject: ApiEnvironment; 54 | 55 | for (let environment of apiConfig.environments) { 56 | if (environment.name == selectedEnv) { 57 | currentEnvObject = environment; 58 | break; 59 | } 60 | } 61 | return currentEnvObject; 62 | 63 | } 64 | 65 | /** 66 | * Static template file to be used. 67 | */ 68 | get templateFile() { 69 | return join(__dirname, '../../templates/_.env.ts.ejs'); 70 | } 71 | 72 | /** Actual file Path for the object. */ 73 | get filePath() { 74 | return `${this.projectRoot}/packages/${this.applicationName}/.env`; 75 | } 76 | 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './smartModel'; 2 | export * from './readme.model'; 3 | export * from './env.smart-model'; 4 | export * from './smartContractControllers.smart-model'; 5 | export * from './smartContractModels.smart-model'; 6 | export * from './smartRoutes.smart-model'; 7 | export * from './smartRouter.smart-model'; 8 | export * from './smartApiController.smart-model'; 9 | export * from './smartApiSwaggerYaml.smart-model'; 10 | -------------------------------------------------------------------------------- /src/models/readme.model.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-line-length 2 | import { join } from 'path'; 3 | import { SysWrapper } from '../utils/sysWrapper'; 4 | import { SmartModel } from './smartModel'; 5 | 6 | /** Controller compiler object. */ 7 | export class ReadmeModel extends SmartModel { 8 | constructor( 9 | public name: string, 10 | public projectName?: string, 11 | public ignoreConvention?: boolean) { 12 | super(name, projectName); 13 | } 14 | 15 | /** Save to disk. */ 16 | async save() { 17 | await SysWrapper.createFile( 18 | this.filePath, 19 | `# ${this.projectName} - ${this.name} 20 | 21 | This awesome project was created automatically with Convector Rest API. 22 | 23 | `); 24 | } 25 | 26 | /** 27 | * Static template file to be used. 28 | */ 29 | get templateFile() { 30 | return join(__dirname, '../../templates/_controller.ts.ejs'); 31 | } 32 | 33 | /** Actual file Path for the object. */ 34 | get filePath() { 35 | if (!this.ignoreConvention) { 36 | return `${this.projectRoot}/README.md`; 37 | } else { 38 | return join(process.cwd(), `README.md`); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/models/smartApiController.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | import { ReflectionUtils } from '../utils/reflectionUtils'; 5 | 6 | /** Model compiler object. */ 7 | export class SmartApiController extends SmartModel { 8 | 9 | /** 10 | * 11 | * @param name File name 12 | * @param chaincodeName Chaincode Name 13 | * @param projectName Chaincode project name 14 | * @param ignoreConvention Save right here 15 | */ 16 | 17 | constructor( 18 | public name: string, 19 | public chaincodeName: string, 20 | public projectName: string, 21 | public controllers: { [k: string]: any }[], 22 | public ignoreConvention?: boolean) { 23 | super(name, projectName); 24 | } 25 | 26 | recompile() { 27 | throw new Error('Method not implemented.'); 28 | } 29 | 30 | async save() { 31 | let dto = await this.getDTO(); 32 | await SysWrapper.createFileFromTemplate( 33 | 34 | this.filePath, 35 | { 36 | dto: dto 37 | 38 | }, this.templateFile); 39 | } 40 | 41 | /** TypeScript classs. */ 42 | 43 | get chaincodeClientFolder() { 44 | return this.chaincodeName.match(/[a-z]+/gi) 45 | .map(function (word) { 46 | return word + '-cc/client'; 47 | }) 48 | .join(''); 49 | } 50 | 51 | get applicationName() { 52 | return this.chaincodeName.match(/[a-z]+/gi) 53 | .map(function (word) { 54 | return word + '-app'; 55 | }) 56 | .join(''); 57 | } 58 | /** 59 | * Static template file to be used. 60 | */ 61 | get templateFile() { 62 | return join(__dirname, '../../templates/_smartApiController.ts.ejs'); 63 | } 64 | 65 | /** Actual file Path for the object. */ 66 | get filePath() { 67 | return `${this.projectRoot}/packages/server/src/controllers/controller.ts`; 68 | } 69 | 70 | private async getMethods(controllerName: string) { 71 | let controllersPattern = join(process.cwd(), `.`) + `/packages/` + controllerName + `/src/**/*controller*.ts`; 72 | let controllerNames = await ReflectionUtils.getClassNames(controllersPattern); 73 | let methods = await ReflectionUtils.getClassMethods(controllersPattern, controllerNames[0]); 74 | return methods; 75 | } 76 | 77 | private async getDTO(): Promise<{ [k: string]: any }> { 78 | let dto: { [k: string]: any }[] = []; 79 | for (let innerController of this.controllers) { 80 | let innerDto: { [k: string]: any } = {}; 81 | let getAllMethods = []; 82 | let getByIdMethods = []; 83 | let createMethods = []; 84 | let serviceMethods = []; 85 | 86 | let controllerMethods = await this.getMethods(innerController.name); 87 | for (let method of controllerMethods) { 88 | //d("method name: " + method.getName()); 89 | if (method.getDecorator('GetAll')) { 90 | //d("è un getterAll"); 91 | getAllMethods.push(method.getName()); 92 | } else if (method.getDecorator('GetById')) { 93 | //d("è un getterById"); 94 | getByIdMethods.push(method.getName()); 95 | } else if (method.getDecorator('Create')) { 96 | //d("è un creator"); 97 | let createObj: { [k: string]: any } = {}; 98 | createObj.methodName = method.getName(); 99 | createObj.methodParameterType = method.getDecorator('Create') 100 | .getArguments()[0].getText().replace(/[ '|\" ]/g, ''); 101 | // d(JSON.stringify(createObj)); 102 | createMethods.push(createObj); 103 | } else if (method.getDecorator('Service')) { 104 | // d( method.getName() + " è un service"); 105 | let serviceObj: { [k: string]: any } = {}; 106 | serviceObj.methodName = method.getName(); 107 | serviceObj.parameters = method.getParameters(); 108 | //d(JSON.stringify(serviceObj)); 109 | serviceMethods.push(serviceObj); 110 | } 111 | } 112 | 113 | innerDto.controllerClassName = innerController.controller; 114 | innerDto.getAllMethods = getAllMethods; 115 | innerDto.getByIdMethods = getByIdMethods; 116 | innerDto.createMethods = createMethods; 117 | innerDto.serviceMethods = serviceMethods; 118 | innerDto.name = innerController.name.substring(0, innerController.name.lastIndexOf('-cc')); 119 | 120 | dto.push(innerDto); 121 | } 122 | return dto; 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/models/smartApiSwaggerYaml.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | import { ClassDeclaration, MethodDeclaration, PropertyDeclaration } from 'ts-simple-ast'; 5 | 6 | import { ReflectionUtils } from '../utils/reflectionUtils'; 7 | 8 | /** Model compiler object. */ 9 | export class SmartApiSwaggerYamlModels extends SmartModel { 10 | 11 | /** 12 | * 13 | * @param name File name 14 | * @param chaincodeName Chaincode Name 15 | * @param projectName Chaincode project name 16 | * @param ignoreConvention Save right here 17 | */ 18 | 19 | private dto; 20 | 21 | constructor( 22 | public name: string, 23 | public chaincodeName: string, 24 | public projectName: string, 25 | public controllers: { [k: string]: any }[], 26 | public ignoreConvention?: boolean) { 27 | super(name, projectName); 28 | } 29 | 30 | recompile() { 31 | throw new Error('Method not implemented.'); 32 | } 33 | 34 | async save() { 35 | // let dto = JSON.stringify(this.getDTO(), null, 4); 36 | this.dto = await this.getDTO(); 37 | await SysWrapper.createFileFromTemplate( 38 | this.filePath, 39 | { 40 | dto: this.dto 41 | }, this.templateFile); 42 | } 43 | 44 | /** TypeScript classs. */ 45 | 46 | get chaincodeClientFolder() { 47 | return this.chaincodeName.match(/[a-z]+/gi) 48 | .map(function (word) { 49 | return word + '-cc/client'; 50 | }) 51 | .join(''); 52 | } 53 | 54 | get applicationName() { 55 | return this.chaincodeName.match(/[a-z]+/gi) 56 | .map(function (word) { 57 | return word + '-app'; 58 | }) 59 | .join(''); 60 | } 61 | /** 62 | * Static template file to be used. 63 | */ 64 | get templateFile() { 65 | return join(__dirname, '../../templates/_smartApiSwagger.yaml.ejs'); 66 | } 67 | 68 | /** Actual file Path for the object. */ 69 | get filePath() { 70 | return `packages/server/src/common/swagger/Api.yaml`; 71 | } 72 | 73 | private async getMethods(controllerPath: string) { 74 | let controllersPattern = join(process.cwd(), `.`) + 75 | controllerPath.substring(controllerPath.indexOf('file:') + 6) + `/src/**/*controller*.ts`; 76 | let controllerNames = await ReflectionUtils.getClassNames(controllersPattern); 77 | let methods = await ReflectionUtils.getClassMethods(controllersPattern, controllerNames[0]); 78 | return methods; 79 | } 80 | 81 | private async getModelClasses(controllerPath: string) { 82 | let modelsPattern = join(process.cwd(), `.`) + controllerPath.substring(controllerPath.indexOf('file:') + 6) 83 | + `/src/**/*model*.ts`; 84 | let modelClasses = await ReflectionUtils.getClasses(modelsPattern); 85 | return modelClasses; 86 | } 87 | 88 | private async getModelPropertiesDescription(controllerPath: string, className: string) { 89 | let modelsPattern = join(process.cwd(), `.`) + controllerPath.substring(controllerPath.indexOf('file:') + 6) + 90 | `/src/**/*model*.ts`; 91 | let classObj: { [k: string]: any } = {}; 92 | classObj.className = className; 93 | classObj.classNameLowered = classObj.className.charAt(0).toLowerCase() + classObj.className.slice(1); 94 | let modelObj = await ReflectionUtils.getClassParametersDescriptionFull(modelsPattern, className, classObj); 95 | return modelObj; 96 | } 97 | 98 | private static getPropertyExampleExactPath(exactPath: string, className: string) { 99 | // d('className prima='+className); 100 | if (className.lastIndexOf('/') >= 0) { 101 | className = className.substring(className.lastIndexOf('/') + 1, className.lastIndexOf('.model')); 102 | } 103 | //d('className='+className); 104 | 105 | let classObj: { [k: string]: any } = {}; 106 | classObj.className = className; 107 | classObj.classNameLowered = classObj.className.charAt(0).toLowerCase() + classObj.className.slice(1); 108 | let propertyExample = ReflectionUtils.getPropertyExample(className, exactPath); 109 | 110 | return propertyExample; 111 | } 112 | 113 | private static getPropertyExample(controllerPath: string, className: string) { 114 | // d('className prima='+className); 115 | let modelsPattern = ''; 116 | modelsPattern = join(process.cwd(), `.`) + controllerPath.substring(controllerPath.indexOf('file:') + 6) + 117 | `/src/**/*.ts`; 118 | 119 | if (className.lastIndexOf('/') >= 0) { 120 | className = className.substring(className.lastIndexOf('/') + 1, className.lastIndexOf('.model')); 121 | } 122 | //d('className='+className); 123 | 124 | let classObj: { [k: string]: any } = {}; 125 | classObj.className = className; 126 | classObj.classNameLowered = classObj.className.charAt(0).toLowerCase() + classObj.className.slice(1); 127 | let propertyExample = ReflectionUtils.getPropertyExample(className, modelsPattern); 128 | 129 | return propertyExample; 130 | } 131 | 132 | private async getDTO() { 133 | let dto: { [k: string]: any }[] = []; 134 | for (let innerController of this.controllers) { 135 | let innerDto: { [k: string]: any } = {}; 136 | innerDto.projectName = this.projectName; 137 | innerDto.models = []; 138 | innerDto.serviceMethods = []; 139 | innerDto.createMethods = []; 140 | innerDto.getByIdMethods = []; 141 | innerDto.getAllMethods = []; 142 | innerDto.name = innerController.name.substring(0, innerController.name.lastIndexOf('-cc')); 143 | 144 | let modelClasses = await this.getModelClasses(innerController.version); 145 | 146 | for (let modelClass of modelClasses) { 147 | let modelObj = await this.getModelPropertiesDescription(innerController.version, modelClass.getName()); 148 | innerDto.models.push(modelObj); 149 | } 150 | 151 | //d(innerDto.models); 152 | 153 | let controllerMethods = await this.getMethods(innerController.version); 154 | 155 | for (let method of controllerMethods) { 156 | 157 | if (method.getDecorator('GetAll')) { 158 | let getAllObj: { [k: string]: any } = {}; 159 | getAllObj.methodParameterType = method.getDecorator('GetAll').getArguments()[0].getText() 160 | .replace(/[ '|\' ]/g, ''); 161 | getAllObj.methodParameterTypeLowered = method.getDecorator('GetAll').getArguments()[0] 162 | .getText().replace(/[ '|\' ]/g, '').toLowerCase(); 163 | innerDto.getAllMethods.push(getAllObj); 164 | } else if (method.getDecorator('GetById')) { 165 | let getByIdObj: { [k: string]: any } = {}; 166 | getByIdObj.methodParameterType = method.getDecorator('GetById').getArguments()[0] 167 | .getText().replace(/[ '|\' ]/g, ''); 168 | getByIdObj.methodParameterTypeLowered = method.getDecorator('GetById').getArguments()[0] 169 | .getText().replace(/[ '|\' ]/g, '').toLowerCase(); 170 | innerDto.getByIdMethods.push(getByIdObj); 171 | } else if (method.getDecorator('Create')) { 172 | // d('è un creator'); 173 | let createObj: { [k: string]: any } = {}; 174 | createObj.methodName = method.getName(); 175 | createObj.methodParameterType = method.getDecorator('Create').getArguments()[0] 176 | .getText().replace(/[ '|\' ]/g, ''); 177 | createObj.methodParameterTypeLowered = method.getDecorator('Create').getArguments()[0] 178 | .getText().replace(/[ '|\' ]/g, '').toLowerCase(); 179 | innerDto.createMethods.push(createObj); 180 | } else if (method.getDecorator('Service')) { 181 | let serviceMethodParameters: { [k: string]: any }[] = []; 182 | let serviceObj: { [k: string]: any } = {}; 183 | serviceObj.methodName = method.getName(); 184 | serviceObj.methodWithParameters = false; 185 | serviceObj.methodEndPoint = serviceObj.methodName; 186 | let parametersForEndpoint = ''; 187 | let first = true; 188 | method.getParameters().forEach(async function (parameter) { 189 | //serviceObj.methodWithParameters = true; 190 | let param: { [k: string]: any } = {}; 191 | param.name = parameter.getName(); 192 | // d('param.name==' + param.name); 193 | if (parameter.getType().getArrayType() != undefined) { 194 | let arrayType = parameter.getType().getArrayType().getText(); 195 | let parameterType = parameter.getType().getText(); 196 | param.type = 'array'; 197 | param.itemType = arrayType.substring(arrayType.lastIndexOf('.') + 1); 198 | 199 | if (arrayType.indexOf('\'') >= 0) { 200 | param.importPath = arrayType.substring(arrayType.indexOf('\'') + 1, 201 | arrayType.lastIndexOf('\'')) + '.ts'; 202 | } else { 203 | param.importPath = undefined; 204 | } 205 | 206 | if (param.importPath == undefined) { 207 | param.example = SmartApiSwaggerYamlModels 208 | .getPropertyExample(innerController.version, param.itemType + '['); 209 | } else { 210 | param.example = SmartApiSwaggerYamlModels 211 | .getPropertyExampleExactPath(param.importPath, param.itemType + '['); 212 | } 213 | } else { 214 | let parameterType = parameter.getType().getText(); 215 | param.type = parameterType.substring(parameterType.lastIndexOf('.') + 1); 216 | param.example = SmartApiSwaggerYamlModels.getPropertyExample(innerController.version, param.type); 217 | } 218 | 219 | serviceMethodParameters.push(param); 220 | 221 | if (first) { 222 | parametersForEndpoint = '/' + '{' + parameter.getName() + '}'; 223 | first = false; 224 | } else { 225 | parametersForEndpoint = parametersForEndpoint.concat('/{').concat(parameter.getName()) + '}'; 226 | } 227 | }); 228 | serviceObj.methodParameters = serviceMethodParameters; 229 | innerDto.serviceMethods.push(serviceObj); 230 | } else { 231 | continue; 232 | } 233 | } 234 | dto.push(innerDto); 235 | } 236 | //d(dto[1].models[0].classProperties); 237 | return dto; 238 | } 239 | 240 | } 241 | -------------------------------------------------------------------------------- /src/models/smartContractControllers.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | import { Utils } from '../utils'; 5 | import { d } from '../utils/debug'; 6 | 7 | /** Model compiler object. */ 8 | export class SmartContractControllers extends SmartModel { 9 | 10 | /** 11 | * 12 | * @param name File name 13 | * @param chaincodeName Chaincode Name 14 | * @param projectName Chaincode project name 15 | * @param ignoreConvention Save right here 16 | */ 17 | 18 | private dto; 19 | 20 | constructor( 21 | public name: string, 22 | public chaincodeName: string, 23 | public projectName: string, 24 | public controllers: { [k: string]: any }[], 25 | public ignoreConvention?: boolean) { 26 | super(name, projectName); 27 | } 28 | 29 | recompile() { 30 | throw new Error('Method not implemented.'); 31 | } 32 | 33 | async save() { 34 | await SysWrapper.createFileFromTemplate( 35 | this.filePath, 36 | { 37 | dto: this.getDTO() 38 | 39 | }, this.templateFile); 40 | } 41 | 42 | /** TypeScript classs. */ 43 | get controllerClient() { 44 | return `${Utils.toPascalCase(this.chaincodeName)}ControllerClient`; 45 | } 46 | 47 | get chaincodeClientFolder() { 48 | return this.chaincodeName.match(/[a-z]+/gi) 49 | .map(function (word) { 50 | return word + '-cc/client'; 51 | }) 52 | .join(''); 53 | } 54 | 55 | get controllerName() { 56 | return this.chaincodeName.match(/[a-z]+/gi) 57 | .map(function (word) { 58 | return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase() + 'Controller'; 59 | }) 60 | .join(''); 61 | } 62 | 63 | get applicationName() { 64 | return this.chaincodeName.match(/[a-z]+/gi) 65 | .map(function (word) { 66 | return word + '-app'; 67 | }) 68 | .join(''); 69 | } 70 | 71 | /** 72 | * Static template file to be used. 73 | */ 74 | get templateFile() { 75 | return join(__dirname, '../../templates/_smartContractControllers.ts.ejs'); 76 | } 77 | 78 | /** Actual file Path for the object. */ 79 | get filePath() { 80 | return `${this.projectRoot}/packages/${this.applicationName}/server/smartContractControllers.ts`; 81 | } 82 | 83 | private getDTO() { 84 | let dto: { [k: string]: any }[] = []; 85 | for (let innerController of this.controllers) { 86 | let innerDto: { [k: string]: any } = {}; 87 | innerDto.controllerClient = innerController.controller + 'Client'; 88 | innerDto.chaincodeControllerFolder = innerController.name + '/dist/src'; 89 | innerDto.controllerName = innerController.controller; 90 | d("innerDto.controllerPath==" + innerDto.controllerPath); 91 | innerDto.name = innerController.name.substring(0, innerController.name.lastIndexOf("-cc")); 92 | //d(innerDto); 93 | dto.push(innerDto); 94 | } 95 | return dto; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/models/smartContractModels.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | 5 | import { ReflectionUtils } from '../utils/reflectionUtils'; 6 | import { d } from '../utils/debug'; 7 | 8 | 9 | /** Model compiler object. */ 10 | export class SmartContractModels extends SmartModel { 11 | 12 | /** 13 | * 14 | * @param name File name 15 | * @param chaincodeName Chaincode Name 16 | * @param projectName Chaincode project name 17 | * @param ignoreConvention Save right here 18 | */ 19 | 20 | private dto; 21 | 22 | constructor( 23 | public name: string, 24 | public chaincodeName: string, 25 | public projectName: string, 26 | public controllers: { [k: string]: any }[], 27 | public ignoreConvention?: boolean) { 28 | super(name, projectName); 29 | } 30 | 31 | recompile() { 32 | throw new Error('Method not implemented.'); 33 | } 34 | 35 | async save() { 36 | let dto = await this.getDTO(); 37 | await SysWrapper.createFileFromTemplate( 38 | this.filePath, 39 | { 40 | dto: dto 41 | 42 | }, this.templateFile); 43 | } 44 | 45 | /** TypeScript classs. */ 46 | 47 | private async getModelNames(controllerName: string, controllerPath: string) { 48 | let modelPath = ''; 49 | if (controllerPath[0] != '/') { 50 | modelPath = join(process.cwd(), '/') + controllerPath; 51 | } 52 | else { 53 | modelPath = controllerPath; 54 | } 55 | let modelsPattern = modelPath + `/src/**/*model*.ts`; 56 | let modelNames = await ReflectionUtils.getClassNames(modelsPattern); 57 | return modelNames; 58 | } 59 | 60 | private getChaincodeClientFolder(controllerName: string) { 61 | d(controllerName + "/dist/src"); 62 | return controllerName + "/dist/src"; 63 | } 64 | 65 | get applicationName() { 66 | return this.chaincodeName.match(/[a-z]+/gi) 67 | .map(function (word) { 68 | return word + '-app'; 69 | }) 70 | .join(''); 71 | } 72 | 73 | /** 74 | * Static template file to be used. 75 | */ 76 | get templateFile() { 77 | return join(__dirname, '../../templates/_smartContractModels.ts.ejs'); 78 | } 79 | 80 | /** Actual file Path for the object. */ 81 | get filePath() { 82 | return `${this.projectRoot}/packages/${this.applicationName}/server/smartContractModels.ts`; 83 | } 84 | 85 | private async getDTO() { 86 | let dto: { [k: string]: any }[] = []; 87 | for (let innerController of this.controllers) { 88 | let innerDto: { [k: string]: any } = {}; 89 | innerDto.controllerPath = innerController.version.substring(innerController.version.lastIndexOf("file:") + 5, innerController.version.length); 90 | innerDto.models = await this.getModelNames(innerController.name, innerDto.controllerPath); 91 | innerDto.chaincodeClientFolder = this.getChaincodeClientFolder(innerController.name); 92 | d('innerDto.models==' + innerDto.models); 93 | dto.push(innerDto); 94 | } 95 | d('dto==' + dto); 96 | return dto; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/models/smartModel.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | export abstract class SmartModel { 4 | constructor( 5 | public name: string, 6 | public projectName?: string) 7 | {} 8 | 9 | get projectRoot() 10 | { 11 | if (!this.projectName) 12 | { 13 | // Inside of project folder 14 | return join(process.cwd(), `.`); 15 | } else 16 | { 17 | // Outside of project folder 18 | return join(process.cwd(), `./${this.projectName}`); 19 | } 20 | } 21 | 22 | abstract save(): Promise; 23 | } 24 | -------------------------------------------------------------------------------- /src/models/smartRouter.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | import { MethodDeclaration } from "ts-simple-ast"; 5 | 6 | import { ReflectionUtils } from '../utils/reflectionUtils'; 7 | 8 | /** Model compiler object. */ 9 | export class SmartRouterModels extends SmartModel { 10 | 11 | /** 12 | * 13 | * @param name File name 14 | * @param chaincodeName Chaincode Name 15 | * @param projectName Chaincode project name 16 | * @param ignoreConvention Save right here 17 | */ 18 | constructor( 19 | public name: string, 20 | public chaincodeName: string, 21 | public projectName: string, 22 | public controllers: { [k: string]: any }[], 23 | public ignoreConvention?: boolean) { 24 | super(name, projectName); 25 | } 26 | 27 | recompile() { 28 | throw new Error('Method not implemented.'); 29 | } 30 | 31 | async save() { 32 | let dto = await this.getDTO(); 33 | await SysWrapper.createFileFromTemplate( 34 | this.filePath, 35 | { 36 | dto: dto 37 | 38 | }, this.templateFile); 39 | } 40 | 41 | /** TypeScript classs. */ 42 | 43 | get chaincodeClientFolder() { 44 | return this.chaincodeName.match(/[a-z]+/gi) 45 | .map(function (word) { 46 | return word + '-cc/client'; 47 | }) 48 | .join(''); 49 | } 50 | 51 | get applicationName() { 52 | return this.chaincodeName.match(/[a-z]+/gi) 53 | .map(function (word) { 54 | return word + '-app'; 55 | }) 56 | .join(''); 57 | } 58 | /** 59 | * Static template file to be used. 60 | */ 61 | get templateFile() { 62 | return join(__dirname, '../../templates/_smartRouter.ts.ejs'); 63 | } 64 | 65 | /** Actual file Path for the object. */ 66 | get filePath() { 67 | return `${this.projectRoot}/packages/server/src/controllers/router.ts`; 68 | } 69 | 70 | private async getMethods(controllerName: string) { 71 | let controllersPattern = join(process.cwd(), `.`) + `/packages/` + controllerName + `/src/**/*controller*.ts`; 72 | let controllerNames = await ReflectionUtils.getClassNames(controllersPattern); 73 | let methods = await ReflectionUtils.getClassMethods(controllersPattern, controllerNames[0]); 74 | return methods; 75 | } 76 | 77 | private async getDTO() { 78 | let dto: { [k: string]: any }[] = []; 79 | for (let innerController of this.controllers) { 80 | let innerDto: { [k: string]: any } = {}; 81 | let getAllMethods = []; 82 | let getByIdMethods = []; 83 | let createMethods = []; 84 | let serviceMethods = []; 85 | 86 | let controllerMethods = await this.getMethods(innerController.name); 87 | for (let method of controllerMethods) { 88 | //d("method name: " + method.getName()); 89 | if (method.getDecorator("GetAll")) { 90 | let getAllObj: { [k: string]: any } = {}; 91 | getAllObj.methodName = method.getName(); 92 | getAllObj.methodClassName = method.getDecorator("GetAll").getArguments()[0].getText().replace(/[ '|\" ]/g, ''); 93 | getAllObj.methodEndPoint = getAllObj.methodClassName.charAt(0).toLowerCase() + getAllObj.methodClassName.slice(1) + 's'; 94 | getAllMethods.push(getAllObj); 95 | } else if (method.getDecorator("GetById")) { 96 | let getIdObj: { [k: string]: any } = {}; 97 | getIdObj.methodName = method.getName(); 98 | getIdObj.methodClassName = method.getDecorator("GetById").getArguments()[0].getText().replace(/[ '|\" ]/g, ''); 99 | getIdObj.methodEndPoint = getIdObj.methodClassName.charAt(0).toLowerCase() + getIdObj.methodClassName.slice(1) + 's'; 100 | getByIdMethods.push(getIdObj); 101 | } else if (method.getDecorator("Create")) { 102 | //d("è un creator"); 103 | let createObj: { [k: string]: any } = {}; 104 | createObj.methodName = method.getName(); 105 | createObj.methodClassName = method.getDecorator("Create").getArguments()[0].getText().replace(/[ '|\" ]/g, ''); 106 | createObj.methodEndPoint = createObj.methodClassName.charAt(0).toLowerCase() + createObj.methodClassName.slice(1) + 's'; 107 | createMethods.push(createObj); 108 | } 109 | else if (method.getDecorator("Service")) { 110 | let serviceObj: { [k: string]: any } = {}; 111 | serviceObj.methodName = method.getName(); 112 | serviceObj.methodEndPoint = serviceObj.methodName; 113 | serviceMethods.push(serviceObj); 114 | } 115 | } 116 | 117 | innerDto.controllerClassName = innerController.controller; 118 | innerDto.getAllMethods = getAllMethods; 119 | innerDto.getByIdMethods = getByIdMethods; 120 | innerDto.createMethods = createMethods; 121 | innerDto.serviceMethods = serviceMethods; 122 | innerDto.name = innerController.name.substring(0, innerController.name.lastIndexOf("-cc")); 123 | 124 | dto.push(innerDto); 125 | } 126 | return dto; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /src/models/smartRoutes.smart-model.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { SysWrapper } from '../utils/sysWrapper'; 3 | import { SmartModel } from '../models/smartModel'; 4 | 5 | /** Model compiler object. */ 6 | export class SmartRoutesModels extends SmartModel { 7 | 8 | /** 9 | * 10 | * @param name File name 11 | * @param chaincodeName Chaincode Name 12 | * @param projectName Chaincode project name 13 | * @param ignoreConvention Save right here 14 | */ 15 | constructor( 16 | public name: string, 17 | public chaincodeName: string, 18 | public projectName: string, 19 | public ignoreConvention?: boolean) { 20 | super(name, projectName); 21 | } 22 | 23 | recompile() { 24 | throw new Error('Method not implemented.'); 25 | } 26 | 27 | async save() { 28 | await SysWrapper.createFileFromTemplate( 29 | this.filePath, 30 | { 31 | projectName: this.projectName 32 | }, this.templateFile); 33 | } 34 | 35 | /** TypeScript classs. */ 36 | 37 | 38 | get applicationName() { 39 | return this.chaincodeName.match(/[a-z]+/gi) 40 | .map(function (word) { 41 | return word + '-app'; 42 | }) 43 | .join(''); 44 | } 45 | 46 | /** 47 | * Static template file to be used. 48 | */ 49 | get templateFile() { 50 | return join(__dirname, '../../templates/_smartRoutes.ts.ejs'); 51 | } 52 | 53 | /** Actual file Path for the object. */ 54 | get filePath() { 55 | return `packages/${this.applicationName}/server/routes.ts`; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/restApi.ts: -------------------------------------------------------------------------------- 1 | import { ApiGenerator } from './apiGenerator'; 2 | import { Analytics } from './utils/analytics'; 3 | import { SysWrapper } from './utils/sysWrapper'; 4 | 5 | export class RestApi { 6 | 7 | static async generateApi(chaincode: string, chaincodeConfigFile: string) { 8 | const restApi = new RestApi(chaincode, chaincodeConfigFile); 9 | await restApi.generateApi(chaincode, chaincodeConfigFile); 10 | return restApi; 11 | } 12 | 13 | analytics: Analytics; 14 | 15 | /** 16 | * 17 | * @param name Project Name 18 | * @param chaincode File Name 19 | */ 20 | constructor(public name?: string, public chaincode?: string, public chaincodeConfigFile?: string) { 21 | this.analytics = new Analytics(); 22 | this.chaincode = this.chaincode || this.name; 23 | this.chaincodeConfigFile = this.chaincodeConfigFile; 24 | } 25 | 26 | public async generateApi(chaincode, chaincodeConfigFile) { 27 | let apiGenerator = new ApiGenerator(this.name, chaincode, chaincodeConfigFile); 28 | await apiGenerator.generate(); 29 | } 30 | 31 | // public static async compileApiApplication(chaincode) { 32 | // let command = 'npx'; 33 | // let tags = ['lerna', 'run', 'compile', '--scope', chaincode + '-app']; 34 | // await SysWrapper.executeCommand(command, tags); 35 | // } 36 | // 37 | // public static async startApiApplication(chaincode) { 38 | // let command = 'npx'; 39 | // let tags = ['lerna', 'run', 'dev', '--scope', chaincode + '-app', '--stream']; 40 | // await SysWrapper.executeCommand(command, tags); 41 | // } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/analytics.ts: -------------------------------------------------------------------------------- 1 | import * as Insight from 'insight'; 2 | import * as fs from 'fs-extra'; 3 | import { join } from 'path'; 4 | 5 | /** 6 | * Intelligence for Convector CLI. 7 | */ 8 | export class Analytics { 9 | trackingCode = 'UA-58712709-1'; 10 | insight: Insight; 11 | 12 | constructor() { 13 | let pkg = JSON.parse(fs.readFileSync(join(__dirname, '../../package.json')).toString()); 14 | // d(pkg); 15 | this.insight = new Insight({ 16 | // Google Analytics tracking code 17 | trackingCode: this.trackingCode, 18 | pkg, 19 | optOut: undefined 20 | }); 21 | this.init(); 22 | } 23 | 24 | /** Ask for permissions. */ 25 | init() { 26 | // this.insight.optOut = undefined; 27 | // Ask for permission the first time 28 | if (this.insight.optOut === undefined) { 29 | // tslint:disable-next-line:max-line-length 30 | this.insight.askPermission('convector-rest-api may anonymously report usage statistics to improve the tool over time'); 31 | this.permissionAcceptance(); 32 | } else { 33 | if (this.insight.optOut) { 34 | this.returnRejected(); 35 | } else { 36 | this.returnAccepted(); 37 | } 38 | } 39 | } 40 | 41 | /** Log a project creation */ 42 | trackNewProject(label?: string) { 43 | this.track(CategoryEnum.PROJECT, ActionEnum.NEW, label); 44 | } 45 | 46 | /** Log a chaincode creation */ 47 | trackGenerateCC(label?: string) { 48 | this.track(CategoryEnum.CHAINCODE, ActionEnum.GENERATE, label); 49 | } 50 | 51 | /** Log a model creation */ 52 | trackGenerateModel(label?: string) { 53 | this.track(CategoryEnum.MODEL, ActionEnum.GENERATE, label); 54 | } 55 | 56 | /** Log a controller creation */ 57 | trackGenerateController(label?: string) { 58 | this.track(CategoryEnum.CONTROLLER, ActionEnum.GENERATE, label); 59 | } 60 | 61 | /** Explicit logging. */ 62 | track(category: CategoryEnum, action: ActionEnum, label?: string) { 63 | // d(!this.insight.optOut); 64 | if (!this.insight.optOut) { 65 | this.insight.trackEvent({ 66 | category: category, 67 | action: action, 68 | label 69 | }); 70 | } 71 | } 72 | 73 | /** 74 | * Accepted to anonymously share insights. 75 | */ 76 | permissionAcceptance() { 77 | this.insight.trackEvent({ 78 | category: CategoryEnum.TRACKING, 79 | action: 'accept', 80 | }); 81 | } 82 | 83 | /** 84 | * Rejected to share insights. 85 | */ 86 | permissionRejection() { 87 | this.insight.trackEvent({ 88 | category: CategoryEnum.TRACKING, 89 | action: 'reject', 90 | }); 91 | } 92 | 93 | /** Returning user who accepted. */ 94 | returnAccepted() { 95 | this.insight.trackEvent({ 96 | category: CategoryEnum.TRACKING, 97 | action: 'returning', 98 | label: 'accepted before' 99 | }); 100 | } 101 | 102 | /** Returning user who rejected. */ 103 | returnRejected() { 104 | this.insight.trackEvent({ 105 | category: CategoryEnum.TRACKING, 106 | action: 'returning', 107 | label: 'rejected before' 108 | }); 109 | } 110 | } 111 | 112 | export enum CategoryEnum { 113 | TRACKING = 'tracking', 114 | PROJECT = 'project', 115 | CHAINCODE = 'chaincode', 116 | MODEL = 'model', 117 | CONTROLLER = 'controller', 118 | } 119 | export enum ActionEnum { 120 | NEW = 'new', 121 | GENERATE = 'generate' 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/apiConfig.ts: -------------------------------------------------------------------------------- 1 | import { ApiEnvironment } from "./apiEnvironment"; 2 | 3 | export interface ApiConfig 4 | { 5 | selected: string, 6 | environments: ApiEnvironment[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/apiEnvironment.ts: -------------------------------------------------------------------------------- 1 | export interface ApiEnvironment { 2 | name: string; 3 | APP_ID: string; 4 | PORT: string; 5 | LOG_LEVEL: string; 6 | REQUEST_LIMIT: string; 7 | SESSION_SECRET: string; 8 | SWAGGER_API_SPEC: string; 9 | KEYSTORE: string; 10 | USERCERT: string; 11 | ORGCERT: string; 12 | NETWORKPROFILE: string; 13 | CHANNEL: string; 14 | CHAINCODE: string; 15 | COUCHDBVIEW: string; 16 | COUCHDB_PORT: string; 17 | COUCHDB_HOST: string; 18 | COUCHDB_PROTOCOL: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/debug.ts: -------------------------------------------------------------------------------- 1 | import Debug from 'debug'; 2 | export const d = Debug('conv-rest-api'); -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './analytics'; 2 | export * from './reflectionUtils'; 3 | export * from './sysWrapper'; 4 | export * from './utils'; 5 | -------------------------------------------------------------------------------- /src/utils/reflectionUtils.ts: -------------------------------------------------------------------------------- 1 | import { join, relative } from 'path'; 2 | import { Project, ImportDeclaration, ClassDeclaration, ClassDeclarationStructure, MethodDeclaration, EnumDeclaration, PropertyDeclarationStructure, InterfaceDeclaration, InterfaceDeclarationStructure, SourceFile } from "ts-simple-ast"; 3 | import { d } from './debug'; 4 | 5 | export module ReflectionUtils { 6 | 7 | /** Check if a file exists. 8 | * @returns Promise 9 | */ 10 | export function getClassNames(pathPattern: string): Promise { 11 | return new Promise(function (fulfilled, rejected) { 12 | let classNames: string[] = []; 13 | const project = new Project({}); 14 | project.addExistingSourceFiles(pathPattern); 15 | let sourceFiles = project.getSourceFiles(); 16 | ////d("sourceFiles: " + sourceFiles); 17 | for (let sourceFile of sourceFiles) { 18 | classNames.push(sourceFile.getClasses()[0].getStructure().name); 19 | } 20 | fulfilled(classNames); 21 | }); 22 | } 23 | 24 | export function getFirstClass(pathPattern: string): Promise { 25 | return new Promise(function (fulfilled, rejected) { 26 | let classes: ClassDeclaration[] = []; 27 | const project = new Project({}); 28 | project.addExistingSourceFiles(pathPattern); 29 | let sourceFiles = project.getSourceFiles(); 30 | ////d("sourceFiles: " + sourceFiles); 31 | for (let sourceFile of sourceFiles) { 32 | classes.push(sourceFile.getClasses()[0]); 33 | } 34 | fulfilled(classes[0]); 35 | }); 36 | } 37 | 38 | export function getImportDeclarations(pathPattern: string): Promise { 39 | return new Promise(function (fulfilled, rejected) { 40 | let imports: ImportDeclaration[] = []; 41 | const project = new Project({}); 42 | project.addExistingSourceFiles(pathPattern); 43 | let sourceFiles = project.getSourceFiles(); 44 | ////d("sourceFiles: " + sourceFiles); 45 | for (let sourceFile of sourceFiles) { 46 | Array.prototype.push.apply(imports, sourceFile.getImportDeclarations()); 47 | } 48 | fulfilled(imports); 49 | }); 50 | } 51 | 52 | export function getClassImportDeclarations(pathPattern: string, className: string): ImportDeclaration[] { 53 | let imports: ImportDeclaration[] = []; 54 | const project = new Project({}); 55 | project.addExistingSourceFiles(pathPattern); 56 | let sourceFiles = project.getSourceFiles(); 57 | //d("sourceFiles: " + sourceFiles); 58 | for (let sourceFile of sourceFiles) { 59 | let sourceFileClasses = sourceFile.getClasses(); 60 | for (let sourceFileClass of sourceFileClasses) { 61 | if (sourceFileClass.getName() == className) { 62 | Array.prototype.push.apply(imports, sourceFile.getImportDeclarations()); 63 | //break; 64 | } 65 | } 66 | } 67 | //d("imports: " + imports); 68 | 69 | return imports; 70 | } 71 | 72 | export function getClass(pathPattern: string, className: string): ClassDeclaration { 73 | let classes: ClassDeclaration[] = []; 74 | const project = new Project({}); 75 | project.addExistingSourceFiles(pathPattern); 76 | let sourceFiles = project.getSourceFiles(); 77 | ////d("sourceFiles: " + sourceFiles); 78 | for (let sourceFile of sourceFiles) { 79 | ////d("classes:" + sourceFile.getClasses()); 80 | Array.prototype.push.apply(classes, sourceFile.getClasses()); 81 | } 82 | ////d("classes:" + classes); 83 | for (let i in classes) { 84 | // //d("class name to be found=" + className); 85 | // //d("class[" + i + "]=" + classes[i]); 86 | 87 | if (classes[i].getName().indexOf(className) >= 0) { 88 | // //d("class name found!=" + className); 89 | return classes[i]; 90 | } 91 | } 92 | // //d("class name not found :((( =" + className); 93 | return null; 94 | } 95 | 96 | export function getEnum(pathPattern: string, enumName: string): EnumDeclaration { 97 | let enums: EnumDeclaration[] = []; 98 | const project = new Project({}); 99 | project.addExistingSourceFiles(pathPattern); 100 | let sourceFiles = project.getSourceFiles(); 101 | ////d("sourceFiles: " + sourceFiles); 102 | for (let sourceFile of sourceFiles) { 103 | ////d("classes:" + sourceFile.getClasses()); 104 | Array.prototype.push.apply(enums, sourceFile.getEnums()); 105 | } 106 | ////d("classes:" + classes); 107 | for (let i in enums) { 108 | // //d("class name to be found=" + className); 109 | // //d("class[" + i + "]=" + classes[i]); 110 | 111 | if (enums[i].getName().indexOf(enumName) >= 0) { 112 | // //d("class name found!=" + className); 113 | //d("enum=="+enums[i].getText()); 114 | return enums[i]; 115 | } 116 | } 117 | // //d("class name not found :((( =" + className); 118 | return null; 119 | } 120 | 121 | export function getInterface(pathPattern: string, interfaceName: string): InterfaceDeclaration { 122 | let interfaces: InterfaceDeclaration[] = []; 123 | const project = new Project({}); 124 | project.addExistingSourceFiles(pathPattern); 125 | let sourceFiles = project.getSourceFiles(); 126 | ////d("sourceFiles: " + sourceFiles); 127 | for (let sourceFile of sourceFiles) { 128 | ////d("classes:" + sourceFile.getClasses()); 129 | Array.prototype.push.apply(interfaces, sourceFile.getInterfaces()); 130 | } 131 | ////d("classes:" + classes); 132 | for (let i in interfaces) { 133 | // //d("class name to be found=" + className); 134 | // //d("class[" + i + "]=" + classes[i]); 135 | 136 | if (interfaces[i].getName().indexOf(interfaceName) >= 0) { 137 | // //d("class name found!=" + className); 138 | //d("enum=="+enums[i].getText()); 139 | return interfaces[i]; 140 | } 141 | } 142 | // //d("class name not found :((( =" + className); 143 | return null; 144 | } 145 | 146 | 147 | export function getClassFromSource(sourceFile: SourceFile, className: string): Promise { 148 | return new Promise(function (fulfilled, rejected) { 149 | let classes = sourceFile.getClasses(); 150 | ////d("className:" + className); 151 | ////d("classes:" + classes); 152 | for (let i in classes) { 153 | ////d("classes[" + i + "] = " + classes[i].getName()); 154 | if (classes[i].getName().indexOf(className) >= 0) { 155 | ////d("getClassFromSource found class " + className); 156 | return fulfilled(classes[i]); 157 | } 158 | } 159 | return fulfilled(null); 160 | }); 161 | } 162 | 163 | export function getClasses(pathPattern: string): Promise { 164 | return new Promise(function (fulfilled, rejected) { 165 | let classes: ClassDeclaration[] = []; 166 | const project = new Project({}); 167 | project.addExistingSourceFiles(pathPattern); 168 | let sourceFiles = project.getSourceFiles(); 169 | ////d("sourceFiles: " + sourceFiles); 170 | for (let sourceFile of sourceFiles) { 171 | Array.prototype.push.apply(classes, sourceFile.getClasses()); 172 | } 173 | fulfilled(classes); 174 | }); 175 | } 176 | 177 | /** Check if a file exists. 178 | * @returns Promise 179 | */ 180 | export function getClassMethods(pathPattern: string, className: string): Promise { 181 | return new Promise(function (fulfilled, rejected) { 182 | let methods: MethodDeclaration[] = []; 183 | const project = new Project({}); 184 | project.addExistingSourceFiles(pathPattern); 185 | let sourceFiles = project.getSourceFiles(); 186 | ////d("sourceFiles: " + sourceFiles); 187 | let sourceFile = sourceFiles[0]; 188 | const classClass = sourceFile.getClassOrThrow(className); 189 | methods = classClass.getMethods(); 190 | fulfilled(methods); 191 | }); 192 | } 193 | 194 | //deve essere settato il path preciso 195 | export function getClassParameters(pathPattern: string, className: string): Promise { 196 | return new Promise(function (fulfilled, rejected) { 197 | let properties: PropertyDeclarationStructure[] = []; 198 | const project = new Project({}); 199 | project.addExistingSourceFiles(pathPattern); 200 | let sourceFiles = project.getSourceFiles(); 201 | ////d("sourceFiles: " + sourceFiles); 202 | let sourceFile = sourceFiles[0]; 203 | const classClass = sourceFile.getClassOrThrow(className); 204 | properties = classClass.getStructure().properties; 205 | ////d("className: " + className); 206 | for (let prop of properties) { 207 | ////d("prop name: " + prop.name); 208 | let propType; 209 | if (prop.type == undefined) { 210 | propType = 'string'; 211 | } 212 | else { 213 | propType = prop.type; 214 | } 215 | ////d("prop type: " + propType); 216 | } 217 | 218 | fulfilled(properties); 219 | }); 220 | } 221 | 222 | export function getPropertyExample(propertyType: string, pathPattern: string, originalType?: string) { 223 | 224 | if (propertyType == undefined || propertyType == 'string') { 225 | return 'a_text'; 226 | } 227 | else if (propertyType == 'number') { 228 | return '123'; 229 | } 230 | else if (propertyType == 'boolean') { 231 | return true; 232 | } 233 | else if (propertyType.toString().indexOf("[") >= 0) { 234 | let returnExample = "["; 235 | // d("invoking recursion with:" + propertyType.toString().substring(0, propertyType.toString().indexOf("["))); 236 | returnExample += 237 | ReflectionUtils.getPropertyExample(propertyType.toString().substring(0, propertyType.toString().indexOf("[")), pathPattern) + ", " + 238 | ReflectionUtils.getPropertyExample(propertyType.toString().substring(0, propertyType.toString().indexOf("[")), pathPattern) 239 | ; 240 | returnExample += "]"; 241 | // d("return example = " + returnExample); 242 | return returnExample; 243 | } 244 | else if (propertyType == 'enum') { 245 | let returnExample = ""; 246 | let enumClass = getEnum(pathPattern, originalType); 247 | // return enumClass.getMembers()[0].getText(); 248 | return 0; 249 | } 250 | else { 251 | let classObj: { [k: string]: any } = {}; 252 | classObj = ReflectionUtils.getClassParametersDescriptionFull(pathPattern, propertyType, {}); 253 | if (classObj == {}) { 254 | return null; 255 | } 256 | 257 | let classProperties = classObj.classProperties; 258 | let returnExample = "{"; 259 | if (classProperties != undefined) { 260 | for (let property of classProperties) { 261 | if (property.propName === 'type') { 262 | continue; 263 | } 264 | //d("invoking recursion for " + property.originalPropType+ " in " + pathPattern); 265 | returnExample += '\n' + property.propName + ': ' + ReflectionUtils.getPropertyExample(property.originalPropType, pathPattern) + ','; 266 | //d("getting example for " + property.originalPropType ); 267 | } 268 | if (returnExample != "{") { 269 | returnExample = returnExample.slice(0, -1); 270 | } 271 | } 272 | returnExample += "}"; 273 | // d("getting example for " + propertyType + " example:" + returnExample); 274 | return returnExample; 275 | } 276 | } 277 | 278 | export function getClassTypeAndPath(pathPattern: string, className: string): { [k: string]: any } { 279 | let returnObj: { [k: string]: any } = {}; 280 | 281 | let classClass = getClass(pathPattern, className); 282 | 283 | let enumClass = null; 284 | let interfaceClass = null; 285 | 286 | let isClassInterface = false; 287 | let isEnum = false; 288 | let isClass = false; 289 | 290 | if (classClass == null || classClass == undefined) { 291 | enumClass = getEnum(pathPattern, className); 292 | if (enumClass == null || enumClass == undefined) { 293 | interfaceClass = getInterface(pathPattern, className); 294 | if (interfaceClass != null && interfaceClass != undefined) { 295 | //d(className + " is an interface"); 296 | isClassInterface = true; 297 | } 298 | else { 299 | return undefined; 300 | } 301 | } 302 | else { 303 | //d(className + " is an interface"); 304 | isEnum = true; 305 | } 306 | } 307 | else { 308 | isClass = true; 309 | } 310 | 311 | returnObj.pathPattern = pathPattern; 312 | returnObj.isClass = isClass; 313 | returnObj.isEnum = isEnum; 314 | returnObj.isClassInterface = isClassInterface; 315 | returnObj.classClass = classClass; 316 | returnObj.enumClass = enumClass; 317 | returnObj.interfaceClass = interfaceClass; 318 | 319 | return returnObj; 320 | } 321 | 322 | 323 | export function getClassParametersDescriptionFull( 324 | pathPattern: string, className: string, classObj: 325 | { [k: string]: any }): { [k: string]: any } { 326 | 327 | 328 | let inspectedClassObj: { [k: string]: any } = getClassTypeAndPath(pathPattern, className); 329 | let classClassStructure: ClassDeclarationStructure | InterfaceDeclarationStructure = {}; 330 | 331 | if (inspectedClassObj == undefined) { 332 | return classObj; 333 | } 334 | 335 | if (!inspectedClassObj.isClassInterface) { 336 | classClassStructure = inspectedClassObj.classClass.getStructure(); 337 | } 338 | else { 339 | //d("className " + className + " is an interface!" ); 340 | classClassStructure = inspectedClassObj.interfaceClass.getStructure(); 341 | } 342 | 343 | let classProperties = classClassStructure.properties; 344 | 345 | //d("classObj.classProperties before=" + classObj.classProperties); 346 | 347 | if (classClassStructure.extends != undefined) { 348 | //d(classClassStructure.name + " extends " + classClassStructure.extends); 349 | //d(className + " extends => " + classClassStructure.extends); 350 | let baseClassName = ""; 351 | if (!(classClassStructure.extends instanceof Array)) { 352 | // d(classClassStructure.extends + " not an arrray"); 353 | if (classClassStructure.extends.indexOf("<") >= 0) { 354 | baseClassName = classClassStructure.extends.substring(0, classClassStructure.extends.indexOf("<")); 355 | } 356 | else if (classClassStructure.extends.indexOf("[") >= 0) { 357 | baseClassName = classClassStructure.extends.substring(0, classClassStructure.extends.indexOf("[")); 358 | } 359 | else { 360 | baseClassName = classClassStructure.extends; 361 | } 362 | let importDeclarations: ImportDeclaration[] = []; 363 | //d("invoking getClassImportDeclarations"); 364 | 365 | // d("looking for :" + className + " in " + pathPattern); 366 | importDeclarations = ReflectionUtils.getClassImportDeclarations(pathPattern, className); 367 | 368 | //d("importDeclarations for " + className + " has length: " + importDeclarations.length); 369 | 370 | for (let importDeclaration of importDeclarations) { 371 | if (importDeclaration.getText().indexOf(baseClassName) >= 0) { 372 | let moduleSpecifier = importDeclaration.getModuleSpecifierValue(); 373 | let baseClassSource = ""; 374 | if (moduleSpecifier.startsWith(".")) { 375 | baseClassSource = pathPattern.substring(0, pathPattern.lastIndexOf("/") + 1) + moduleSpecifier.substring(moduleSpecifier.indexOf("/") + 1) + ".ts"; 376 | } 377 | else if (moduleSpecifier.startsWith("/")) { 378 | baseClassSource = moduleSpecifier; 379 | } 380 | else { 381 | baseClassSource = join(process.cwd(), `.`) + `/node_modules/` + moduleSpecifier + "/**/*.ts*"; 382 | } 383 | 384 | //d("moduleSpecifier: " + moduleSpecifier); 385 | // d("normal for " + className); 386 | // d(getClassTypeAndPath(pathPattern, className)); 387 | // d("alternative for " + className); 388 | // d(getClassTypeAndPath(baseClassSource, className)); 389 | 390 | if (getClassTypeAndPath(pathPattern, baseClassName) != undefined) { 391 | getClassParametersDescriptionFull(pathPattern, baseClassName, classObj); 392 | } 393 | else if (getClassTypeAndPath(baseClassSource, baseClassName) != undefined) { 394 | getClassParametersDescriptionFull(baseClassSource, baseClassName, classObj); 395 | } 396 | else { 397 | d(baseClassName + " non trovato nè in " + pathPattern + " nè in " + baseClassSource); 398 | } 399 | // d("looking for :" + baseClassName + " in " + pathPattern); 400 | 401 | 402 | } 403 | } 404 | } 405 | else { 406 | d(className + " is an interface that extends:"); 407 | for (let baseClass of classClassStructure.extends) { 408 | d(" - " + baseClass); 409 | if (baseClass.indexOf("<") >= 0) { 410 | baseClassName = baseClass.substring(0, baseClass.indexOf("<")); 411 | } 412 | else if (baseClass.indexOf("[") >= 0) { 413 | baseClassName = baseClass.substring(0, baseClass.indexOf("[")); 414 | } 415 | else { 416 | baseClassName = baseClass; 417 | } 418 | let importDeclarations: ImportDeclaration[] = []; 419 | //d("invoking getClassImportDeclarations"); 420 | 421 | d("looking for :" + className + " in " + pathPattern); 422 | importDeclarations = ReflectionUtils.getClassImportDeclarations(pathPattern, className); 423 | 424 | //d("importDeclarations for " + className + " has length: " + importDeclarations.length); 425 | 426 | for (let importDeclaration of importDeclarations) { 427 | if (importDeclaration.getText().indexOf(baseClassName) >= 0) { 428 | let moduleSpecifier = importDeclaration.getModuleSpecifierValue(); 429 | let baseClassSource = ""; 430 | if (moduleSpecifier.startsWith(".")) { 431 | baseClassSource = pathPattern.substring(0, pathPattern.lastIndexOf("/") + 1) + moduleSpecifier.substring(moduleSpecifier.indexOf("/") + 1) + ".ts"; 432 | } 433 | else if (moduleSpecifier.startsWith("/")) { 434 | baseClassSource = moduleSpecifier; 435 | } 436 | else { 437 | baseClassSource = join(process.cwd(), `.`) + `/node_modules/` + moduleSpecifier + "/**/*.ts*"; 438 | } 439 | 440 | //d("moduleSpecifier: " + moduleSpecifier); 441 | // d("normal for " + className); 442 | // d(getClassTypeAndPath(pathPattern, className)); 443 | // d("alternative for " + className); 444 | // d(getClassTypeAndPath(baseClassSource, className)); 445 | 446 | if (getClassTypeAndPath(pathPattern, baseClassName) != undefined) { 447 | d(baseClassName + " found in " + pathPattern); 448 | getClassParametersDescriptionFull(pathPattern, baseClassName, classObj); 449 | } 450 | else if (getClassTypeAndPath(baseClassSource, baseClassName) != undefined) { 451 | d(baseClassName + " found in " + baseClassSource); 452 | getClassParametersDescriptionFull(baseClassSource, baseClassName, classObj); 453 | } 454 | else { 455 | d(baseClassName + " non trovato nè in " + pathPattern + " nè in " + baseClassSource); 456 | } 457 | // d("looking for :" + baseClassName + " in " + pathPattern); 458 | 459 | 460 | } 461 | } 462 | } 463 | } 464 | } 465 | 466 | for (let property of classProperties) { 467 | 468 | let pathPatternForExample = pathPattern; 469 | if (property.name === 'type') { 470 | continue; 471 | } 472 | 473 | let propertyPresent = false; 474 | if (classObj.classProperties != undefined) { 475 | for (let p of classObj.classProperties) { 476 | if (p.propName == property.name) { 477 | propertyPresent = true; 478 | break; 479 | } 480 | } 481 | } 482 | 483 | if (propertyPresent) { 484 | continue; 485 | } 486 | 487 | let classPropertyObj: { [k: string]: any } = {}; 488 | classPropertyObj.propName = property.name; 489 | 490 | if (property.type == undefined) { 491 | classPropertyObj.propType = 'string'; 492 | classPropertyObj.propExample = ReflectionUtils.getPropertyExample(classPropertyObj.propType, pathPattern); 493 | } 494 | else if (property.type.toString().indexOf("[") >= 0) { 495 | classPropertyObj.propType = 'array'; 496 | classPropertyObj.originalPropType = property.type.toString(); 497 | classPropertyObj.propItemType = property.type.toString().substring(0, property.type.toString().indexOf("[")); 498 | // d("invoking getPropertyExample for " + classPropertyObj.originalPropType); 499 | 500 | classPropertyObj.propExample = ReflectionUtils.getPropertyExample(classPropertyObj.originalPropType, pathPattern); 501 | } 502 | else { 503 | //d("tento se " + property.type.toString() + " è un enum in " + pathPattern); 504 | let enumClass = getEnum(pathPattern, property.type.toString()); 505 | 506 | if (enumClass != null) { 507 | //d(property.type.toString() + " is an enum with values: " + enumClass.getMembers()); 508 | classPropertyObj.propType = 'enum'; 509 | classPropertyObj.originalType = property.type.toString(); 510 | classPropertyObj.enumValues = enumClass.getMembers(); 511 | } 512 | else { 513 | let inspectedClassObj: { [k: string]: any } = getClassTypeAndPath(pathPattern, className); 514 | 515 | if (inspectedClassObj.interfaceClass != null) { 516 | classPropertyObj.propType = 'interface'; 517 | classPropertyObj.interfaceProperties = inspectedClassObj.interfaceClass.getStructure().properties; 518 | //d(classPropertyObj.interfaceProperties); 519 | } 520 | else { 521 | classPropertyObj.propType = property.type.toString(); 522 | if (ReflectionUtils.getClassTypeAndPath(pathPattern, classPropertyObj.propType) == undefined) { 523 | // d("non trovo " + classPropertyObj.propType + " in " + pathPattern); 524 | let importDeclarations = ReflectionUtils.getClassImportDeclarations(pathPattern, className); 525 | 526 | for (let importDeclaration of importDeclarations) { 527 | //d("cerco " + classPropertyObj.propType + " in " + importDeclaration.getText()); 528 | if (importDeclaration.getText().indexOf(classPropertyObj.propType) >= 0) { 529 | let moduleSpecifier = importDeclaration.getModuleSpecifierValue(); 530 | //d("trovato " + classPropertyObj.propType + " in " + moduleSpecifier); 531 | let baseClassSource = join(process.cwd(), `.`) + `/node_modules/` + moduleSpecifier + "/**/*.ts*"; 532 | let classObject = getClassTypeAndPath(baseClassSource, classPropertyObj.propType); 533 | 534 | if (classObject != undefined) { 535 | //d("trovato " + classPropertyObj.propType + " in " + classObject.pathPattern); 536 | pathPatternForExample = classObject.pathPattern; 537 | break; 538 | } 539 | 540 | } 541 | } 542 | } 543 | } 544 | } 545 | classPropertyObj.propExample = ReflectionUtils.getPropertyExample(classPropertyObj.propType, pathPatternForExample, classPropertyObj.originalType); 546 | } 547 | 548 | 549 | if (classObj.classProperties == undefined) { 550 | classObj.classProperties = []; 551 | } 552 | classObj.classProperties.push(classPropertyObj); 553 | // modelPropertiesNames.push(property.getName()); 554 | } 555 | 556 | 557 | return classObj; 558 | 559 | 560 | } 561 | 562 | function removeDuplicates(arr) { 563 | let unique_array = []; 564 | let unique_names_array = []; 565 | 566 | for (let i = 0; i < arr.length; i++) { 567 | if (unique_names_array.indexOf(arr[i].name) == -1) { 568 | unique_names_array.push(arr[i].name); 569 | unique_array.push(arr[i]); 570 | } 571 | } 572 | return unique_array 573 | } 574 | 575 | } 576 | -------------------------------------------------------------------------------- /src/utils/sysWrapper.ts: -------------------------------------------------------------------------------- 1 | import * as ejs from 'ejs'; 2 | import * as fs from 'fs-extra'; 3 | import { exec } from 'shelljs'; 4 | import memFs = require('mem-fs'); 5 | import memFsEditor = require('mem-fs-editor'); 6 | 7 | export module SysWrapper { 8 | let d = console.log; 9 | /** Create a file to specific path from contents. 10 | * @returns Promise 11 | */ 12 | export function createFile(filePath: string, contents: string): Promise { 13 | return new Promise(function (fulfilled, rejected) { 14 | try { 15 | write(filePath, contents, fulfilled); 16 | } catch (ex) { 17 | rejected(ex); 18 | } 19 | }); 20 | } 21 | 22 | /** 23 | * Renders to disk a file from a template 24 | * @param filePath Destination path 25 | * @param contents Contents in JSON format 26 | * @param templatePath Location of the template 27 | */ 28 | export function createFileFromTemplate(filePath: string, contents: any, templatePath: string): Promise { 29 | return renderTemplateFromFile(templatePath, contents).then((compiledContents: string) => { 30 | return new Promise(function (fulfilled, rejected) { 31 | try { 32 | write(filePath, compiledContents, fulfilled); 33 | } catch (ex) { 34 | rejected(ex); 35 | } 36 | }); 37 | }); 38 | } 39 | 40 | export function createFileRaw(filePath: string, contents: Buffer): Promise { 41 | return new Promise(function (fulfilled, rejected) { 42 | try { 43 | writeBuffer(filePath, contents, fulfilled); 44 | } catch (ex) { 45 | rejected(ex); 46 | } 47 | }); 48 | } 49 | 50 | /** 51 | * Remove based on a path. 52 | */ 53 | export function removePath(filePath: string): Promise { 54 | return remove(filePath); 55 | } 56 | 57 | /** 58 | * Get a file from a path. 59 | */ 60 | export function getFile(filePath: string, content?: any): Promise { 61 | return new Promise(async function (fulfilled, rejected) { 62 | if (content) { 63 | fulfilled(await renderTemplateFromFile(filePath, content)); 64 | } else { 65 | const store = memFs.create(); 66 | const editor = memFsEditor.create(store); 67 | try { 68 | let file = editor.read(filePath); 69 | if (!file) { 70 | rejected('Empty or not found file.'); 71 | } 72 | fulfilled(file); 73 | } catch (ex) { 74 | rejected(ex); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | /** 81 | * Execs a specified file. 82 | * @param filePath Full file path 83 | * @param content Optional contents to render 84 | */ 85 | export async function execFile(filePath: string, 86 | content?: any): Promise { 87 | 88 | if (content) { 89 | let renderedFileContent = await renderTemplateFromFile(filePath, content); 90 | 91 | return exec( 92 | renderedFileContent, 93 | { silent: false } 94 | ); 95 | } else { 96 | let simpleFileContent = await getFile(filePath); 97 | 98 | if (exec(simpleFileContent, 99 | { silent: false, shell: '/bin/bash' }).code !== 0) { 100 | d('Found error while running script!'); 101 | throw new Error('Errors found in script, stopping execution'); 102 | } 103 | } 104 | } 105 | 106 | export async function execContent(content: any): Promise { 107 | if (exec(content, 108 | { silent: false, shell: '/bin/bash' }).code !== 0) { 109 | d('Found error while running script!'); 110 | throw new Error('Errors found in script, stopping execution'); 111 | } 112 | } 113 | 114 | /** Get a file from a path. 115 | * @returns Promise 116 | */ 117 | export function getFileRaw(filePath: string): Promise { 118 | return new Promise(async function (fulfilled, rejected) { 119 | try { 120 | const store = memFs.create(); 121 | const editor = memFsEditor.create(store); 122 | 123 | let file = editor.read(filePath, { raw: true }); 124 | if (!file) { 125 | rejected('Empty or not found file.'); 126 | } 127 | fulfilled(file); 128 | } catch (ex) { 129 | rejected(ex); 130 | } 131 | }); 132 | } 133 | 134 | /** Copies a file 135 | * @returns Promise 136 | */ 137 | export function copyFile(from: string, to: string): Promise { 138 | return new Promise(function (fulfilled, rejected) { 139 | const store = memFs.create(); 140 | const editor = memFsEditor.create(store); 141 | editor.copy(from, to); 142 | editor.commit([], fulfilled); 143 | }); 144 | } 145 | 146 | /** Check if a file exists. 147 | * @returns Promise 148 | */ 149 | export function existsPath(filePath: string): Promise { 150 | return new Promise(function (fulfilled, rejected) { 151 | const store = memFs.create(); 152 | const editor = memFsEditor.create(store); 153 | fulfilled(editor.exists(filePath)); 154 | }); 155 | } 156 | 157 | /** Create a file in JSON format. 158 | * @returns Promise 159 | */ 160 | export function createJSON(filePath: string, contents: any): Promise { 161 | return new Promise(function (fulfilled, rejected) { 162 | const store = memFs.create(); 163 | const editor = memFsEditor.create(store); 164 | editor.writeJSON(filePath, contents); 165 | editor.commit([], fulfilled); 166 | }); 167 | } 168 | 169 | /** Get a JSON from file sys. 170 | * @return Promise JSON object 171 | */ 172 | export function getJSON(filePath: string): Promise { 173 | return new Promise(function (fulfilled, rejected) { 174 | try { 175 | const store = memFs.create(); 176 | const editor = memFsEditor.create(store); 177 | fulfilled(editor.readJSON(filePath)); 178 | } catch (ex) { 179 | rejected(ex); 180 | } 181 | }); 182 | } 183 | 184 | /** Create a folder. 185 | * @return Promise 186 | */ 187 | export function createFolder(folder: string) { 188 | return fs.ensureDir(folder); 189 | } 190 | 191 | /** 192 | * Render a new string from a disk file and contents. 193 | * */ 194 | export function renderTemplateFromFile(filePath: string, content: any): Promise { 195 | return new Promise(async function (fulfilled, rejected) { 196 | const store = memFs.create(); 197 | const editor = memFsEditor.create(store); 198 | let file = editor.read(filePath); 199 | if (!file) { 200 | return rejected(Error(`${filePath} does not exist.`)); 201 | } 202 | fulfilled(await renderTemplateFromContent(file, content)); 203 | }); 204 | } 205 | 206 | /** Render a new string from a string template and contentes. */ 207 | export function renderTemplateFromContent(templateContent: string, content: any): Promise { 208 | return new Promise(function (fulfilled, rejected) { 209 | let renderedFile = ejs.render(templateContent, content); 210 | fulfilled(renderedFile); 211 | }); 212 | } 213 | 214 | function remove(filePath: string): Promise { 215 | return fs.remove(filePath); 216 | } 217 | 218 | function write(filePath: string, contents: string, cb: any) { 219 | const store = memFs.create(); 220 | const editor = memFsEditor.create(store); 221 | editor.write(filePath, 222 | contents); 223 | editor.commit([], cb); 224 | } 225 | 226 | function writeBuffer(filePath: string, contents: Buffer, cb: any) { 227 | const store = memFs.create(); 228 | const editor = memFsEditor.create(store); 229 | 230 | editor.write(filePath, 231 | contents); 232 | editor.commit([], cb); 233 | } 234 | 235 | export function enumFilesInFolder(folder: string): Promise { 236 | return new Promise(function (fulfilled, rejected) { 237 | fs.readdir(folder, (err, files) => { 238 | fulfilled(files); 239 | }); 240 | }); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export namespace Utils { 2 | export function toPascalCase(text: string): string { 3 | const camelText = toCamelCase(text); 4 | 5 | return camelText[0].toUpperCase() + camelText.slice(1); 6 | } 7 | 8 | export function toCamelCase(text: string): string { 9 | if(!text){ 10 | return ''; 11 | } 12 | text = text.replace(text[0], text[0].toLowerCase()); 13 | return text.replace(/([a-z])-([a-z])/ig, (matches, g1, g2) => `${g1}${g2.toUpperCase()}`); 14 | } 15 | } -------------------------------------------------------------------------------- /templates/_smartApiController.ts.ejs: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | <% dto.forEach(function(innerDto){ %> 3 | import { <%= innerDto.controllerClassName %>BackEnd } from '../convector';<% }); %> 4 | 5 | export class Controller {<% dto.forEach(function(innerDto){ %><% innerDto.getAllMethods.forEach(function(method){ %> 6 | async <%= innerDto.name + '_' + method %>(req: Request, res: Response): Promise { 7 | let result = await <%= innerDto.controllerClassName %>BackEnd.<%= method %>(); 8 | res.status(200).json(result); 9 | } 10 | <% }); %> 11 | <% innerDto.getByIdMethods.forEach(function(method){ %> 12 | async <%= innerDto.name + '_' + method %>(req: Request, res: Response) { 13 | let result = await <%= innerDto.controllerClassName %>BackEnd.<%= method %>(req.params.id); 14 | if (!result) { 15 | return res.status(404); 16 | } 17 | res.json(result); 18 | } 19 | <% }); 20 | innerDto.createMethods.forEach(function(method){ %> 21 | async <%= innerDto.name + '_' + method.methodName %>(req: Request, res: Response) { 22 | try { 23 | let modelRaw = req.body; 24 | let result = await <%= innerDto.controllerClassName %>BackEnd.<%= method.methodName %>(modelRaw); 25 | res.json(result); 26 | } catch (ex) { 27 | console.log(ex.message, ex.stack); 28 | res.status(500).send(ex.stack); 29 | } 30 | } 31 | <% }); 32 | innerDto.serviceMethods.forEach(function(method){ %> 33 | async <%= innerDto.name + '_' + method.methodName %>(req: Request, res: Response) { 34 | try { 35 | let params = req.body; 36 | <% let parameters = new Array(); 37 | method.parameters.forEach(function(parameter){ 38 | parameters.push("params."+ parameter.getName()); 39 | }); 40 | parameterString = parameters.join(','); %> 41 | let returnObject = await <%= innerDto.controllerClassName %>BackEnd.<%= method.methodName %>(<%= parameterString %>); 42 | if (returnObject === undefined) { 43 | return res.status(404); 44 | } 45 | res.json(returnObject); 46 | } catch (ex) { 47 | console.log(ex.message, ex.stack); 48 | res.status(500).send(ex.stack); 49 | } 50 | } 51 | <% }); %> 52 | <% }); %> 53 | } 54 | export default new Controller(); 55 | -------------------------------------------------------------------------------- /templates/_smartApiSwagger.yaml.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | 3 | %> 4 | swagger: "2.0" 5 | info: 6 | version: 1.0.0 7 | title: <%= dto[0].projectName %> 8 | description: <%= dto[0].projectName %> REST API Application 9 | basePath: /api/v1/<%= dto[0].projectName %> 10 | 11 | tags: 12 | <% dto.forEach(function(innerDto){ 13 | innerDto.models.forEach(function(model){ %> 14 | - name: <%= model.className %>s 15 | description: Simple <%= model.classNameLowered %> endpoints 16 | <% }); %><% }); %> 17 | 18 | consumes: 19 | - application/json 20 | produces: 21 | - application/json 22 | 23 | definitions: 24 | <% dto.forEach(function(innerDto){ 25 | let prefix = ''; 26 | let controllerMethodPrefix = ''; 27 | if (dto.length > 1) { 28 | prefix = innerDto.name + '/'; 29 | controllerMethodPrefix = innerDto.name + '_'; 30 | } 31 | innerDto.models.forEach(function(model){ %> 32 | 33 | <%= controllerMethodPrefix + model.className %>Body: 34 | type: object 35 | title: <%= model.className %> 36 | required: 37 | <% model.classProperties.forEach(function(prop){ %> - <%= prop.propName %> 38 | <% }); %>properties:<% model.classProperties.forEach(function(prop){ %> 39 | <%= prop.propName %>:<% if (prop.propType == 'enum') { 40 | let enumCounter = 0; %> 41 | type: integer 42 | enum:<% prop.enumValues.forEach(function(enumValue){ %> 43 | - <%= enumCounter %><% enumCounter++; }); 44 | %><% } else if (prop.propType == 'array') { %> 45 | type: <%= prop.propType %> 46 | items: 47 | type: <%= prop.propItemType %><% } else { %> 48 | type: <%= prop.propType %><% } %> 49 | example: <%= prop.propExample %><% }); 50 | }); 51 | }); %> 52 | <% dto.forEach(function(innerDto){ 53 | let prefix = ''; 54 | let controllerMethodPrefix = ''; 55 | if (dto.length > 1) { 56 | prefix = innerDto.name + '/'; 57 | controllerMethodPrefix = innerDto.name + '_'; 58 | } innerDto.serviceMethods.forEach(function(serviceMethod){ %> 59 | 60 | <%= controllerMethodPrefix + serviceMethod.methodName %>Body: 61 | type: object 62 | title: <%= controllerMethodPrefix + serviceMethod.methodName %>Params 63 | required: 64 | <% serviceMethod.methodParameters.forEach(function(parameter){ %> - <%= parameter.name %> 65 | <% }); %>properties:<% serviceMethod.methodParameters.forEach(function(parameter){ %> 66 | <%= parameter.name %>: 67 | type: <%= parameter.type %><% if (parameter.type == 'array') { %> 68 | items: 69 | type: <%= parameter.itemType %><% } %> 70 | example: <%= parameter.example %><% }); 71 | }); 72 | });%> 73 | 74 | paths: 75 | <% dto.forEach(function(innerDto){ 76 | let prefix = ''; 77 | let controllerMethodPrefix = ''; 78 | if (dto.length > 1) { 79 | prefix = innerDto.name + '/'; 80 | controllerMethodPrefix = innerDto.name + '_'; 81 | } 82 | innerDto.models.forEach(function(model){ 83 | let prefixWrote = false; 84 | innerDto.getAllMethods.forEach(function(method){ 85 | if (method.methodParameterType == model.className) { 86 | if (prefixWrote == false) { %> 87 | 88 | /<%= prefix + model.classNameLowered %>s: <% 89 | prefixWrote = true; 90 | } %> 91 | get: 92 | tags: 93 | - <%= method.methodParameterType %>s 94 | description: Fetch all <%= method.methodParameterTypeLowered %>s 95 | responses: 96 | 200: 97 | description: Returns all <%= method.methodParameterTypeLowered %>s <% } }); 98 | innerDto.createMethods.forEach(function(method){ 99 | if (method.methodParameterType == model.className) { 100 | if (prefixWrote == false) { %> 101 | 102 | /<%= prefix + model.classNameLowered %>s: <% 103 | prefixWrote = true; 104 | } %> 105 | post: 106 | tags: 107 | - <%= method.methodParameterType %>s 108 | description: Create a new <%= method.methodParameterTypeLowered %> 109 | parameters: 110 | - name: <%= method.methodParameterTypeLowered %> 111 | in: body 112 | description: a <%= method.methodParameterTypeLowered %> 113 | required: true 114 | schema: 115 | $ref: "#/definitions/<%= controllerMethodPrefix + method.methodParameterType %>Body" 116 | responses: 117 | 200: 118 | description: Successful insertion of <%= method.methodParameterTypeLowered %>s <% } });%> 119 | <% innerDto.getByIdMethods.forEach(function(method){ 120 | if (method.methodParameterType == model.className) { %> 121 | 122 | /<%= prefix + method.methodParameterTypeLowered %>s/{id}: 123 | get: 124 | tags: 125 | - <%= method.methodParameterType %>s 126 | parameters: 127 | - name: id 128 | in: path 129 | required: true 130 | description: The id of the <%= method.methodParameterTypeLowered %> to retrieve 131 | type: string 132 | responses: 133 | 200: 134 | description: Return the <%= method.methodParameterTypeLowered %> with the specified id 135 | 404: 136 | description: <%= method.methodParameterType %> not found <% } }); });%> 137 | <% innerDto.serviceMethods.forEach(function(serviceMethod){ %> 138 | 139 | /<%= prefix + serviceMethod.methodEndPoint %>: 140 | post: 141 | tags: 142 | - <%= serviceMethod.methodName %> 143 | description: <%= serviceMethod.methodName %> 144 | parameters: 145 | - name: <%= controllerMethodPrefix + serviceMethod.methodName %>Params 146 | in: body 147 | required: true 148 | schema: 149 | $ref: "#/definitions/<%= controllerMethodPrefix + serviceMethod.methodName %>Body" 150 | responses: 151 | 200: 152 | description: <%= serviceMethod.methodName %> executed correctly 153 | 500: 154 | description: <%= serviceMethod.methodName %> raised an exception 155 | <% }); 156 | });%> 157 | -------------------------------------------------------------------------------- /templates/_smartRouter.ts.ejs: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import controller from './controller' 3 | export default express.Router()<% 4 | dto.forEach(function(innerDto){ 5 | let prefix = ''; 6 | let controllerMethodPrefix = ''; 7 | if (dto.length > 1) { 8 | prefix = innerDto.name + '/'; 9 | } 10 | controllerMethodPrefix = innerDto.name + '_';%> 11 | <% innerDto.createMethods.forEach(function(createMethod){ %> 12 | .post('/<%= prefix + createMethod.methodEndPoint %>/', controller.<%= controllerMethodPrefix + createMethod.methodName %>)<% }); 13 | innerDto.getAllMethods.forEach(function(getAllMethod){ %> 14 | .get('/<%= prefix + getAllMethod.methodEndPoint %>/', controller.<%= controllerMethodPrefix + getAllMethod.methodName %>)<% }); 15 | innerDto.getByIdMethods.forEach(function(getByIdMethod){ %> 16 | .get('/<%= prefix + getByIdMethod.methodEndPoint %>/:id', controller.<%= controllerMethodPrefix + getByIdMethod.methodName %>)<% }); 17 | innerDto.serviceMethods.forEach(function(serviceMethod){ %> 18 | .post('/<%= prefix + serviceMethod.methodEndPoint %>', controller.<%= controllerMethodPrefix + serviceMethod.methodName %>)<% });%><% });%> 19 | 20 | ; 21 | -------------------------------------------------------------------------------- /templates_scripts/generate_api_template.bash: -------------------------------------------------------------------------------- 1 | echo '[conv-rest-api] Removing previously generated app (packages/server)' 2 | rm -rf packages/server 3 | echo '[conv-rest-api] Generating stub folder ' 4 | cd packages/ 5 | mkdir server 6 | cd server 7 | 8 | wait 9 | 10 | echo '[conv-rest-api] Exiting packages folder...' 11 | cd ../ 12 | cd ../ 13 | echo $PWD 14 | echo '[conv-rest-api] Compiling new app...' 15 | # npx lerna run compile --scope $1-app 16 | echo '[conv-rest-api] Bootstrapping...' 17 | # npx lerna bootstrap 18 | echo '[conv-rest-api] Adding chaincode(s)...' 19 | # declare -a chaincodes=$2 20 | # for ccode in "${chaincodes[@]}"; do 21 | # npx lerna add $ccode --scope=$1-app --include-filtered-dependencies 22 | # done 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "rootDir": "./src", 5 | "target": "es5", 6 | "module": "commonjs", 7 | "declaration": true, 8 | "sourceMap": true, 9 | "importHelpers": true, 10 | "experimentalDecorators": true, 11 | "removeComments": true, 12 | "lib": [ 13 | "es2015" 14 | ] 15 | }, 16 | "include": [ 17 | "./**/*" 18 | ], 19 | "exclude": [ 20 | "./tests/**/*" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "comment-format": [ 5 | false 6 | ], 7 | "curly": true, 8 | "eofline": false, 9 | "forin": true, 10 | "indent": [ 11 | true, 12 | "spaces", 13 | 2 14 | ], 15 | "label-position": true, 16 | "max-line-length": [ 17 | true, 18 | 120 19 | ], 20 | "member-access": false, 21 | "no-conditional-assignment": true, 22 | "no-require-imports": false, 23 | "member-ordering": [ 24 | true, 25 | { 26 | "order": [ 27 | "static-field", 28 | "instance-field", 29 | "constructor", 30 | "public-instance-method", 31 | "protected-instance-method", 32 | "private-instance-method" 33 | ] 34 | } 35 | ], 36 | "no-any": false, 37 | "no-arg": true, 38 | "no-bitwise": true, 39 | "no-magic-numbers": false, 40 | "object-literal-key-quotes": [ 41 | false 42 | ], 43 | "object-literal-shorthand": false, 44 | "no-mergeable-namespace": true, 45 | "no-console": [ 46 | false, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-construct": true, 54 | "no-debugger": true, 55 | "no-duplicate-variable": true, 56 | "no-empty": true, 57 | "no-empty-interface": true, 58 | "no-eval": true, 59 | "completed-docs": [ 60 | false 61 | ], 62 | "no-inferrable-types": [ 63 | true 64 | ], 65 | "no-invalid-this": [ 66 | true 67 | ], 68 | "cyclomatic-complexity": [ 69 | true, 70 | 20 71 | ], 72 | "no-shadowed-variable": true, 73 | "no-string-literal": false, 74 | "no-switch-case-fall-through": true, 75 | "no-for-in-array": false, 76 | "no-floating-promises": true, 77 | "no-trailing-whitespace": false, 78 | "no-unused-expression": true, 79 | "no-unused-variable": true, 80 | "no-unsafe-finally": true, 81 | "no-parameter-properties": false, 82 | "no-use-before-declare": true, 83 | "use-isnan": true, 84 | "switch-default": true, 85 | "jsdoc-format": false, 86 | "no-var-requires": false, 87 | "no-var-keyword": true, 88 | "no-default-export": true, 89 | "restrict-plus-operands": false, 90 | "object-literal-sort-keys": false, 91 | "prefer-for-of": true, 92 | "no-null-keyword": false, 93 | "new-parens": true, 94 | "no-angle-bracket-type-assertion": false, 95 | "no-consecutive-blank-lines": true, 96 | "file-header": [ 97 | false 98 | ], 99 | "align": [ 100 | true 101 | ], 102 | "array-type": [ 103 | false 104 | ], 105 | "interface-name": [ 106 | false 107 | ], 108 | "arrow-parens": false, 109 | "one-variable-per-declaration": [ 110 | false 111 | ], 112 | "one-line": [ 113 | true, 114 | "check-open-brace", 115 | "check-catch", 116 | "check-else", 117 | "check-finally" 118 | ], 119 | "quotemark": [ 120 | true, 121 | "single", 122 | "avoid-escape" 123 | ], 124 | "radix": false, 125 | "semicolon": [ 126 | true 127 | ], 128 | "trailing-comma": [ 129 | false 130 | ], 131 | "triple-equals": [ 132 | false 133 | ], 134 | "typedef": [ 135 | true 136 | ], 137 | "variable-name": [ 138 | true, 139 | "allow-leading-underscore", 140 | "allow-pascal-case" 141 | ], 142 | "whitespace": [ 143 | false 144 | ] 145 | } 146 | } 147 | --------------------------------------------------------------------------------