├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── MAINTAINERS.md ├── README-ja.md ├── README.md ├── contract ├── .eslintrc.js ├── index.js ├── lib │ └── fabcar.js ├── package-lock.json └── package.json ├── package-lock.json ├── run-local.md └── web-app ├── client ├── .editorconfig ├── .gitignore ├── README.md ├── angular.json ├── e2e │ ├── protractor.conf.js │ ├── src │ │ ├── app.e2e-spec.ts │ │ ├── app.po.ts │ │ └── components │ │ │ ├── changeCarOwnerForm.ts │ │ │ ├── createCarForm.ts │ │ │ └── submit.ts │ └── tsconfig.e2e.json ├── package-lock.json ├── package.json ├── src │ ├── app │ │ ├── api.service.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.spec.ts │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── car.ts │ │ ├── query-all-cars │ │ │ ├── query-all-cars.component.html │ │ │ ├── query-all-cars.component.scss │ │ │ ├── query-all-cars.component.spec.ts │ │ │ └── query-all-cars.component.ts │ │ └── submit-component │ │ │ ├── change-car-owner-form │ │ │ ├── change-car-owner-form.component.html │ │ │ ├── change-car-owner-form.component.scss │ │ │ └── change-car-owner-form.component.ts │ │ │ ├── create-car-form │ │ │ ├── create-car-form.component.html │ │ │ ├── create-car-form.component.scss │ │ │ ├── create-car-form.component.spec.ts │ │ │ └── create-car-form.component.ts │ │ │ ├── submit.component.html │ │ │ ├── submit.component.scss │ │ │ ├── submit.component.spec.ts │ │ │ └── submit.component.ts │ ├── assets │ │ └── .gitkeep │ ├── browserslist │ ├── colors.scss │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── favicon.ico │ ├── ibmDuocolors.scss │ ├── index.html │ ├── karma.conf.js │ ├── main.ts │ ├── polyfills.ts │ ├── styles.css │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ └── tslint.json ├── tsconfig.json └── tslint.json └── server ├── .eslintrc.js ├── config.json ├── deregisterUser.js ├── enrollAdmin.js ├── mychannel_fabcar_profile.json ├── package-lock.json ├── package.json ├── registerUser.js └── src ├── app.js └── fabric └── network.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | **/.DS_Store 3 | node_modules/ 4 | /dist 5 | wallet/ 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | .vscode/ 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw* 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10.16.3" 4 | 5 | script: 6 | - cd contract && npm install && npm run lint && cd ../web-app/server && npm install && npm run lint && cd ../client && npm install && npm run lint 7 | 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. 6 | 7 | In addition to the issue tracker, [#ibmcode on 8 | Slack](https://dwopen.slack.com) is the best way to get into contact with the 9 | project's maintainers. 10 | 11 | To contribute code, documentation, or tests, please submit a pull request to 12 | the GitHub repository. Generally, we expect two maintainers to review your pull 13 | request before it is approved for merging. For more details, see the 14 | [MAINTAINERS](MAINTAINERS.md) page. 15 | -------------------------------------------------------------------------------- /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 | 203 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | ## Maintainers Guide 2 | 3 | This guide is intended for maintainers — anybody with commit access to one or 4 | more Developer Journey repositories. 5 | 6 | ## Methodology: 7 | 8 | A master branch. This branch MUST be releasable at all times. Commits and 9 | merges against this branch MUST contain only bugfixes and/or security fixes. 10 | Maintenance releases are tagged against master. 11 | 12 | A develop branch. This branch contains your proposed changes. 13 | 14 | The remainder of this document details how to merge pull requests to the 15 | repositories. 16 | 17 | ## Merge approval 18 | 19 | The project maintainers use LGTM (Looks Good To Me) in comments on the code 20 | review to indicate acceptance. A change requires LGTMs from two of the members 21 | of the [eti-journey-admins](https://github.com/orgs/IBM/teams/eti-journey-admins/members) 22 | team. If the code is written by a member, the change only requires one more 23 | LGTM. 24 | 25 | ## Reviewing Pull Requests 26 | 27 | We recommend reviewing pull requests directly within GitHub. This allows a 28 | public commentary on changes, providing transparency for all users. When 29 | providing feedback be civil, courteous, and kind. Disagreement is fine, so 30 | long as the discourse is carried out politely. If we see a record of uncivil 31 | or abusive comments, we will revoke your commit privileges and invite you to 32 | leave the project. 33 | 34 | During your review, consider the following points: 35 | 36 | ### Does the change have impact? 37 | 38 | While fixing typos is nice as it adds to the overall quality of the project, 39 | merging a typo fix at a time can be a waste of effort. 40 | (Merging many typo fixes because somebody reviewed the entire component, 41 | however, is useful!) Other examples to be wary of: 42 | 43 | Changes in variable names. Ask whether or not the change will make 44 | understanding the code easier, or if it could simply a personal preference 45 | on the part of the author. 46 | 47 | Essentially: feel free to close issues that do not have impact. 48 | 49 | ### Do the changes make sense? 50 | 51 | If you do not understand what the changes are or what they accomplish, 52 | ask the author for clarification. Ask the author to add comments and/or 53 | clarify test case names to make the intentions clear. 54 | 55 | At times, such clarification will reveal that the author may not be using 56 | the code correctly, or is unaware of features that accommodate their needs. 57 | If you feel this is the case, work up a code sample that would address the 58 | issue for them, and feel free to close the issue once they confirm. 59 | 60 | ### Is this a new feature? If so: 61 | 62 | Does the issue contain narrative indicating the need for the feature? If not, 63 | ask them to provide that information. Since the issue will be linked in the 64 | changelog, this will often be a user's first introduction to it. 65 | 66 | Are new unit tests in place that test all new behaviors introduced? If not, do 67 | not merge the feature until they are! 68 | Is documentation in place for the new feature? (See the documentation 69 | guidelines). If not do not merge the feature until it is! 70 | Is the feature necessary for general use cases? Try and keep the scope of any 71 | given component narrow. If a proposed feature does not fit that scope, 72 | recommend to the user that they maintain the feature on their own, and close 73 | the request. You may also recommend that they see if the feature gains traction 74 | amongst other users, and suggest they re-submit when they can show such support. 75 | -------------------------------------------------------------------------------- /README-ja.md: -------------------------------------------------------------------------------- 1 | [See this lab in English](./README.md) 2 | 3 | 4 | # FabCar Blockchain Sample 5 | 6 | >Hyperledger Fabric sample Fabcar on IBM Blockchain Platform 7 | 8 | このコードパターンは、IBM Blockchain Platformでネットワークを設定し、Fabcarスマートコントラクトをネットワークに展開する方法を示しています。 次に、IDを含むネットワークとやり取りしてスマートコントラクトでトランザクションを送信するようにアプリケーションをセットアップします。 アプリケーションは、Fabric Node SDKを使用してネットワークへのリクエストを処理するNode.jsサーバーと、Webインターフェースを起動するAngularクライアントでセットアップされます。 9 | 10 | このコードパターンを完了すると、次のことができるようになります: 11 | 12 | * IBM Blockchain PlatformでHyperledger Fabricネットワークをセットアップする 13 | * IBM Blockchain Platformを介してスマートコントラクトをインストールおよびインスタンス化する 14 | * Hyperledger Fabric SDKを使用してNode.jsサーバーを開発し、展開されたネットワークと対話する 15 | * WebアプリのAngularフロントエンドを作成して、ネットワークとインターフェイスする 16 | 17 | ## アプリケーション構成図 18 | 19 |

20 | 21 |

22 | 23 | 1. Blockchain Operatorは、IBM Blockchain Platformサービスをセットアップします 24 | 2. IBM Blockchain Platformを使用すると、IBM Kubernetes Service上にHyperledger Fabricネットワークを作成でき、ネットワーク上にFabcarスマートコントラクトをインストールしてインスタンス化できます。 25 | 3. Node.jsアプリケーションサーバーは、Fabric sdkを使用して、IBM Blockchain Platformにデプロイされたネットワークとやり取りし、Webクライアント用のAPIを作成します 26 | 4. AngularクライアントはNode.jsアプリケーションAPIを使用してネットワークと対話します 27 | 5.ユーザーは、Fabcar Angular Webインターフェースと対話して、ブロックチェーン台帳と状態を更新および照会します 28 | 29 | 30 | ## 必要なコンポーネント 31 | 32 | * [IBM Blockchain Platform](https://www.ibm.com/cloud/blockchain-platform) は、デプロイ手順を簡素化および加速できるユーザーインターフェイスを使用して、ブロックチェーンネットワークを完全に制御できます。また、IBM Cloud Kubernetes Serviceでブロックチェーンコンポーネントを管理します。 33 | * [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service) は、コンピュートホストのクラスターを作成し、高可用性コンテナーを展開します。 Kubernetesクラスターを使用すると、アプリケーションを迅速にデプロイ、更新、スケーリングするために必要なリソースを安全に管理できます。 34 | * [IBM Blockchain Platform Extension for VS Code](https://marketplace.visualstudio.com/items?itemName=IBMBlockchain.ibm-blockchain-platform) は、Hyperledger Fabric環境への接続を含む、スマートコントラクトの開発、テスト、および展開においてユーザーを支援するように設計されています。 35 | 36 | 37 | ## 利用技術 38 | 39 | + [Hyperledger Fabric v1.4](https://hyperledger-fabric.readthedocs.io/en/release-1.4/) は、高度な機密性、弾力性、柔軟性、およびスケーラビリティを提供するモジュール式アーキテクチャに支えられた分散型台帳ソリューションのプラットフォームです。 40 | + [Node.js](https://nodejs.org/en/) は、サーバー側のJavaScriptコードを実行するオープンソースのクロスプラットフォームJavaScriptランタイム環境です。 41 | + [Express.js](https://expressjs.com/) は、Webおよびモバイルアプリケーションに堅牢な機能セットを提供する、最小限で柔軟なNode.js Webアプリケーションフレームワークです。 42 | + [Angular.io](https://angular.io/) これは、Webアプリケーションを構築するためのフロントエンドフレームワークです。 43 | 44 | ## 事前準備 45 | 46 | - [IBM Cloud account](https://cloud.ibm.com/registration/?target=%2Fdashboard%2Fapps) 47 | - [Node v8.x or v10.x and npm v6.x or greater](https://nodejs.org/en/download/) 48 | - [VSCode version 1.38.0 or greater](https://code.visualstudio.com) 49 | - [IBM Blockchain Platform Extension for VSCode](https://marketplace.visualstudio.com/items?itemName=IBMBlockchain.ibm-blockchain-platform) 50 | 51 | 52 | ## アプリケーションの実行 53 | 54 | 次の手順に従って、このコードパターンをセットアップして実行します。 手順の詳細は以下のとおりです。 55 | 56 | ## ステップ 57 | 58 | > ローカルネットワークで実行する場合は [こちら](./run-local.md) を参照して下さい。 59 | 60 | 1. [リポジトリのクローン](#1-リポジトリのクローン) 61 | 2. [スマートコントラクトのパッケージ](#2-スマートコントラクトのパッケージ) 62 | 3. [IBM Cloud サービスの作成](#3-IBM-Cloud-サービスの作成) 63 | 4. [ネットワークのビルド](#4-ネットワークのビルド) 64 | 5. [FabCarスマートコントラクトのネットワークへのデプロイ](#5-FabCarスマートコントラクトのネットワークへのデプロイ) 65 | 6. [アプリケーションのネットワークへの接続](#6-アプリケーションのネットワークへの接続) 66 | 7. [アプリケーションの実行](#7-アプリケーションの実行) 67 | 68 | 69 | ### 1. リポジトリのクローン 70 | 71 | こちらのリポジトリを任意のローカロフォルダへクローンします: 72 | 73 | ```bash 74 | git clone https://github.com/IBM/fabcar-blockchain-sample.git 75 | cd fabcar-blockchain-sample 76 | ``` 77 | 78 | ### 2. スマートコントラクトのパッケージ 79 | 80 | IBM Blockchain Platform拡張機能を使用して、Fabcarスマートコントラクトをパッケージ化します。 81 | 82 | * Visual Studioコードを開き、事前にCloneした `fabcar-blockchain-sample` リポジトリから `contract` フォルダーを開きます。 83 | ** `fabcar-blockchain-sample` ディレクトリ全体ではなく、 `contract` フォルダーを開くことが重要です。そうしないと、使用しているプログラミング言語が理解できないというエラーが表示されます。** 84 | 85 | * いろいろなVS Code オプションを表示するには、 `F1`キーを押します。 `IBM Blockchain Platform:Package Open Project` を選択します。 86 | 87 |

88 | 89 |

90 | 91 | * 左側の `IBM Blockchain Platform` 拡張ボタンをクリックします。 これにより、パッケージ化されたコントラクトが上部に、ブロックチェーン接続が下部に表示されます。 92 | 93 |

94 | 95 |

96 | 97 | * 次に、スマートコントラクトをエクスポートするために、パッケージ化されたコントラクトを右クリックして(この場合はfabcar@1.0.0を選択)、 `Export Package` を選択します。 98 | 99 | * ローカルマシン上の場所を選択し、 `.cds` ファイルを保存します。 このパッケージ化されたスマートコントラクトを後で使用して、IBM Blockchain Platformサービスにデプロイします。 100 | 101 | 次に、IBM Cloud上でHyperledger Fabricネットワークを構成し、このネットワークを使用してアプリケーションを実行するために、必要ないろいろなサービスのセットアップを開始します。 102 | 103 | 104 | ### 3. IBM Cloud サービスの作成 105 | 106 | * [IBM Cloud Kubernetes Service](https://cloud.ibm.com/kubernetes/catalog/cluster) を作成します。 サービスは `カタログ` で見つけることができます。 このコードパターンでは、 `Free(無料)` クラスターを使用して名前を付けることができます。IBM Cloudでは、30日後に有効期限が切れる無料クラスターのインスタンスを1つ許可しています。 **注:IBM Cloud Kubernetes Serviceのセットアップが完了するまで20分かかる場合があります**。 107 | 108 |
109 |

110 | 111 |

112 |
113 | 114 | * IBM Cloud上に [IBM Blockchain Platform](https://cloud.ibm.com/catalog/services/blockchain-platform) サービスを作成します。このサービスは `カタログ` から見つけることができ、任意の名前を付与できます。 115 | 116 |
117 |

118 | 119 |

120 |
121 | 122 | * Kubernetesクラスターが稼働したら、クラスターにIBM Blockchain Platformをデプロイできます。IBM Blockchain Platformサービスを作成するステップの中で、サービスをデプロイするIBM Cloud上のクラスターを選択します。 123 | 124 |
125 |

126 | 127 |

128 |
129 | 130 | * ブロックチェーンプラットフォームがKubernetesクラスターにデプロイされると、コンソールを起動してブロックチェーンネットワークでの運用を開始できます。 131 | 132 | 133 | ### 4. ネットワークのビルド 134 | 135 | IBM Blockchain Platformが提供するネットワークを構築します [ドキュメント](https://console.bluemix.net/docs/services/blockchain/howto/ibp-console-build-network.html#ibp-console-build-network) 。 これには、独自のMSPとCA(認証局)を持つ単一のピア組織と、独自のMSPとCAを持つ注文者組織とのチャネルの作成が含まれます。 ピアを展開してノードを操作するために、それぞれのIDを作成します。 136 | 137 | 138 | #### 組織 CA のピアを作成します 139 | - 認証局の追加をクリックします。 140 | - IBM Cloud 認証局の作成を選択し次へをクリックします。 141 | - CA 表示名に `Org1 CA` を記述します。 142 | - CA 管理者登録 IDに `admin` を、CA 管理者登録秘密事項に `adminpw` を記述します。 143 | 144 |
145 |

146 | 147 |

148 |
149 | 150 | 151 | #### 組織ピアのCA管理IDを関連付ける 152 | - 作成したOrg1 CA認証局が実行中であることを確認し、選択します。(タイルの右上のボックスが緑色になってることを確認) 153 | - 最初に、アイデンティティの関連付けを行います。アイデンティティの関連付けボタンをクリックします。 154 | - サイドパネルで、登録ID を選択します。 155 | - 登録IDに `admin` を、登録秘密事項に `adminpw` を指定します。ID表示名には `Org1 CA Identity` を使用します。 156 | - アイデンティティの関連付けをクリックします。ウォレットにアイデンティティを追加し、管理者のアイデンティティを Org1 CA に関連付けます。 157 | 158 |
159 |

160 | 161 |

162 |
163 | 164 | #### ピア組織CAを使用して、ピアおよびorg1管理者IDを登録します 165 | - Org1 CA 認証局を選択し、CA用に作成された `admin` identityがテーブルに表示されていることを確認します。 166 | - 組織`org1`の管理者を登録します。ユーザーの登録ボタンをクリックします。登録IDに `org1admin` を、登録秘密事項に `org1adminpw` を指定し、次へをクリックします。このIDのTypeを `admin` に設定します。 ルートアフィリエーションを使用を指定するか、このフィールドのチェックを外して、ドロップダウンリストから関連組織のいずれかを選択できます。最大登録数フィールドは空白のままにします。 次へ]をクリックします。 167 | - このユーザーには属性を追加しません。 ユーザーを登録をクリックします。 168 | - 再度ピアのIDを作成するプロセスを行います。ユーザーの登録ボタンをクリックします。登録IDに `peer1` を、秘密の登録登録秘密事項に ` peer1pw` を指定し、次へをクリックします。このIDのTypeを `peer` に設定します。 ルートアフィリエーションを使用を指定するか、このフィールドのチェックを外して、ドロップダウンリストから関連組織のいずれかを選択できます。 次へをクリックします。 169 | - このユーザーには属性を追加しません。 ユーザーを登録をクリックします。 170 | 171 |
172 |

173 | 174 |

175 |
176 | 177 | 178 | #### 組織ピアのMSP定義を作成します 179 | - 左側のナビゲーションの組織タブに移動し、MSP定義の作成をクリックします。 180 | - MSP表示名に`Org1 MSP`と入力し、MSP IDに`Org1msp`を入力します。 181 | - ルート認証局の詳細の下で、組織のルートCAとして`Org1 CA`を作成したピアCAを指定します。 182 | - 組織管理者のの登録IDに、`org1admin`を、登録秘密事項に、`org1adminpw`を指定します。 次に、ID名`Org1 Admin`を指定します。 183 | - 生成ボタンをクリックして、このIDを組織の管理者として登録し、IDをウォレットにエクスポートします。 エクスポートをクリックして、管理者証明書をファイルシステムにエクスポートします。 最後に、MSP定義の作成をクリックします。 184 | 185 |
186 |

187 | 188 |

189 |
190 | 191 | 192 | #### ピアを作成します 193 | - ノードページで、ピアの追加をクリックします。 194 | - 新しいピアの作成の下のIBM Cloudおよびをクリックします。 195 | - ピアに`Peer Org1`の表示名を付けます。 196 | - 次の画面で、認証機関として`Org1 CA`を選択します。次に、ピア用に作成したピアIDの登録ID および登録秘密事項を `peer1`、`peer1pw` で指定します。次に、ドロップダウンリストから管理者証明書(MSPから)、`Org1 MSP`を選択し、をクリックします。 197 | - TLS登録IDに、`admin`、およびTLS登録秘密事項に、`adminpw`を指定します。作成時に指定した登録IDと登録秘密事項と同じです CA。 TLS CSRホスト名は空白のままにします。 198 | - 最後のサイドパネルでは、IDを関連付けるように求められ、ピアの管理者になります。ピア管理ID`Org1 Admin`を選択します。 199 | - 概要を確認し、ピアの追加をクリックします。 200 | 201 |
202 |

203 | 204 |

205 |
206 | 207 | 208 | ### トランザクションの順序付け(Orderer)用ノードを作成します 209 | - 認証局の追加をクリックします。 210 | - IBM Cloud認証局の作成を選択し次へをクリックします。 211 | - CA 表示名 に `Orderer CA` を入力します。 212 | - CA 管理者登録 IDに `admin` を、CA 管理者登録機密事項に `adminpw` を設定します。 213 | 214 |
215 |

216 | 217 |

218 |
219 | 220 | 221 | #### 発注者組織のCA管理者IDを関連付ける 222 | - ノードタブで、作成した認証局Orderer CAが実行中なのを確認し選択します。(タイルの右上のボックスが緑色になってることを確認) 223 | - 最初に、アイデンティティの関連付けを行います。アイデンティティの関連付けボタンをクリックします。 224 | - サイドパネルで、登録ID を選択します。 225 | - 登録IDに `admin` を、登録秘密事項に `adminpw` を指定します。ID表示名には `Orderer CA Identity` を使用します。 226 | - アイデンティティの関連付けをクリックします。ウォレットにアイデンティティを追加し、管理者のアイデンティティをOrderer CAに関連付けます。 227 | 228 |
229 |

230 | 231 |

232 |
233 | 234 | #### 発注者組織CAを使用して、OrdererとOrdererの管理者IDを登録します 235 | - Orderer CA認証局を選択し、CA用に作成された `admin` identityがテーブルに表示されていることを確認します。 236 | - 組織`org1`の管理者を登録します。ユーザーの登録ボタンをクリックします。登録IDに `ordereradmin` を、登録秘密事項に `ordereradminpw` を指定し、次へをクリックします。このIDのTypeを `admin` に設定します。 ルートアフィリエーションを使用を指定するか、このフィールドのチェックを外して、ドロップダウンリストから関連組織のいずれかを選択できます。最大登録数フィールドは空白のままにします。 次へ]をクリックします。 237 | - このユーザーには属性を追加しません。 ユーザーを登録をクリックします。 238 | - 再度ピアのIDを作成するプロセスを行います。ユーザーの登録ボタンをクリックします。登録IDに `orderer1` を、秘密の登録登録秘密事項に `orderer1pw` を指定し、次へをクリックします。このIDのTypeを `orderer` に設定します。 ルートアフィリエーションを使用を指定するか、このフィールドのチェックを外して、ドロップダウンリストから関連組織のいずれかを選択できます。 次へをクリックします。 239 | - このユーザーには属性を追加しません。 ユーザーを登録をクリックします。 240 | 241 |
242 |

243 | 244 |

245 |
246 | 247 | 248 | #### 順序付けサービス組織のMSP定義を作成します 249 | - 左側のナビゲーションの組織タブに移動し、MSP定義の作成をクリックします。 250 | - MSP表示名に`Orderer MSP`、 MSP ID に`orderermsp`を入力します。 251 | - ルート認証局の詳細の下で、組織のルートCAとして、作成したピアCA `Orderer CA` を指定します。 252 | - 組織管理者に、`ordereradmin`を、登録ID に、`ordereradminpw`を指定します。 次に、ID名、`Orderer Admin`を指定します。 253 | - 生成ボタンをクリックして、このIDを組織の管理者として登録し、IDをウォレットにエクスポートします。エクスポートをクリックして、管理者証明書をファイルシステムにエクスポートします。最後に、MSP定義の作成をクリックします。 254 | 255 |
256 |

257 | 258 |

259 |
260 | 261 | #### 順序付けサービスを作成します 262 | - ノードページで、順序付けサービスの追加をクリックします。 263 | - IBM Cloud順序付けサービスの作成をクリックして、次へに進みます。 264 | - 順序付けサービスの表示名に `Orderer` を入力します。 265 | - 次の画面で、認証機関として`Orderer CA`を選択します。次に、注文者用に作成したピアIDの登録ID登録秘密事項を `orderer1`、`orderer1pw` で指定します。次に、組織MSPドロップダウンリストからAdministrator Certificate(from MSP)である`Orderer MSP`を選択します。TLS CSRホスト名は空白のままにし、次へをクリックします。 266 | 267 | - 次のステップは、このピアにIDを関連付けて、ピアの管理者にすることです。 ピア管理ID `Orderer Admin` を選択し、次へをクリックします。 268 | - 概要を確認し、順序付けサービスの追加をクリックします。 269 | 270 |
271 |

272 | 273 |

274 |
275 | 276 | 277 | #### トランザクションのために、順序付けサービスにコンソーシアムメンバー(共同事業体メンバー)として組織を追加します 278 | 注文者に組織をコンソーシアムメンバーとして追加して取引します 279 | - ノードタブに移動し、作成したOrdererをクリックします。 280 | - 共同事業体メンバーで、組織の追加をクリックします。 281 | - ドロップダウンリストから `Org1 MSP` を選択します。これは、ピアの組織Org1を表すMSPであるためです。 282 | - 組織の追加をクリックします。 283 | 284 |
285 |

286 | 287 |

288 |
289 | 290 | 291 | #### チャネルを作成します 292 | - 左側のナビゲーションタブからチャネルを選択します。 293 | - チャネルの作成をクリックします。 294 | - チャネル名に `mychannel` を設定します。 295 | - 作成した順序付けサービス `Orderer` を順序付けサービスリストから選択します。 296 | - 組織セクションで、組織の下のチャネルメンバー `Org1 MSP (Org1MSP)` を選択します。 297 | - ドロップダウンリストからチャネル作成者の組織を識別するMSPを選択します。 これは `Org1 MSP (Org1MSP)` である必要があります。 298 | - 組織の横にある追加をクリックします。 組織をOperatorにします。 299 | - チャネル作成者の組織セクションで、チャネル作成者の MSPに`Org1 MSP (Org1MSP)` を、アイデンティティに `Org1 Admin` を選択します。 300 | - チャネルの作成をクリックします。 301 | 302 |
303 |

304 | 305 |

306 |
307 | 308 | 309 | #### ピアをチャネルへ参加させます 310 | - 作成したmychannelのタイルをクリックして、サイドパネルを起動します。 311 | - `Peer Org1(Org1MSP)` を選択して、 `チャネルへの結合` をクリックします。 312 | 313 |
314 |

315 | 316 |

317 |
318 | 319 | 320 | ## 5. FabCarスマートコントラクトのネットワークへのデプロイ 321 | 322 | #### スマートコントラクトをインストールします 323 | - 左側のナビゲーションのスマートコントラクトタブに移動し、スマートコントラクトのインストールをクリックします。 324 | - Visual Studioコード用のIBM Blockchain Platform Extensionを使用し、事前にパッケージ化したFabcarスマートコントラクトパッケージファイル(おそらく `fabcar@1.0.0.cds` という名前)のBlockchainの場所を参照します。 325 | - ファイルの追加をクリックして、パッケージ化されたスマートコントラクトを見つけます。 326 | - スマートコントラクトがアップロードされたら、インストールをクリックします。 327 | 328 |
329 |

330 | 331 |

332 |
333 | 334 | #### スマートコントラクトをインスタンス化します 335 | - スマートコントラクトタブで、ピアにインストールされているリストからスマートコントラクトを見つけ、行の右側のオーバーフローメニューからインスタンス化をクリックします。 336 | - 開いたサイドパネルで、スマートコントラクトをインスタンス化するチャネル `mychannel` を選択します。次へをクリックします。 337 | - ポリシーに、組織メンバー `Org1MSP` を選択します。 次へを2回クリックします。 338 | - 機能名に `initLedger` を指定し、引数を空白のままにします。 339 | - スマートコントラクトのインスタンス化をクリックします。 340 | 341 |
342 |

343 | 344 |

345 |
346 | 347 | 348 | ## 6. アプリケーションのネットワークへの接続 349 | 350 | #### 接続プロファイルを介してSDKに接続します 351 | - インスタンス化されたスマート・コントラクトの下で、リストで「fabcar」コントラクトを見つけます。 行の右側のオーバーフローメニューから `SDKを使用した接続` をクリックします。 352 | - 接続用のMSPのドロップダウンから`Org1MSP`を選択します。 353 | - 認証局ドロップダウンから `Org1 CA` を選択します。 354 | - 下にスクロールして接続プロファイルのダウンロードをクリックして、接続プロファイルをダウンロードします。これにより、Node.js WebアプリケーションとBlockchain Network間の接続を確立するために使用する接続JSONがダウンロードされます。 355 | - ダウンロードが完了したら、閉じるをクリックします。 356 | 357 |
358 |

359 | 360 |

361 |
362 | 363 | 364 | #### アプリ管理者を作成します 365 | - 左側のバーのノードタブに移動し、認証局で組織のCAであるOrg1 CAを選択します。 366 | - ユーザーの登録をクリックします。 367 | - 登録ID登録機密事項に `app-admin` と `app-adminpw` を指定します。このIDのTypeを `client` として設定します。 ルートアフィリエーションを使用を指定するか、このフィールドのチェックを外して、ドロップダウンリストから関連組織のいずれかを選択できます。最大登録数フィールドは空白のままにします。次へをクリックします。 368 | - 属性で、属性の追加をクリックします。属性を `hf.Registrar.Roles` = `*` として指定します。 これにより、このIDがレジストラとして機能し、アプリのIDが発行されます。属性の追加をクリックします。 369 | - 登録をクリックします。 370 | 371 |
372 |

373 | 374 |

375 |
376 | 377 | 378 | #### アプリケーション接続を更新します 379 | - ダウンロードした接続プロファイルを[サーバーフォルダー](web-app/server)にコピーします 380 | - [config.json](web-app/server/config.json)ファイルを次のように更新します。 381 | - ダウンロードした接続jsonファイル名。 382 | - アプリ管理者のenroll idおよびenroll secret。事前に`app-admin`および`app-adminpw`として作成しました。 383 | - `Org1MSP`として提供したorgMSP ID。 384 | - `organizations`->`Org1MSP`-> certificateAuthoritiesの下の接続jsonファイルにあるcaName。これはIPアドレスとポートのようなものです。 385 | - 登録するユーザー名。 386 | - IBM Blockchain Platformに接続するために、ゲートウェイ検出を `{enabled:true、asLocalhost:false}`に更新。 387 | 388 | > デフォルト設定では、VS Codeからローカルファブリックインスタンスに接続するようになっています。 389 | 390 | ```js 391 | { 392 | "connection_file": "mychannel_fabcar_profile.json", 393 | "appAdmin": "app-admin", 394 | "appAdminSecret": "app-adminpw", 395 | "orgMSPID": "Org1MSP", 396 | "caName": "169.46.208.151:30404", 397 | "userName": "user1", 398 | "gatewayDiscovery": { "enabled": true, "asLocalhost": false } 399 | } 400 | ``` 401 | 402 | 403 | ## 7. アプリケーションの実行 404 | 405 | * #### 管理者を登録する 406 | - 最初に `web-app` ディレクトリに移動し、ノードの依存関係をインストールします。 407 | ```bash 408 | cd web-app/server 409 | npm install 410 | ``` 411 | 412 | - `enrollAdmin.js` を実行します。 413 | ```bash 414 | node enrollAdmin.js 415 | ``` 416 | 417 | - ターミナルに次のように表示されれば成功です。 418 | ```bash 419 | msg: Successfully enrolled admin user app-admin and imported it into the wallet 420 | ``` 421 | 422 | * #### ユーザーを登録する 423 | - `registerUser.js` を実行します。 424 | ```bash 425 | node registerUser.js 426 | ``` 427 | 428 | - ターミナルに次のように表示されれば成功です。 429 | ```bash 430 | Successfully registered and enrolled admin user user1 and imported it into the wallet 431 | ``` 432 | 433 | * #### アプリケーションサーバーの起動 434 | - `server` ディレクトリからサーバーを開始します。 435 | ```bash 436 | npm start 437 | ``` 438 | 439 | * #### Webクライアントの開始 440 | - 新しいターミナルで、Webクライアントフォルダーを開き、依存関係をインストールします。 441 | ```bash 442 | cd web-app/client 443 | npm install 444 | ``` 445 | 446 | - クライアントアプリを開始します。 447 | ```bash 448 | npm start 449 | ``` 450 | 451 | http://localhost:4200/ にアクセスし、実行されているアプリを確認することができます。 452 | 453 |
454 |

455 | 456 |

457 |
458 | 459 | IBM Blockchain Platformコンソールにアクセスしてユーザーを監視し、追加されたブロックを含むチャンネルに関する情報を取得できます。 460 | 461 |
462 |

463 | 464 |

465 |
466 | 467 | 468 | ## トラブルシューティング 469 | 470 | * If you encounter an error ``discover error: access denied``, you need to set the `gatewayDiscovery` properly in your `config.json` file. This is REQUIRED You must set it as follows to connect to IBP: 471 | 472 | `"gatewayDiscovery": {"enabled": true, "asLocalhost": false }` 473 | 474 | 475 | ## リンク 476 | * [Hyperledger Fabric Docs](http://hyperledger-fabric.readthedocs.io/en/latest/) 477 | * [IBM Code Patterns for Blockchain](https://developer.ibm.com/patterns/category/blockchain/) 478 | 479 | 480 | ## ライセンス(英語) 481 | 482 | This code pattern is licensed under the Apache Software License, Version 2. Separate third-party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](https://www.apache.org/licenses/LICENSE-2.0.txt). 483 | 484 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 485 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [日本語はこちら - Japanese version](./README-ja.md) 2 | 3 | 4 | [![Build Status](https://travis-ci.org/IBM/fabcar-blockchain-sample.svg?branch=master)](https://travis-ci.org/IBM/fabcar-blockchain-sample) 5 | 6 | 7 | # FabCar Blockchain Sample 8 | 9 | >Hyperledger Fabric sample Fabcar on IBM Blockchain Platform 10 | 11 | > **NOTE**: This developer pattern creates a blockchain network on *IBM Blockchain Platform version **2.5*** using the *Hyperledger Fabric version **1.4***. 12 | 13 | This code pattern demonstrates setting up a network on the IBM Blockchain Platform and deploying the Fabcar smart contract on the network. Next, we setup our application to interact with the network including identities to submit transactions on the smart contract. The application is setup with a Node.js server using the Fabric Node SDK to process requests to the network, and an Angular client to bring up a web interface. 14 | 15 | When the reader has completed this code pattern, they will understand how to: 16 | 17 | * Setup a Hyperledger Fabric network on IBM Blockchain Platform 18 | * Install and instantiate smart contract through the IBM Blockchain Platform 19 | * Develop a Node.js server with the Hyperledger Fabric SDK to interact with the deployed network 20 | * Create an Angular frontend for the web app to interface with the network 21 | 22 | 23 | ## Architecture flow 24 | 25 |

26 | 27 |

28 | 29 | 1. The Blockchain Operator sets up the IBM Blockchain Platform service. 30 | 2. The IBM Blockchain Platform enables to create a Hyperledger Fabric network onto a IBM Cloud Kubernetes Service, allowing to install and instantiate the Fabcar smart contract on the network. 31 | 3. The Node.js application server uses the Fabric SDK to interact with the deployed network on IBM Blockchain Platform and creates APIs for a web client. 32 | 4. The Angular client uses the Node.js application API to interact with the network. 33 | 5. The User interacts with the Fabcar Angular web interface to update and query the blockchain ledger and state. 34 | 35 | 36 | ## Included components 37 | 38 | * [IBM Blockchain Platform](https://www.ibm.com/cloud/blockchain-platform) gives you total control of your blockchain network with a user interface that can simplify and accelerate your journey to deploy and manage blockchain components on the IBM Cloud Kubernetes Service. 39 | * [IBM Cloud Kubernetes Service](https://www.ibm.com/cloud/container-service) creates a cluster of compute hosts and deploys highly available containers. A Kubernetes cluster lets you securely manage the resources that you need to quickly deploy, update, and scale applications. 40 | * [IBM Blockchain Platform Extension for VS Code](https://marketplace.visualstudio.com/items?itemName=IBMBlockchain.ibm-blockchain-platform) is designed to assist users in developing, testing, and deploying smart contracts -- including connecting to Hyperledger Fabric environments. 41 | 42 | 43 | ## Featured technologies 44 | 45 | + [Hyperledger Fabric v1.4](https://hyperledger-fabric.readthedocs.io/en/release-1.4/) is a platform for distributed ledger solutions, underpinned by a modular architecture that delivers high degrees of confidentiality, resiliency, flexibility, and scalability. 46 | + [Node.js](https://nodejs.org/en/) is an open source, cross-platform JavaScript run-time environment that executes server-side JavaScript code. 47 | + [Express.js](https://expressjs.com/) is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. 48 | + [Angular.io](https://angular.io/) is a front-end framework for building web applications. 49 | 50 | 51 | ## Prerequisites 52 | 53 | - [IBM Cloud account](https://cloud.ibm.com/registration/?target=%2Fdashboard%2Fapps) 54 | - [Node v10.x and npm v6.x or greater](https://nodejs.org/en/download/) 55 | - [VSCode version 1.38.0 or greater](https://code.visualstudio.com) 56 | - [IBM Blockchain Platform Extension for VSCode](https://marketplace.visualstudio.com/items?itemName=IBMBlockchain.ibm-blockchain-platform) 57 | 58 | 59 | ## Running the application 60 | 61 | Follow these steps to set up and run this code pattern. The steps are described in detail below. 62 | 63 | 64 | ## Steps 65 | 66 | > To run a local network, you can find steps [here](./run-local.md) 67 | 68 | 1. [Clone the repo](#1-clone-the-repo) 69 | 2. [Package the smart contract](#2-package-the-smart-contract) 70 | 3. [Create IBM Cloud services](#3-create-ibm-cloud-services) 71 | 4. [Build a network](#4-build-a-network) 72 | 5. [Deploy FabCar Smart Contract on the network](#5-deploy-fabcar-smart-contract-on-the-network) 73 | 6. [Connect application to the network](#6-connect-application-to-the-network) 74 | 7. [Run the application](#7-run-the-application) 75 | 76 | 77 | ### 1. Clone the repo 78 | 79 | Clone this repository in a folder your choice: 80 | 81 | ```bash 82 | git clone https://github.com/IBM/fabcar-blockchain-sample.git 83 | cd fabcar-blockchain-sample 84 | ``` 85 | 86 | ### 2. Package the smart contract 87 | 88 | We will use the IBM Blockchain Platform extension on VS Code to package the Fabcar smart contract. 89 | 90 | * Open Visual Studio code and open the `contract` folder from `fabcar-blockchain-sample` repository that was cloned earlier. 91 | **It is important that you are opening the `contract` folder and not the entire `fabcar-blockchain-sample` directory; otherwise you will see an error that states that it doesn't understand what programming language you are using.** 92 | 93 | * Press the `F1` key to see the different VS code options. Choose `IBM Blockchain Platform: Package Open Project`. 94 | 95 |

96 | 97 |

98 | 99 | * Click the `IBM Blockchain Platform` extension button on the left. This will show the packaged contracts on top and the blockchain connections on the bottom. 100 | 101 |

102 | 103 |

104 | 105 | * Next, right click on the packaged contract (in this case, select fabcar@1.0.0) to export it and choose `Export Package`. 106 | 107 | * Choose a location on your machine and save the `.cds` file. We will use this packaged smart contract later to deploy on the IBM Blockchain Platform service. 108 | 109 | Now, we will start setting up the different services required for configuring our Hyperledger Fabric network on the IBM Cloud and for running our application using this network. 110 | 111 | 112 | ### 3. Create IBM Cloud services 113 | 114 | * Create the [IBM Cloud Kubernetes Service](https://cloud.ibm.com/kubernetes/catalog/cluster). You can find the service in the `Catalog`. For this code pattern, we can use the `Free` cluster, and give it a name. Note, that the IBM Cloud allows one instance of a free cluster which expires after 30 days. **Note: it could take 20 minutes for the IBM Cloud Kubernetes Service setup to complete**. 115 | 116 |
117 |

118 | 119 |

120 |
121 | 122 | * Create the [IBM Blockchain Platform](https://cloud.ibm.com/catalog/services/blockchain-platform) service on the IBM Cloud. You can find the service in the `Catalog`, and give it a name. 123 | 124 |
125 |

126 | 127 |

128 |
129 | 130 | * After your kubernetes cluster is up and running, you can deploy your IBM Blockchain Platform on the cluster. Again - wait for the IBM Cloud Kubernetes service to indicate it was deployed. The IBM Blockchain Platform service walks through few steps and finds your cluster on the IBM Cloud to deploy the service on. 131 | 132 |
133 |

134 | 135 |

136 |
137 | 138 | * Once the Blockchain Platform is deployed on the Kubernetes cluster, you can launch the console to start configuring your blockchain network. 139 | 140 | 141 | ### 4. Build a network 142 | 143 | We will build a network as provided by the IBM Blockchain Platform [documentation](https://cloud.ibm.com/docs/services/blockchain/howto?topic=blockchain-ibp-console-build-network#ibp-console-build-network). This will include creating a channel with a single peer organization with its own MSP and CA (Certificate Authority), and an orderer organization with its own MSP and CA. We will create the respective identities to deploy peers and operate nodes. 144 | 145 | 146 | #### Create your peer organization CA 147 | - Navigate to the Nodes tab in the left navigation and click Add Certificate Authority +. 148 | - Click Create a Certificate Authority + and click Next. 149 | - Give it a CA display name of `Org1 CA`, a CA administrator enroll ID of `admin` and a CA administrator enroll secret of `adminpw`, then click Next. 150 | - Review the summary and click Add Certificate Authority. 151 | 152 |
153 |

154 | 155 |

156 |
157 | 158 | 159 | #### Associate the peer organization CA admin identity 160 | - In the Nodes tab, select the Org1 CA once it is running (indicated by the green box in the tile). 161 | - Click Associate identity on the CA overview panel. 162 | - On the side panel, select the Enroll ID tab. 163 | - Provide an Enroll ID of `admin` and an Enroll secret of `adminpw`. Use the default value of `Org1 CA Admin` for the Identity display name. 164 | - Click Associate identity to associate the `admin` identity with the Org1 CA. 165 | 166 |
167 |

168 | 169 |

170 |
171 | 172 | 173 | #### Use peer organization CA to register the peer and org1 admin identities 174 | - Select the Org1 CA Certificate Authority and ensure the `admin` identity that was created for the CA is visible in the table. 175 | - The next step is to register an admin for the organization "Org1". Click on the Register User + button. Give an Enroll ID of `org1admin` and an Enroll secret of `org1adminpw`. Set the Type for this identity as `admin`. Specify to Use root affiliation. Leave the Maximum enrollments field blank. Click Next. 176 | - Skip the section to add attributes to this user and click Register user. 177 | - Repeat the process to create an identity of the peer. Click on the Register User + button. Give an Enroll ID of `peer1` and an Enroll secret of `peer1pw`. Set the Type for this identity as `peer`. Specify to Use root affiliation. Leave the Maximum enrollments field blank. Click Next. 178 | - Skip the section to add attributes to this user and click Register user. 179 | 180 |
181 |

182 | 183 |

184 |
185 | 186 | 187 | #### Create the peer organization MSP definition 188 | - Navigate to the Organizations tab in the left navigation and click Create MSP definition +. 189 | - Enter the MSP display name as `Org1MSP` and the MSP ID as `Org1MSP`. Click Next. 190 | - Specify `Org1 CA` as the Root Certificate Authority. Click Next. 191 | - Select the New identity tab. Give the Enroll ID and Enroll secret for your organization admin, i.e. `org1admin` and `org1adminpw` respectively. Then, give the Identity name as `Org1 Admin`. 192 | - Click the Generate button to enroll this identity as the admin of your organization and add the identity to the wallet. Click Export to export the admin certificates to your file system. Click Next. 193 | - Review all the information and click Create MSP definition. 194 | 195 |
196 |

197 | 198 |

199 |
200 | 201 | 202 | #### Create a peer 203 | - Navigate to the Nodes tab in the left navigation and click Add peer +. 204 | - Click Create a peer + and then click Next. 205 | - Give the Peer display name as `Peer Org1` and click Next. 206 | - On the next screen, select `Org1 CA` as the Certificate Authority. Then, give the Peer enroll ID and Peer enroll secret as `peer1` and `peer1pw` respectively. Select the Organization MSP as `Org1MSP`. Leave the TLS CSR hostname blank and select the highest value available in the drop-down for Fabric version, i.e. `2.1.1-0`. Click Next. 207 | - Provide `Org1 Admin` as the Peer administrator identity and click Next. 208 | - Review the summary and click Add peer. 209 | 210 |
211 |

212 | 213 |

214 |
215 | 216 | 217 | #### Create your orderer organization CA 218 | - Navigate to the Nodes tab in the left navigation and click Add Certificate Authority +. 219 | - Click Create a Certificate Authority + and click Next. 220 | - Give it a CA display name of `Orderer CA`, a CA administrator enroll ID of `admin` and a CA administrator enroll secret of `adminpw`, then click Next. 221 | - Review the summary and click Add Certificate Authority. 222 | 223 |
224 |

225 | 226 |

227 |
228 | 229 | 230 | #### Associate the orderer organization CA admin identity 231 | - In the Nodes tab, select the Orderer CA once it is running (indicated by the green box in the tile). 232 | - Click Associate identity on the CA overview panel. 233 | - On the side panel, select the Enroll ID tab. 234 | - Provide an Enroll ID of `admin` and an Enroll secret of `adminpw`. Use the default value of `Orderer CA Admin` for the Identity display name. 235 | - Click Associate identity to associate the `admin` identity with the Orderer CA. 236 | 237 |
238 |

239 | 240 |

241 |
242 | 243 | 244 | #### Use orderer organization CA to register orderer and orderer admin identities 245 | - Select the Orderer CA Certificate Authority and ensure the `admin` identity that was created for the CA is visible in the table. 246 | - The next step is to register an admin for the organization "Orderer". Click on the Register User + button. Give an Enroll ID of `ordereradmin` and an Enroll secret of `ordereradminpw`. Set the Type for this identity as `admin`. Specify to Use root affiliation. Leave the Maximum enrollments field blank. Click Next. 247 | - Skip the section to add attributes to this user and click Register user. 248 | - Repeat the process to create an identity of the orderer. Click on the Register User + button. Give an Enroll ID of `orderer` and an Enroll secret of `ordererpw`. Set the Type for this identity as `orderer`. Specify to Use root affiliation. Leave the Maximum enrollments field blank. Click Next. 249 | - Skip the section to add attributes to this user and click Register user. 250 | 251 |
252 |

253 | 254 |

255 |
256 | 257 | 258 | #### Create the orderer organization MSP definition 259 | - Navigate to the Organizations tab in the left navigation and click Create MSP definition +. 260 | - Enter the MSP display name as `OrdererMSP` and the MSP ID as `OrdererMSP`. Click Next. 261 | - Specify `Orderer CA` as the Root Certificate Authority. Click Next. 262 | - Select the New identity tab. Give the Enroll ID and Enroll secret for your organization admin, i.e. `ordereradmin` and `ordereradminpw` respectively. Then, give the Identity name as `Orderer Admin`. 263 | - Click the Generate button to enroll this identity as the admin of your organization and add the identity to the wallet. Click Export to export the admin certificates to your file system. Click Next. 264 | - Review all the information and click Create MSP definition. 265 | 266 |
267 |

268 | 269 |

270 |
271 | 272 | 273 | 274 | #### Create an orderer 275 | - Navigate to the Nodes tab in the left navigation and click Add ordering service +. 276 | - Click Create an ordering service + and then click Next. 277 | - Give the Ordering service display name as `Orderer` and click Next. 278 | - On the next screen, select `Orderer CA` as the Certificate Authority. Then, give the Ordering service enroll ID and Ordering service enroll secret as `orderer` and `ordererpw` respectively. Select the Organization MSP as `OrdererMSP`. Leave the TLS CSR hostname blank and select the highest value available in the drop-down for Fabric version, i.e. `2.1.1-0`. Click Next. 279 | - Provide `Orderer Admin` as the Orderer administrator identity and click Next. 280 | - Review the summary and click Add ordering service. 281 | 282 | 283 |
284 |

285 | 286 |

287 |
288 | 289 | 290 | #### Add organization as Consortium Member on the orderer to transact 291 | - Navigate to the Nodes tab, and click on the Orderer that was created. 292 | - Under Consortium Members, click Add organization +. 293 | - Select the Existing MSP ID tab. From the drop-down list, select `Org1MSP (Org1MSP)`, as this is the MSP that represents the peer's organization "Org1". 294 | - Click Add organization. 295 | 296 |
297 |

298 | 299 |

300 |
301 | 302 | 303 | #### Create the channel 304 | - Navigate to the Channels tab in the left navigation and click Create channel +. 305 | - Click Next. 306 | - Give the Channel name as `mychannel`. Select `Orderer` from the Ordering service drop-down list. Click Next. 307 | - Under Organizations, select `Org1MSP (Org1MSP)` from the drop-down list to add the organization "Org1" as a member of this channel. Click the Add button. Set the permissions for this member as Operator. Click Next. 308 | - Leave the Policy as the default value i.e. `1 out of 1`. Click Next. 309 | - Select the Channel creator MSP as `Org1MSP (Org1MSP)` and the Identity as `Org1 Admin`. Click Next. 310 | - Review the summary and click Create channel. 311 | 312 |
313 |

314 | 315 |

316 |
317 | 318 | 319 | #### Join your peer to the channel 320 | - Click on the newly created channel mychannel. 321 | - In the side panel that opens, under Choose from available peers, select `Peer Org1`. Once the peer is selected, a check mark will be displayed next to it. Ensure that Make anchor peer(s) is marked as `Yes`. Click Join channel. 322 | 323 |
324 |

325 | 326 |

327 |
328 | 329 | 330 | ### 5. Deploy FabCar Smart Contract on the network 331 | 332 | #### Install a smart contract 333 | - Navigate to the Smart contracts tab in the left navigation and click Install smart contract +. 334 | - Click on Add file. 335 | - Browse to the location of the Fabcar smart contract package file (it is probably named `fabcar@1.0.0.cds`), which we packaged earlier using the IBM Blockchain Platform extension for Visual Studio code. 336 | - Once the contract is uploaded, click Install smart contract. 337 | 338 |
339 |

340 | 341 |

342 |
343 | 344 | 345 | #### Instantiate smart contract 346 | - Under Installed smart contracts, find the smart contract from the list (**Note: ours is called fabcar**) installed on our peer and click Instantiate from the overflow menu on the right side of the row. 347 | - On the side panel that opens, select the channel, `mychannel` on which to instantiate the smart contract. Click Next. 348 | - Select `Org1MSP` as the organization member to be included in the endorsement policy. Click Next. 349 | - Skip the Setup private data collection step and simply click Next. 350 | - Provide the Function name as `initLedger` and leave the Arguments blank. 351 | - Click Instantiate smart contract. 352 | 353 |
354 |

355 | 356 |

357 |
358 | 359 | 360 | ### 6. Connect application to the network 361 | 362 | #### Connect with sdk through connection profile 363 | - Navigate to the Organizations tab in the left navigation, and click on Org1MSP. 364 | - Click on Download Connection Profile. 365 | - In the side panel that opens up, select `Yes` as the response for Include Org1 CA for user registration and enrollment?. Under Select peers to include, select `Peer Org1`. Then click Download connection profile. This will download the connection json which we will use to establish a connection between the Node.js web application and the Blockchain Network. 366 | 367 |
368 |

369 | 370 |

371 |
372 | 373 | 374 | #### Create an application admin 375 | - Navigate to the Nodes tab in the left navigation, and under Certificate Authorities, choose Org1 CA. 376 | - Click on the Register User + button. Give an Enroll ID of `app-admin` and an Enroll secret of `app-adminpw`. Set the Type for this identity as `client`. Specify to Use root affiliation. Leave the Maximum enrollments field blank. Click Next. 377 | - Click on Add attribute +. Enter the attribute name as `hf.Registrar.Roles` and the attribute value as `*`. **NOTE: If you wish to use the deregisterUser.js script to remove/revoke/delete existing users, then you need to add another attribute `hf.Revoker` with the attribute value of `true` to your application admin.** 378 | - Click Register user. 379 | 380 |
381 |

382 | 383 |

384 |
385 | 386 | 387 | #### Update application connection profile 388 | 389 | - Copy the connection profile you downloaded into the [server folder](web-app/server). 390 | - Update the [config.json](web-app/server/config.json) file with: 391 | - The connection json file name you downloaded. 392 | - The enroll id and enroll secret for your app admin, which we earlier provided as `app-admin` and `app-adminpw` respectively. 393 | - The orgMSP ID, which we provided as `Org1MSP`. 394 | - The caName, which can be found in your connection json file under "organizations" -> "Org1MSP" -> certificateAuthorities". This would be like an IP address and a port. 395 | - The username you would like to register. 396 | - Update gateway discovery to `{ enabled: true, asLocalhost: false }` to connect to IBM Blockchain Platform. 397 | 398 | > the current default contents of the config.json are to connect to a local fabric instance from VS Code. 399 | 400 | After the updates, the contents of the config.json should look similar to the file shown below: 401 | ``` 402 | { 403 | "connection_file": "Org1MSP_profile.json", 404 | "appAdmin": "app-admin", 405 | "appAdminSecret": "app-adminpw", 406 | "orgMSPID": "Org1MSP", 407 | "caName": "169.46.208.151:30404", 408 | "userName": "user1", 409 | "gatewayDiscovery": { "enabled": true, "asLocalhost": false } 410 | } 411 | ``` 412 | 413 | 414 | ### 7. Run the application 415 | 416 | #### Enroll admin 417 | 418 | - First, navigate to the `web-app/server` directory, and install the node dependencies: 419 | 420 | ```bash 421 | cd web-app/server 422 | npm install 423 | ``` 424 | 425 | - Run the `enrollAdmin.js` script: 426 | 427 | ```bash 428 | node enrollAdmin.js 429 | ``` 430 | 431 | - You should see the following in the terminal: 432 | 433 | ```bash 434 | msg: Successfully enrolled admin user app-admin and imported it into the wallet 435 | ``` 436 | 437 | 438 | #### Register User 439 | 440 | - From the `server` directory, run the `registerUser.js` script: 441 | 442 | ```bash 443 | node registerUser.js 444 | ``` 445 | 446 | - You should see the following in the terminal: 447 | 448 | ```bash 449 | Successfully registered and enrolled admin user user1 and imported it into the wallet 450 | ``` 451 | 452 | #### Deregister User 453 | 454 | **NOTE: The following steps need to be performed only if you wish to revoke an existing user.** 455 | 456 | By default, removal of identities is disabled in IBM Blockchain Platform. If you wish to remove identities, you need to manually override this default setting in the IBM Blockchain Platform console. 457 | 458 | - On the console, go to the `Nodes` tab using the left hand navigation pane and click on your organization's CA. 459 | - Click on the settings icon in the left side. 460 | - In the pane that opens up on the right side, click on `Edit configuration JSON (Advanced)`. 461 | - Paste the following into the input block for `Configuration updates`, then click `Update Certificate Authority`. 462 | **NOTE: The value of 10 for passwordattempts is the default value. If your certificate authority was set up with a different number for passwordattempts then you need to use that number. You can find this value from the `Current configuration` section which is just above the `Configuration updates` section.** 463 | 464 | ```json 465 | { 466 | "ca": { 467 | "cfg": { 468 | "identities": { 469 | "passwordattempts": 10, 470 | "allowremove": true 471 | } 472 | } 473 | } 474 | } 475 | ``` 476 | 477 |
478 |

479 | 480 |

481 |
482 | 483 | 484 | The removal of identities will now be enabled. As long as your application admin has been created with the `hf.Revoker` attribute set to the value of `true` (as specified in the [Create an application admin](#-create-an-application-admin) step above, you can use the `deregisterUser.js` script to remove the user identity. 485 | 486 | - From the `server` directory, run the `deregisterUser.js` script. This script removes/revokes the user identified by `userName` specified in the [config.json](web-app/server/config.json) file. 487 | 488 | ```bash 489 | node deregisterUser.js 490 | ``` 491 | 492 | - You should see the following in the terminal: 493 | 494 | ```bash 495 | Successfully deregistered the user user1 and deleted it from the wallet. 496 | ``` 497 | 498 | 499 | #### Start the application server 500 | 501 | - From the `server` directory, start the server: 502 | 503 | ```bash 504 | npm start 505 | ``` 506 | 507 | 508 | #### Start the web client 509 | 510 | - In a new terminal, open the `web-app/client` directory and install the dependencies: 511 | 512 | ```bash 513 | cd web-app/client 514 | npm install 515 | ``` 516 | 517 | - Start the client: 518 | 519 | ```bash 520 | npm start 521 | ``` 522 | 523 | You can find the app running at http://localhost:4200/ 524 | 525 |
526 |

527 | 528 |

529 |
530 | 531 | You can go to the IBM Blockchain Platform console to monitor your users and get information on your channel including the blocks added. 532 | 533 |
534 |

535 | 536 |

537 |
538 | 539 | 540 | ## Troubleshooting 541 | 542 | * If you encounter an error ``discover error: access denied``, you need to set the `gatewayDiscovery` properly in your `config.json` file. This is REQUIRED You must set it as follows to connect to IBP: 543 | 544 | `"gatewayDiscovery": {"enabled": true, "asLocalhost": false }` 545 | 546 | * When running the *registerUser.js* script, if you get an error that says `Failed to register user user1: TypeError [ERR_INVALID_ARG_TYPE]: The "options.ca" property must be one of type string, Buffer, TypedArray, or DataView. Received type object` , you can get past this error by editing your connection profile that was downloaded from IBM Blockchain Platform. Open the connection profile and look for `tlsCACerts` under your certificateAuthority. If the `pem` value under `tlsCACerts` is of type array, remove the square brackets `[]` and convert it to a string. That is, if your connection profile is like the following image: 547 | 548 |

549 | 550 |

551 | 552 | update it as shown in the image below: 553 | 554 |

555 | 556 |

557 | 558 | * When running the *registerUser.js* script, if you get an error that says `Error: Calling register endpoint failed with error [Error: self signed certificate]`, you can get past this by adding `"httpOptions": {"verify": false}` to the certificateAuthorities section of the connection profile that was downloaded from IBM Blockchain Platform. 559 | 560 |
561 |

562 | 563 |

564 |
565 | 566 | * WHen running the *deregisterUser.js script, if you get an error that says `[[{"code":56,"message":"Identity removal is disabled"}]]`, this is because identity removal is disabled by default in IBM Blockchain Platform. You will have to enable it by updating the CA using the steps provided in the [Deregister User](#-deregister-user) section above. 567 | 568 | * When running the *deregisterUser.js* script, if you get an error that says `Failed to deregister user user1: Error: fabric-ca request revoke failed with errors [[{"code":71,"message":"Authorization failure"}]]`, this is because your application admin does not have the `hf.Revoker` attribute set to `true`. You will need to add a new application admin with this attribute, enroll the admin using the enroll.js script and then you should be able to run the deregisterUser.js script using this new application admin. 569 | 570 | 571 | ## Links 572 | 573 | * [Hyperledger Fabric Docs](http://hyperledger-fabric.readthedocs.io/en/latest/) 574 | * [IBM Code Patterns for Blockchain](https://developer.ibm.com/patterns/category/blockchain/) 575 | 576 | 577 | ## License 578 | 579 | This code pattern is licensed under the Apache Software License, Version 2. Separate third-party code objects invoked within this code pattern are licensed by their respective providers pursuant to their own separate licenses. Contributions are subject to the [Developer Certificate of Origin, Version 1.1 (DCO)](https://developercertificate.org/) and the [Apache Software License, Version 2](https://www.apache.org/licenses/LICENSE-2.0.txt). 580 | 581 | [Apache Software License (ASL) FAQ](https://www.apache.org/foundation/license-faq.html#WhatDoesItMEAN) 582 | -------------------------------------------------------------------------------- /contract/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | module.exports = { 6 | env: { 7 | node: true, 8 | mocha: true 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 8, 12 | sourceType: 'script' 13 | }, 14 | extends: "eslint:recommended", 15 | rules: { 16 | indent: ['error', 4], 17 | quotes: ['error', 'single'], 18 | semi: ['error', 'always'], 19 | 'no-unused-vars': ['error', { args: 'none' }], 20 | 'no-console': 'off', 21 | curly: 'error', 22 | eqeqeq: 'error', 23 | 'no-throw-literal': 'error', 24 | strict: 'error', 25 | 'no-var': 'error', 26 | 'dot-notation': 'error', 27 | 'no-tabs': 'error', 28 | 'no-trailing-spaces': 'error', 29 | 'no-use-before-define': 'error', 30 | 'no-useless-call': 'error', 31 | 'no-with': 'error', 32 | 'operator-linebreak': 'error', 33 | yoda: 'error', 34 | 'quote-props': ['error', 'as-needed'] 35 | } 36 | }; -------------------------------------------------------------------------------- /contract/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const FabCar = require('./lib/fabcar'); 8 | 9 | module.exports.FabCar = FabCar; 10 | module.exports.contracts = [ FabCar ]; 11 | -------------------------------------------------------------------------------- /contract/lib/fabcar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { Contract } = require('fabric-contract-api'); 8 | 9 | class FabCar extends Contract { 10 | 11 | async initLedger(ctx) { 12 | console.info('============= START : Initialize Ledger ==========='); 13 | const cars = [ 14 | { 15 | color: 'blue', 16 | make: 'Toyota', 17 | model: 'Prius', 18 | owner: 'Tomoko', 19 | }, 20 | { 21 | color: 'red', 22 | make: 'Ford', 23 | model: 'Mustang', 24 | owner: 'Brad', 25 | }, 26 | { 27 | color: 'green', 28 | make: 'Hyundai', 29 | model: 'Tucson', 30 | owner: 'Jin Soo', 31 | }, 32 | { 33 | color: 'yellow', 34 | make: 'Volkswagen', 35 | model: 'Passat', 36 | owner: 'Max', 37 | }, 38 | { 39 | color: 'black', 40 | make: 'Tesla', 41 | model: 'S', 42 | owner: 'Adriana', 43 | }, 44 | { 45 | color: 'purple', 46 | make: 'Peugeot', 47 | model: '205', 48 | owner: 'Michel', 49 | }, 50 | { 51 | color: 'white', 52 | make: 'Chery', 53 | model: 'S22L', 54 | owner: 'Aarav', 55 | }, 56 | { 57 | color: 'violet', 58 | make: 'Fiat', 59 | model: 'Punto', 60 | owner: 'Pari', 61 | }, 62 | { 63 | color: 'indigo', 64 | make: 'Tata', 65 | model: 'Nano', 66 | owner: 'Valeria', 67 | }, 68 | { 69 | color: 'brown', 70 | make: 'Holden', 71 | model: 'Barina', 72 | owner: 'Shotaro', 73 | }, 74 | ]; 75 | 76 | for (let i = 0; i < cars.length; i++) { 77 | cars[i].docType = 'car'; 78 | await ctx.stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); 79 | console.info('Added <--> ', cars[i]); 80 | } 81 | console.info('============= END : Initialize Ledger ==========='); 82 | } 83 | 84 | async queryCar(ctx, carNumber) { 85 | const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state 86 | if (!carAsBytes || carAsBytes.length === 0) { 87 | throw new Error(`${carNumber} does not exist`); 88 | } 89 | console.log(carAsBytes.toString()); 90 | return carAsBytes.toString(); 91 | } 92 | 93 | async createCar(ctx, carNumber, make, model, color, owner) { 94 | console.info('============= START : Create Car ==========='); 95 | 96 | const car = { 97 | color, 98 | docType: 'car', 99 | make, 100 | model, 101 | owner, 102 | }; 103 | 104 | await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); 105 | console.info('============= END : Create Car ==========='); 106 | } 107 | 108 | async queryAllCars(ctx) { 109 | const startKey = 'CAR0'; 110 | const endKey = 'CAR999'; 111 | 112 | const iterator = await ctx.stub.getStateByRange(startKey, endKey); 113 | 114 | const allResults = []; 115 | // eslint-disable-next-line no-constant-condition 116 | while (true) { 117 | const res = await iterator.next(); 118 | 119 | if (res.value && res.value.value.toString()) { 120 | console.log(res.value.value.toString('utf8')); 121 | 122 | const Key = res.value.key; 123 | let Record; 124 | try { 125 | Record = JSON.parse(res.value.value.toString('utf8')); 126 | } catch (err) { 127 | console.log(err); 128 | Record = res.value.value.toString('utf8'); 129 | } 130 | allResults.push({ Key, Record }); 131 | } 132 | if (res.done) { 133 | console.log('end of data'); 134 | await iterator.close(); 135 | console.info(allResults); 136 | return JSON.stringify(allResults); 137 | } 138 | } 139 | } 140 | 141 | async querySingleCar(ctx, key) { 142 | console.log('Key is ' + key); 143 | const res = await ctx.stub.getState(key); 144 | if (res){ 145 | console.log('Result is\n' + JSON.parse(res.toString())); 146 | let Record; 147 | try { 148 | Record = JSON.parse(res.toString('utf8')); 149 | } catch (err) { 150 | console.log(err); 151 | Record = res.toString('utf8'); 152 | } 153 | return JSON.stringify([{ key, Record }]); 154 | } 155 | else{ 156 | console.err('Did not find the car with carNo ' + key); 157 | return []; 158 | } 159 | } 160 | 161 | async changeCarOwner(ctx, carNumber, newOwner) { 162 | console.info('============= START : changeCarOwner ==========='); 163 | 164 | const carAsBytes = await ctx.stub.getState(carNumber); // get the car from chaincode state 165 | if (!carAsBytes || carAsBytes.length === 0) { 166 | throw new Error(`${carNumber} does not exist`); 167 | } 168 | const car = JSON.parse(carAsBytes.toString()); 169 | car.owner = newOwner; 170 | 171 | await ctx.stub.putState(carNumber, Buffer.from(JSON.stringify(car))); 172 | console.info('============= END : changeCarOwner ==========='); 173 | } 174 | 175 | } 176 | 177 | module.exports = FabCar; 178 | -------------------------------------------------------------------------------- /contract/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fabcar", 3 | "version": "1.0.0", 4 | "description": "FabCar contract implemented in JavaScript", 5 | "main": "index.js", 6 | "engines": { 7 | "node": ">=8", 8 | "npm": ">=5" 9 | }, 10 | "scripts": { 11 | "lint": "eslint .", 12 | "pretest": "npm run lint", 13 | "test": "nyc mocha --recursive", 14 | "start": "fabric-chaincode-node start" 15 | }, 16 | "engineStrict": true, 17 | "author": "Hyperledger", 18 | "license": "Apache-2.0", 19 | "dependencies": { 20 | "fabric-contract-api": "~1.4.4", 21 | "fabric-shim": "~1.4.4" 22 | }, 23 | "devDependencies": { 24 | "chai": "^4.2.0", 25 | "eslint": "^6.8.0", 26 | "eslint-config-standard": "^14.1.0", 27 | "eslint-plugin-import": "^2.20.0", 28 | "eslint-plugin-node": "^11.0.0", 29 | "eslint-plugin-promise": "^4.2.1", 30 | "eslint-plugin-standard": "^4.0.1", 31 | "mocha": "^7.0.0", 32 | "nyc": "^15.0.0", 33 | "sinon": "^8.1.0", 34 | "sinon-chai": "^3.4.0" 35 | }, 36 | "nyc": { 37 | "exclude": [ 38 | "coverage/**", 39 | "test/**" 40 | ], 41 | "reporter": [ 42 | "text-summary", 43 | "html" 44 | ], 45 | "all": true, 46 | "check-coverage": true, 47 | "statements": 100, 48 | "branches": 100, 49 | "functions": 100, 50 | "lines": 100 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "@babel/code-frame": { 6 | "version": "7.0.0", 7 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", 8 | "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", 9 | "dev": true, 10 | "requires": { 11 | "@babel/highlight": "7.0.0" 12 | } 13 | }, 14 | "@babel/highlight": { 15 | "version": "7.0.0", 16 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", 17 | "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", 18 | "dev": true, 19 | "requires": { 20 | "chalk": "2.4.2", 21 | "esutils": "2.0.2", 22 | "js-tokens": "4.0.0" 23 | } 24 | }, 25 | "acorn": { 26 | "version": "6.1.1", 27 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", 28 | "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", 29 | "dev": true 30 | }, 31 | "acorn-jsx": { 32 | "version": "5.0.1", 33 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", 34 | "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", 35 | "dev": true 36 | }, 37 | "ajv": { 38 | "version": "6.10.0", 39 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", 40 | "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", 41 | "dev": true, 42 | "requires": { 43 | "fast-deep-equal": "2.0.1", 44 | "fast-json-stable-stringify": "2.0.0", 45 | "json-schema-traverse": "0.4.1", 46 | "uri-js": "4.2.2" 47 | } 48 | }, 49 | "ansi-escapes": { 50 | "version": "3.2.0", 51 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", 52 | "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", 53 | "dev": true 54 | }, 55 | "ansi-regex": { 56 | "version": "3.0.0", 57 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 58 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 59 | "dev": true 60 | }, 61 | "ansi-styles": { 62 | "version": "3.2.1", 63 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 64 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 65 | "dev": true, 66 | "requires": { 67 | "color-convert": "1.9.3" 68 | } 69 | }, 70 | "argparse": { 71 | "version": "1.0.10", 72 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 73 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 74 | "dev": true, 75 | "requires": { 76 | "sprintf-js": "1.0.3" 77 | } 78 | }, 79 | "astral-regex": { 80 | "version": "1.0.0", 81 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", 82 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", 83 | "dev": true 84 | }, 85 | "balanced-match": { 86 | "version": "1.0.0", 87 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 88 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 89 | "dev": true 90 | }, 91 | "brace-expansion": { 92 | "version": "1.1.11", 93 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 94 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 95 | "dev": true, 96 | "requires": { 97 | "balanced-match": "1.0.0", 98 | "concat-map": "0.0.1" 99 | } 100 | }, 101 | "callsites": { 102 | "version": "3.1.0", 103 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 104 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 105 | "dev": true 106 | }, 107 | "chalk": { 108 | "version": "2.4.2", 109 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 110 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 111 | "dev": true, 112 | "requires": { 113 | "ansi-styles": "3.2.1", 114 | "escape-string-regexp": "1.0.5", 115 | "supports-color": "5.5.0" 116 | } 117 | }, 118 | "chardet": { 119 | "version": "0.7.0", 120 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 121 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", 122 | "dev": true 123 | }, 124 | "cli-cursor": { 125 | "version": "2.1.0", 126 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 127 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 128 | "dev": true, 129 | "requires": { 130 | "restore-cursor": "2.0.0" 131 | } 132 | }, 133 | "cli-width": { 134 | "version": "2.2.0", 135 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 136 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 137 | "dev": true 138 | }, 139 | "color-convert": { 140 | "version": "1.9.3", 141 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 142 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 143 | "dev": true, 144 | "requires": { 145 | "color-name": "1.1.3" 146 | } 147 | }, 148 | "color-name": { 149 | "version": "1.1.3", 150 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 151 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 152 | "dev": true 153 | }, 154 | "concat-map": { 155 | "version": "0.0.1", 156 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 157 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 158 | "dev": true 159 | }, 160 | "cross-spawn": { 161 | "version": "6.0.5", 162 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 163 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 164 | "dev": true, 165 | "requires": { 166 | "nice-try": "1.0.5", 167 | "path-key": "2.0.1", 168 | "semver": "5.7.0", 169 | "shebang-command": "1.2.0", 170 | "which": "1.3.1" 171 | } 172 | }, 173 | "debug": { 174 | "version": "4.1.1", 175 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", 176 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", 177 | "dev": true, 178 | "requires": { 179 | "ms": "2.1.2" 180 | } 181 | }, 182 | "deep-is": { 183 | "version": "0.1.3", 184 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 185 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 186 | "dev": true 187 | }, 188 | "doctrine": { 189 | "version": "3.0.0", 190 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", 191 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", 192 | "dev": true, 193 | "requires": { 194 | "esutils": "2.0.2" 195 | } 196 | }, 197 | "emoji-regex": { 198 | "version": "7.0.3", 199 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 200 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 201 | "dev": true 202 | }, 203 | "escape-string-regexp": { 204 | "version": "1.0.5", 205 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 206 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 207 | "dev": true 208 | }, 209 | "eslint": { 210 | "version": "6.0.1", 211 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz", 212 | "integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==", 213 | "dev": true, 214 | "requires": { 215 | "@babel/code-frame": "7.0.0", 216 | "ajv": "6.10.0", 217 | "chalk": "2.4.2", 218 | "cross-spawn": "6.0.5", 219 | "debug": "4.1.1", 220 | "doctrine": "3.0.0", 221 | "eslint-scope": "4.0.3", 222 | "eslint-utils": "1.3.1", 223 | "eslint-visitor-keys": "1.0.0", 224 | "espree": "6.0.0", 225 | "esquery": "1.0.1", 226 | "esutils": "2.0.2", 227 | "file-entry-cache": "5.0.1", 228 | "functional-red-black-tree": "1.0.1", 229 | "glob-parent": "3.1.0", 230 | "globals": "11.12.0", 231 | "ignore": "4.0.6", 232 | "import-fresh": "3.1.0", 233 | "imurmurhash": "0.1.4", 234 | "inquirer": "6.4.1", 235 | "is-glob": "4.0.1", 236 | "js-yaml": "3.13.1", 237 | "json-stable-stringify-without-jsonify": "1.0.1", 238 | "levn": "0.3.0", 239 | "lodash": "4.17.11", 240 | "minimatch": "3.0.4", 241 | "mkdirp": "0.5.1", 242 | "natural-compare": "1.4.0", 243 | "optionator": "0.8.2", 244 | "progress": "2.0.3", 245 | "regexpp": "2.0.1", 246 | "semver": "5.7.0", 247 | "strip-ansi": "4.0.0", 248 | "strip-json-comments": "2.0.1", 249 | "table": "5.4.1", 250 | "text-table": "0.2.0" 251 | } 252 | }, 253 | "eslint-scope": { 254 | "version": "4.0.3", 255 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", 256 | "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", 257 | "dev": true, 258 | "requires": { 259 | "esrecurse": "4.2.1", 260 | "estraverse": "4.2.0" 261 | } 262 | }, 263 | "eslint-utils": { 264 | "version": "1.3.1", 265 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", 266 | "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", 267 | "dev": true 268 | }, 269 | "eslint-visitor-keys": { 270 | "version": "1.0.0", 271 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", 272 | "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", 273 | "dev": true 274 | }, 275 | "espree": { 276 | "version": "6.0.0", 277 | "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", 278 | "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", 279 | "dev": true, 280 | "requires": { 281 | "acorn": "6.1.1", 282 | "acorn-jsx": "5.0.1", 283 | "eslint-visitor-keys": "1.0.0" 284 | } 285 | }, 286 | "esprima": { 287 | "version": "4.0.1", 288 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 289 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 290 | "dev": true 291 | }, 292 | "esquery": { 293 | "version": "1.0.1", 294 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 295 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 296 | "dev": true, 297 | "requires": { 298 | "estraverse": "4.2.0" 299 | } 300 | }, 301 | "esrecurse": { 302 | "version": "4.2.1", 303 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", 304 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", 305 | "dev": true, 306 | "requires": { 307 | "estraverse": "4.2.0" 308 | } 309 | }, 310 | "estraverse": { 311 | "version": "4.2.0", 312 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 313 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 314 | "dev": true 315 | }, 316 | "esutils": { 317 | "version": "2.0.2", 318 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 319 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 320 | "dev": true 321 | }, 322 | "external-editor": { 323 | "version": "3.0.3", 324 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", 325 | "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", 326 | "dev": true, 327 | "requires": { 328 | "chardet": "0.7.0", 329 | "iconv-lite": "0.4.24", 330 | "tmp": "0.0.33" 331 | } 332 | }, 333 | "fast-deep-equal": { 334 | "version": "2.0.1", 335 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 336 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 337 | "dev": true 338 | }, 339 | "fast-json-stable-stringify": { 340 | "version": "2.0.0", 341 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 342 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 343 | "dev": true 344 | }, 345 | "fast-levenshtein": { 346 | "version": "2.0.6", 347 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 348 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 349 | "dev": true 350 | }, 351 | "figures": { 352 | "version": "2.0.0", 353 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 354 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 355 | "dev": true, 356 | "requires": { 357 | "escape-string-regexp": "1.0.5" 358 | } 359 | }, 360 | "file-entry-cache": { 361 | "version": "5.0.1", 362 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", 363 | "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", 364 | "dev": true, 365 | "requires": { 366 | "flat-cache": "2.0.1" 367 | } 368 | }, 369 | "flat-cache": { 370 | "version": "2.0.1", 371 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", 372 | "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", 373 | "dev": true, 374 | "requires": { 375 | "flatted": "2.0.1", 376 | "rimraf": "2.6.3", 377 | "write": "1.0.3" 378 | } 379 | }, 380 | "flatted": { 381 | "version": "2.0.1", 382 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", 383 | "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", 384 | "dev": true 385 | }, 386 | "fs.realpath": { 387 | "version": "1.0.0", 388 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 389 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 390 | "dev": true 391 | }, 392 | "functional-red-black-tree": { 393 | "version": "1.0.1", 394 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 395 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 396 | "dev": true 397 | }, 398 | "glob": { 399 | "version": "7.1.4", 400 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 401 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 402 | "dev": true, 403 | "requires": { 404 | "fs.realpath": "1.0.0", 405 | "inflight": "1.0.6", 406 | "inherits": "2.0.4", 407 | "minimatch": "3.0.4", 408 | "once": "1.4.0", 409 | "path-is-absolute": "1.0.1" 410 | } 411 | }, 412 | "glob-parent": { 413 | "version": "3.1.0", 414 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", 415 | "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", 416 | "dev": true, 417 | "requires": { 418 | "is-glob": "3.1.0", 419 | "path-dirname": "1.0.2" 420 | }, 421 | "dependencies": { 422 | "is-glob": { 423 | "version": "3.1.0", 424 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", 425 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", 426 | "dev": true, 427 | "requires": { 428 | "is-extglob": "2.1.1" 429 | } 430 | } 431 | } 432 | }, 433 | "globals": { 434 | "version": "11.12.0", 435 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 436 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 437 | "dev": true 438 | }, 439 | "has-flag": { 440 | "version": "3.0.0", 441 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 442 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 443 | "dev": true 444 | }, 445 | "iconv-lite": { 446 | "version": "0.4.24", 447 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 448 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 449 | "dev": true, 450 | "requires": { 451 | "safer-buffer": "2.1.2" 452 | } 453 | }, 454 | "ignore": { 455 | "version": "4.0.6", 456 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 457 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 458 | "dev": true 459 | }, 460 | "import-fresh": { 461 | "version": "3.1.0", 462 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", 463 | "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", 464 | "dev": true, 465 | "requires": { 466 | "parent-module": "1.0.1", 467 | "resolve-from": "4.0.0" 468 | } 469 | }, 470 | "imurmurhash": { 471 | "version": "0.1.4", 472 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 473 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 474 | "dev": true 475 | }, 476 | "inflight": { 477 | "version": "1.0.6", 478 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 479 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 480 | "dev": true, 481 | "requires": { 482 | "once": "1.4.0", 483 | "wrappy": "1.0.2" 484 | } 485 | }, 486 | "inherits": { 487 | "version": "2.0.4", 488 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 489 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 490 | "dev": true 491 | }, 492 | "inquirer": { 493 | "version": "6.4.1", 494 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.4.1.tgz", 495 | "integrity": "sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw==", 496 | "dev": true, 497 | "requires": { 498 | "ansi-escapes": "3.2.0", 499 | "chalk": "2.4.2", 500 | "cli-cursor": "2.1.0", 501 | "cli-width": "2.2.0", 502 | "external-editor": "3.0.3", 503 | "figures": "2.0.0", 504 | "lodash": "4.17.11", 505 | "mute-stream": "0.0.7", 506 | "run-async": "2.3.0", 507 | "rxjs": "6.5.2", 508 | "string-width": "2.1.1", 509 | "strip-ansi": "5.2.0", 510 | "through": "2.3.8" 511 | }, 512 | "dependencies": { 513 | "ansi-regex": { 514 | "version": "4.1.0", 515 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 516 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 517 | "dev": true 518 | }, 519 | "strip-ansi": { 520 | "version": "5.2.0", 521 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 522 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 523 | "dev": true, 524 | "requires": { 525 | "ansi-regex": "4.1.0" 526 | } 527 | } 528 | } 529 | }, 530 | "is-extglob": { 531 | "version": "2.1.1", 532 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 533 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 534 | "dev": true 535 | }, 536 | "is-fullwidth-code-point": { 537 | "version": "2.0.0", 538 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 539 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 540 | "dev": true 541 | }, 542 | "is-glob": { 543 | "version": "4.0.1", 544 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 545 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 546 | "dev": true, 547 | "requires": { 548 | "is-extglob": "2.1.1" 549 | } 550 | }, 551 | "is-promise": { 552 | "version": "2.1.0", 553 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 554 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 555 | "dev": true 556 | }, 557 | "isexe": { 558 | "version": "2.0.0", 559 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 560 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 561 | "dev": true 562 | }, 563 | "js-tokens": { 564 | "version": "4.0.0", 565 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 566 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 567 | "dev": true 568 | }, 569 | "js-yaml": { 570 | "version": "3.13.1", 571 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 572 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 573 | "dev": true, 574 | "requires": { 575 | "argparse": "1.0.10", 576 | "esprima": "4.0.1" 577 | } 578 | }, 579 | "json-schema-traverse": { 580 | "version": "0.4.1", 581 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 582 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 583 | "dev": true 584 | }, 585 | "json-stable-stringify-without-jsonify": { 586 | "version": "1.0.1", 587 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 588 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 589 | "dev": true 590 | }, 591 | "levn": { 592 | "version": "0.3.0", 593 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 594 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 595 | "dev": true, 596 | "requires": { 597 | "prelude-ls": "1.1.2", 598 | "type-check": "0.3.2" 599 | } 600 | }, 601 | "lodash": { 602 | "version": "4.17.11", 603 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 604 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 605 | "dev": true 606 | }, 607 | "mimic-fn": { 608 | "version": "1.2.0", 609 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 610 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", 611 | "dev": true 612 | }, 613 | "minimatch": { 614 | "version": "3.0.4", 615 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 616 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 617 | "dev": true, 618 | "requires": { 619 | "brace-expansion": "1.1.11" 620 | } 621 | }, 622 | "minimist": { 623 | "version": "0.0.8", 624 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 625 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 626 | "dev": true 627 | }, 628 | "mkdirp": { 629 | "version": "0.5.1", 630 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 631 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 632 | "dev": true, 633 | "requires": { 634 | "minimist": "0.0.8" 635 | } 636 | }, 637 | "ms": { 638 | "version": "2.1.2", 639 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 640 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 641 | "dev": true 642 | }, 643 | "mute-stream": { 644 | "version": "0.0.7", 645 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 646 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 647 | "dev": true 648 | }, 649 | "natural-compare": { 650 | "version": "1.4.0", 651 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 652 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 653 | "dev": true 654 | }, 655 | "nice-try": { 656 | "version": "1.0.5", 657 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 658 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 659 | "dev": true 660 | }, 661 | "once": { 662 | "version": "1.4.0", 663 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 664 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 665 | "dev": true, 666 | "requires": { 667 | "wrappy": "1.0.2" 668 | } 669 | }, 670 | "onetime": { 671 | "version": "2.0.1", 672 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 673 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 674 | "dev": true, 675 | "requires": { 676 | "mimic-fn": "1.2.0" 677 | } 678 | }, 679 | "optionator": { 680 | "version": "0.8.2", 681 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 682 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 683 | "dev": true, 684 | "requires": { 685 | "deep-is": "0.1.3", 686 | "fast-levenshtein": "2.0.6", 687 | "levn": "0.3.0", 688 | "prelude-ls": "1.1.2", 689 | "type-check": "0.3.2", 690 | "wordwrap": "1.0.0" 691 | } 692 | }, 693 | "os-tmpdir": { 694 | "version": "1.0.2", 695 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 696 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 697 | "dev": true 698 | }, 699 | "parent-module": { 700 | "version": "1.0.1", 701 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 702 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 703 | "dev": true, 704 | "requires": { 705 | "callsites": "3.1.0" 706 | } 707 | }, 708 | "path-dirname": { 709 | "version": "1.0.2", 710 | "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", 711 | "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", 712 | "dev": true 713 | }, 714 | "path-is-absolute": { 715 | "version": "1.0.1", 716 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 717 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 718 | "dev": true 719 | }, 720 | "path-key": { 721 | "version": "2.0.1", 722 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 723 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 724 | "dev": true 725 | }, 726 | "prelude-ls": { 727 | "version": "1.1.2", 728 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 729 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 730 | "dev": true 731 | }, 732 | "progress": { 733 | "version": "2.0.3", 734 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 735 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 736 | "dev": true 737 | }, 738 | "punycode": { 739 | "version": "2.1.1", 740 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 741 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 742 | "dev": true 743 | }, 744 | "regexpp": { 745 | "version": "2.0.1", 746 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", 747 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", 748 | "dev": true 749 | }, 750 | "resolve-from": { 751 | "version": "4.0.0", 752 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 753 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 754 | "dev": true 755 | }, 756 | "restore-cursor": { 757 | "version": "2.0.0", 758 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 759 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 760 | "dev": true, 761 | "requires": { 762 | "onetime": "2.0.1", 763 | "signal-exit": "3.0.2" 764 | } 765 | }, 766 | "rimraf": { 767 | "version": "2.6.3", 768 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 769 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 770 | "dev": true, 771 | "requires": { 772 | "glob": "7.1.4" 773 | } 774 | }, 775 | "run-async": { 776 | "version": "2.3.0", 777 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 778 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 779 | "dev": true, 780 | "requires": { 781 | "is-promise": "2.1.0" 782 | } 783 | }, 784 | "rxjs": { 785 | "version": "6.5.2", 786 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", 787 | "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", 788 | "dev": true, 789 | "requires": { 790 | "tslib": "1.10.0" 791 | } 792 | }, 793 | "safer-buffer": { 794 | "version": "2.1.2", 795 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 796 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 797 | "dev": true 798 | }, 799 | "semver": { 800 | "version": "5.7.0", 801 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 802 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", 803 | "dev": true 804 | }, 805 | "shebang-command": { 806 | "version": "1.2.0", 807 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 808 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 809 | "dev": true, 810 | "requires": { 811 | "shebang-regex": "1.0.0" 812 | } 813 | }, 814 | "shebang-regex": { 815 | "version": "1.0.0", 816 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 817 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 818 | "dev": true 819 | }, 820 | "signal-exit": { 821 | "version": "3.0.2", 822 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 823 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 824 | "dev": true 825 | }, 826 | "slice-ansi": { 827 | "version": "2.1.0", 828 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", 829 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", 830 | "dev": true, 831 | "requires": { 832 | "ansi-styles": "3.2.1", 833 | "astral-regex": "1.0.0", 834 | "is-fullwidth-code-point": "2.0.0" 835 | } 836 | }, 837 | "sprintf-js": { 838 | "version": "1.0.3", 839 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 840 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 841 | "dev": true 842 | }, 843 | "string-width": { 844 | "version": "2.1.1", 845 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 846 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 847 | "dev": true, 848 | "requires": { 849 | "is-fullwidth-code-point": "2.0.0", 850 | "strip-ansi": "4.0.0" 851 | } 852 | }, 853 | "strip-ansi": { 854 | "version": "4.0.0", 855 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 856 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 857 | "dev": true, 858 | "requires": { 859 | "ansi-regex": "3.0.0" 860 | } 861 | }, 862 | "strip-json-comments": { 863 | "version": "2.0.1", 864 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 865 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 866 | "dev": true 867 | }, 868 | "supports-color": { 869 | "version": "5.5.0", 870 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 871 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 872 | "dev": true, 873 | "requires": { 874 | "has-flag": "3.0.0" 875 | } 876 | }, 877 | "table": { 878 | "version": "5.4.1", 879 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.1.tgz", 880 | "integrity": "sha512-E6CK1/pZe2N75rGZQotFOdmzWQ1AILtgYbMAbAjvms0S1l5IDB47zG3nCnFGB/w+7nB3vKofbLXCH7HPBo864w==", 881 | "dev": true, 882 | "requires": { 883 | "ajv": "6.10.0", 884 | "lodash": "4.17.11", 885 | "slice-ansi": "2.1.0", 886 | "string-width": "3.1.0" 887 | }, 888 | "dependencies": { 889 | "ansi-regex": { 890 | "version": "4.1.0", 891 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 892 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 893 | "dev": true 894 | }, 895 | "string-width": { 896 | "version": "3.1.0", 897 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 898 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 899 | "dev": true, 900 | "requires": { 901 | "emoji-regex": "7.0.3", 902 | "is-fullwidth-code-point": "2.0.0", 903 | "strip-ansi": "5.2.0" 904 | } 905 | }, 906 | "strip-ansi": { 907 | "version": "5.2.0", 908 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 909 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 910 | "dev": true, 911 | "requires": { 912 | "ansi-regex": "4.1.0" 913 | } 914 | } 915 | } 916 | }, 917 | "text-table": { 918 | "version": "0.2.0", 919 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 920 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 921 | "dev": true 922 | }, 923 | "through": { 924 | "version": "2.3.8", 925 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 926 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 927 | "dev": true 928 | }, 929 | "tmp": { 930 | "version": "0.0.33", 931 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 932 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 933 | "dev": true, 934 | "requires": { 935 | "os-tmpdir": "1.0.2" 936 | } 937 | }, 938 | "tslib": { 939 | "version": "1.10.0", 940 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", 941 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", 942 | "dev": true 943 | }, 944 | "type-check": { 945 | "version": "0.3.2", 946 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 947 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 948 | "dev": true, 949 | "requires": { 950 | "prelude-ls": "1.1.2" 951 | } 952 | }, 953 | "uri-js": { 954 | "version": "4.2.2", 955 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 956 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 957 | "dev": true, 958 | "requires": { 959 | "punycode": "2.1.1" 960 | } 961 | }, 962 | "which": { 963 | "version": "1.3.1", 964 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 965 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 966 | "dev": true, 967 | "requires": { 968 | "isexe": "2.0.0" 969 | } 970 | }, 971 | "wordwrap": { 972 | "version": "1.0.0", 973 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 974 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 975 | "dev": true 976 | }, 977 | "wrappy": { 978 | "version": "1.0.2", 979 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 980 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 981 | "dev": true 982 | }, 983 | "write": { 984 | "version": "1.0.3", 985 | "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", 986 | "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", 987 | "dev": true, 988 | "requires": { 989 | "mkdirp": "0.5.1" 990 | } 991 | } 992 | } 993 | } 994 | -------------------------------------------------------------------------------- /run-local.md: -------------------------------------------------------------------------------- 1 | 2 | # Create a network locally using IBM Blockchain Platform extension for VS Code 3 | 4 | ## Prerequisites 5 | 6 | You will need to install the [IBM Blockchain Platform Extension for VSCode version 1.38.0 or greater](https://marketplace.visualstudio.com/items?itemName=IBMBlockchain.ibm-blockchain-platform) 7 | 8 | You will also need to satisfy the following requirements for the [IBM Blockchain Platform Extension for VS Code](https://github.com/IBM-Blockchain/blockchain-vscode-extension/blob/master/README.md#requirements): 9 | 10 | - [Node v10.x and npm v6.x or greater](https://nodejs.org/en/download/) 11 | - [VSCode version 1.38.0 or greater](https://code.visualstudio.com) 12 | - [Docker version v17.06.2-ce or greater](https://www.docker.com/get-docker) 13 | - [Docker Compose v1.14.0 or greater](https://docs.docker.com/compose/install/) 14 | 15 | 16 | ## Steps 17 | 18 | 1. [Clone the repo](#1-clone-the-repo) 19 | 2. [Use the VS Code extension to set up a smart contract on a basic Fabric network](#2-use-the-vs-code-extension-to-set-up-a-smart-contract-on-a-basic-fabric-network) 20 | 3. [Run the application](#3-run-the-application) 21 | 22 | 23 | ### 1. Clone the repo 24 | 25 | Clone this repository in a folder your choice: 26 | 27 | ```bash 28 | git clone https://github.com/IBM/fabcar-blockchain-sample.git 29 | cd fabcar-blockchain-sample 30 | ``` 31 | 32 | 33 | ### 2. Use the VS Code extension to set up a smart contract on a basic Fabric network 34 | 35 | We will use the IBM Blockchain Platform extension to package the Fabcar smart contract. 36 | 37 | 38 | ### Package the smart contract 39 | 40 | * Open Visual Studio code and open the `contract` folder from this repository that was cloned earlier. 41 | 42 | Press the `F1` key to see the different VS code options. Choose `IBM Blockchain Platform: Package Open Project`. 43 | 44 |

45 | 46 |

47 | 48 | Click the `IBM Blockchain Platform` extension button on the left. This will show the packaged contracts on top and the blockchain connections on the bottom. 49 | 50 |

51 | 52 |

53 | 54 | * Next, if you want to, you can export the package to a location on your machine. However, exporting the packaged smart contract is not required for installing the smart contract on the local network. 55 | 56 | * Right click on the packaged contract (in this case, select fabcar@1.0.0) to export it and choose `Export Package`. Choose a location on your machine and save the `.cds` file. 57 | 58 | 59 | ### Setup fabric locally 60 | 61 | You should see `FABRIC ENVIRONMENTS` on the left side of the editor. Under this section, you should see `1 Org Local Fabric`. Click it to start the Local Fabric. 62 | 63 |

64 | 65 |

66 | 67 | The extension will now provision the Docker containers that will act as nodes in your network. Once the provisioning is finished and the network is up and running, you will see the options to install and instantiate the smart contract, the `Channels` information, the `Nodes` and the organization msps under `Organizations`. You are now ready to install the smart contract. 68 | 69 |

70 | 71 |

72 | 73 | 74 | ### Install and instantiate the smart contract 75 | 76 | #### Install 77 | 78 | * In the `FABRIC ENVIRONMENTS` section near the bottom, click on `Smart Contracts` > `Installed` > `+ Install`. You will see a pop-up similar to the graphic below. 79 | 80 |

81 | 82 |

83 | 84 | * Then select the packaged contract: `fabcar@1.0.0 Packaged` **Note** The 1.0.0 comes from your `package.json` line: `"version": "1.0.0"` 85 | 86 | After the install is complete, you should get a message `Successfully installed on peer peer0.org1.example.com`. You should also see that the contract is listed under `Installed` under `FABRIC ENVIRONMENTS`. 87 | 88 |

89 | 90 |

91 | 92 | 93 | #### Instantiate 94 | 95 | * Under **Smart Contracts** you will see a section that says **Instantiated**. Click on `+ Instantiate` under it. 96 | 97 | * The extension will then ask you which contract and version to instantiate — choose `fabcar@1.0.0 Installed`. 98 | 99 |

100 | 101 |

102 | 103 | * The extension will then ask you which function to call on instantiate — type in `initLedger` 104 | 105 |

106 | 107 |

108 | 109 | * Next, it will ask you for the arguments to the function. There are none, so just hit enter. 110 | 111 |

112 | 113 |

114 | 115 | * Next, the extension will then ask you do you want to use a provide a private data collection configuration file? - Click on `No`. 116 | 117 |

118 | 119 |

120 | 121 | * Lastly, the extension will then ask you do you want to choose a smart contract endorsement policy. Choose `Default (single endorser, any org)`. 122 | 123 |

124 | 125 |

126 | 127 | Once instantiation of the contract completes, you should get the message `Successfully instantiated smart contract` and you should see `fabcar@1.0.0` under `Instantiated` under `FABRIC ENVIRONMENTS`. 128 | 129 |

130 | 131 |

132 | 133 | 134 | ### Add app-admin identity on CA Node 135 | 136 | We will now create the app-admin identity using the CA (Certificate Authority) node. The identity information and key files are needed in order to authenticate and run the application. 137 | 138 | Under `FABRIC ENVIRONMENTS` section in the left hand pane, expand `Nodes` and right click on `Org1 CA`. Choose `Create Identity (register and enroll)`. 139 | 140 |

141 | 142 |

143 | 144 | Type `app-admin` and press the enter key. 145 | 146 |

147 | 148 |

149 | 150 | The extension will then ask if you want to add any attributes to this identity. Click on `Yes`. 151 | 152 |

153 | 154 |

155 | 156 | The extension will then ask you to provide the attributes for this identity. Enter `[{"name":"hf.Registrar.Roles","value":"*","ecert":true}]`. 157 | 158 |

159 | 160 |

161 | 162 | Once the identity is successfully created, you should get the message `Successfully created identity 'app-admin' with the attributes: [{"name":"hf.Registrar.Roles","value":"*","ecert":true}]`. You can now see `app-admin` in the `FABRIC WALLETS` section under `1 Org Local Fabric` > `Org1`. 163 | 164 |

165 | 166 |

167 | 168 | 169 | ### Export Wallet 170 | 171 | Under `FABRIC WALLETS` in the left hand pane, expand `1 Org Local Fabric`, and under it right click on `Org1` and select `Export Wallet`. 172 | 173 |

174 | 175 |

176 | 177 | You can save the exported files anywhere. 178 | 179 | From the exported directory, copy the contents of the `app-admin` folder to the following location in the directory where you have cloned this repo: 180 | 181 | ``` 182 | /fabcar-blockchain-sample/web-app/server/wallet/app-admin 183 | ``` 184 | 185 |

186 | 187 |

188 | 189 | 190 | ### Save the connection profile and update the config file 191 | 192 | The next step is to obtain the connection profile for `Org1`. Under `FABRIC GATEWAYS` in the left-hand pane, expand `1 Org Local Fabric` and right click on `Org1`. Select `Export Connection Profile`. Save the file to the following location in the directory where you have cloned this repo: 193 | 194 | ``` 195 | /fabcar-blockchain-sample/web-app/server 196 | ``` 197 | 198 |

199 | 200 |

201 | 202 | Next, open the [config.json](./web-app/server/config.json) file. We need to update this file to indicate that we want to run the application locally. 203 | 204 | - Specify the "connection_file" as the name of the connection profile file which was downloaded earlier. Unless you specified a different name when saving the file, it should probably be named `1 Org Local Fabric - Org1_connection.json`. 205 | - The value for "caName" can be obtained from the connection profile file. Open the connection profile and look for the certificateAuthorities section. Obtain the caName from the line just below it. It should probably be `Org1CA`.

206 | - Update gateway discovery to `{ enabled: true, asLocalhost: true }` to connect to the local fabric network. 207 | 208 | After these updates, the config.json file should look similar to the file shown below: 209 | 210 | ``` 211 | { 212 | "connection_file": "1 Org Local Fabric - Org1_connection.json", 213 | "appAdmin": "app-admin", 214 | "appAdminSecret": "app-adminpw", 215 | "orgMSPID": "Org1MSP", 216 | "caName": "Org1CA", 217 | "userName": "user1", 218 | "gatewayDiscovery": { "enabled": true, "asLocalhost": true } 219 | } 220 | ``` 221 | 222 | Almost done, you can run the application. 223 | 224 | 225 | ### 3. Run the application 226 | 227 | 228 | #### Register User 229 | 230 | - Run the `registerUser.js` script. 231 | 232 | ```bash 233 | node registerUser.js 234 | ``` 235 | 236 | - You should see the following in the terminal: 237 | 238 | ```bash 239 | Successfully registered and enrolled the user user1 and imported it into the wallet 240 | ``` 241 | 242 | 243 | #### Run the application server 244 | 245 | - From the `server` directory, start the server. 246 | 247 | ```bash 248 | npm start 249 | ``` 250 | 251 | 252 | #### Start the web client 253 | 254 | - In a new terminal, open the web client folder and install the dependencies. 255 | 256 | ```bash 257 | cd web-app/client 258 | npm install 259 | ``` 260 | 261 | - Start the client: 262 | 263 | ```bash 264 | npm start 265 | ``` 266 | 267 | You can find the app running at http://localhost:4200/ 268 | 269 |
270 |

271 | 272 |

273 |
274 | -------------------------------------------------------------------------------- /web-app/client/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /web-app/client/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /web-app/client/README.md: -------------------------------------------------------------------------------- 1 | # Client 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.2.3. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 28 | -------------------------------------------------------------------------------- /web-app/client/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "client": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/client", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "src/tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [] 29 | }, 30 | "configurations": { 31 | "production": { 32 | "fileReplacements": [ 33 | { 34 | "replace": "src/environments/environment.ts", 35 | "with": "src/environments/environment.prod.ts" 36 | } 37 | ], 38 | "optimization": true, 39 | "outputHashing": "all", 40 | "sourceMap": false, 41 | "extractCss": true, 42 | "namedChunks": false, 43 | "aot": true, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true 47 | } 48 | } 49 | }, 50 | "serve": { 51 | "builder": "@angular-devkit/build-angular:dev-server", 52 | "options": { 53 | "browserTarget": "client:build" 54 | }, 55 | "configurations": { 56 | "production": { 57 | "browserTarget": "client:build:production" 58 | } 59 | } 60 | }, 61 | "extract-i18n": { 62 | "builder": "@angular-devkit/build-angular:extract-i18n", 63 | "options": { 64 | "browserTarget": "client:build" 65 | } 66 | }, 67 | "test": { 68 | "builder": "@angular-devkit/build-angular:karma", 69 | "options": { 70 | "main": "src/test.ts", 71 | "polyfills": "src/polyfills.ts", 72 | "tsConfig": "src/tsconfig.spec.json", 73 | "karmaConfig": "src/karma.conf.js", 74 | "styles": [ 75 | "src/styles.css" 76 | ], 77 | "scripts": [], 78 | "assets": [ 79 | "src/favicon.ico", 80 | "src/assets" 81 | ] 82 | } 83 | }, 84 | "lint": { 85 | "builder": "@angular-devkit/build-angular:tslint", 86 | "options": { 87 | "tsConfig": [ 88 | "src/tsconfig.app.json", 89 | "src/tsconfig.spec.json" 90 | ], 91 | "exclude": [ 92 | "**/node_modules/**" 93 | ] 94 | } 95 | } 96 | } 97 | }, 98 | "client-e2e": { 99 | "root": "e2e/", 100 | "projectType": "application", 101 | "architect": { 102 | "e2e": { 103 | "builder": "@angular-devkit/build-angular:protractor", 104 | "options": { 105 | "protractorConfig": "e2e/protractor.conf.js", 106 | "devServerTarget": "client:serve" 107 | }, 108 | "configurations": { 109 | "production": { 110 | "devServerTarget": "client:serve:production" 111 | } 112 | } 113 | }, 114 | "lint": { 115 | "builder": "@angular-devkit/build-angular:tslint", 116 | "options": { 117 | "tsConfig": "e2e/tsconfig.e2e.json", 118 | "exclude": [ 119 | "**/node_modules/**" 120 | ] 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "defaultProject": "client" 127 | } 128 | -------------------------------------------------------------------------------- /web-app/client/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /web-app/client/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { browser } from 'protractor'; 2 | 3 | import { AppPage } from './app.po'; 4 | import { CreateCarForm } from './components/createCarForm'; 5 | import { ChangeCarOwnerForm } from './components/changeCarOwnerForm'; 6 | import { Submit } from './components/submit'; 7 | 8 | 9 | describe('App', () => { 10 | let page: AppPage; 11 | 12 | beforeEach(() => { 13 | page = new AppPage(); 14 | }); 15 | 16 | it('should have the correct title', () => { 17 | page.navigateTo(); 18 | expect(browser.getTitle()).toEqual('Client'); 19 | }); 20 | 21 | it('should display header', () => { 22 | page.navigateTo(); 23 | expect(page.getParagraphText()).toEqual('FabCar'); 24 | }); 25 | 26 | describe('Submit', () => { 27 | it('should show the create car form by default', () => { 28 | return page.navigateTo() 29 | .then(() => { 30 | return CreateCarForm.waitToAppear(); 31 | }); 32 | }); 33 | 34 | it('should show the change car owner form when clicked on', () => { 35 | return Submit.changeToForm('Change Car Owner') 36 | .then(() => { 37 | return ChangeCarOwnerForm.waitToAppear() 38 | .then(() => { 39 | return Submit.changeToForm('Change Car Owner') 40 | .then(() => { 41 | return ChangeCarOwnerForm.waitToAppear(); 42 | }); 43 | }); 44 | }); 45 | }); 46 | 47 | it('should show the create car form when clicked on', () => { 48 | return Submit.changeToForm('Change Car Owner') 49 | .then(() => { 50 | return Submit.changeToForm('Create Car') 51 | .then(() => { 52 | return CreateCarForm.waitToAppear() 53 | .then(() => { 54 | return Submit.changeToForm('Create Car') 55 | .then(() => { 56 | return CreateCarForm.waitToAppear(); 57 | }); 58 | }); 59 | }); 60 | }); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /web-app/client/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /web-app/client/e2e/src/components/changeCarOwnerForm.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by, ExpectedConditions } from 'protractor'; 2 | 3 | export class ChangeCarOwnerForm { 4 | 5 | static waitToAppear() { 6 | return browser.wait(ExpectedConditions.visibilityOf(element(by.tagName('app-change-car-owner-form'))), 5000); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /web-app/client/e2e/src/components/createCarForm.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by, ExpectedConditions } from 'protractor'; 2 | 3 | export class CreateCarForm { 4 | 5 | static waitToAppear() { 6 | return browser.wait(ExpectedConditions.visibilityOf(element(by.tagName('app-create-car-form'))), 5000); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /web-app/client/e2e/src/components/submit.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by, ExpectedConditions } from 'protractor'; 2 | 3 | export class Submit { 4 | 5 | static waitToAppear() { 6 | return browser.wait(ExpectedConditions.visibilityOf(element(by.tagName('app-create-car-form'))), 5000); 7 | } 8 | 9 | static changeToForm(desiredForm: string) { 10 | const desiredFormButton = element(by.buttonText(desiredForm)); 11 | return desiredFormButton.click(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /web-app/client/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /web-app/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^8.2.14", 15 | "@angular/common": "^8.2.14", 16 | "@angular/compiler": "^8.2.14", 17 | "@angular/core": "^8.2.14", 18 | "@angular/forms": "^8.2.14", 19 | "@angular/http": "^7.2.16", 20 | "@angular/platform-browser": "^8.2.14", 21 | "@angular/platform-browser-dynamic": "^8.2.14", 22 | "@angular/router": "^8.2.14", 23 | "core-js": "^3.6.4", 24 | "rxjs": "~6.5.4", 25 | "handlebars": "4.7.2", 26 | "fstream": "1.0.12", 27 | "zone.js": "~0.10.2" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "~0.803.23", 31 | "@angular/cli": "~8.3.23", 32 | "@angular/compiler-cli": "^8.2.14", 33 | "@angular/language-service": "^8.2.14", 34 | "@types/jasmine": "~3.5.1", 35 | "@types/jasminewd2": "~2.0.8", 36 | "@types/node": "~13.1.8", 37 | "codelyzer": "~5.2.1", 38 | "jasmine-core": "~3.5.0", 39 | "jasmine-spec-reporter": "~4.2.1", 40 | "karma": "~4.4.1", 41 | "karma-chrome-launcher": "~3.1.0", 42 | "karma-coverage-istanbul-reporter": "~2.1.1", 43 | "karma-jasmine": "~3.1.0", 44 | "karma-jasmine-html-reporter": "^1.5.1", 45 | "protractor": "~5.4.2", 46 | "ts-node": "~8.6.2", 47 | "tslint": "~5.20.1", 48 | "typescript": "~3.4.5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /web-app/client/src/app/api.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient, HttpHeaders } from '@angular/common/http'; 3 | import { Subject, BehaviorSubject } from 'rxjs'; 4 | 5 | const httpOptionsJson = { 6 | headers: new HttpHeaders({ 7 | 'Content-Type': 'text/plain', 8 | 'Accept': 'text/plain' 9 | }), 10 | }; 11 | 12 | const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Accept': 'application/json' }); 13 | 14 | const baseURL = `http://localhost:8081`; 15 | const queryAllCarsURL = `/queryAllCars`; 16 | const createCarURL = `/createCar`; 17 | const changeCarOwnerURL = `/changeCarOwner`; 18 | 19 | @Injectable() 20 | export class ApiService { 21 | 22 | public cars$: Subject> = new BehaviorSubject>([]); 23 | 24 | constructor(private http: HttpClient) { 25 | } 26 | 27 | createCar(color: string, make: string, model: string, owner: string) { 28 | return this.http.post(baseURL + createCarURL, ({ 29 | 'make': make, 30 | 'model': model, 31 | 'color': color, 32 | 'owner': owner 33 | }), {headers}).toPromise().then((result) => { this.queryAllCars(); }); 34 | 35 | } 36 | 37 | changeCarOwner(key: string, newOwner: string) { 38 | return this.http.post(baseURL + changeCarOwnerURL, {'key': key, 'newOwner': newOwner}, 39 | {headers}).toPromise().then((result) => { this.queryAllCars(); }); 40 | } 41 | 42 | queryAllCars() { 43 | return this.http.get>(baseURL + queryAllCarsURL, httpOptionsJson).subscribe((response) => { 44 | this.cars$.next(response); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web-app/client/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

FabCar

4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /web-app/client/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | @import '../colors.scss'; 2 | 3 | 4 | :host { 5 | display: flex; 6 | height: 100%; 7 | width: 100%; 8 | background-color: $background; 9 | flex-direction: column; 10 | 11 | } 12 | 13 | header { 14 | display: flex; 15 | background-color: $header-background; 16 | padding-left: 1em; 17 | 18 | h1 { 19 | color: $header-text; 20 | font-size: 1.8em; 21 | } 22 | } 23 | 24 | body { 25 | margin: 1em 0em; 26 | background-color: $background; 27 | height: 100%; 28 | display: flex; 29 | width: 100%; 30 | flex-direction: row; 31 | } 32 | -------------------------------------------------------------------------------- /web-app/client/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 4 | describe('AppComponent', () => { 5 | 6 | beforeEach(async(() => { 7 | TestBed.configureTestingModule({ 8 | declarations: [ 9 | AppComponent, 10 | ], 11 | schemas: [NO_ERRORS_SCHEMA] 12 | }).compileComponents(); 13 | })); 14 | 15 | it('should create the app', async(() => { 16 | const fixture = TestBed.createComponent(AppComponent); 17 | const app = fixture.debugElement.componentInstance; 18 | expect(app).toBeTruthy(); 19 | })); 20 | it(`should have as title 'client'`, async(() => { 21 | const fixture = TestBed.createComponent(AppComponent); 22 | const app = fixture.debugElement.componentInstance; 23 | expect(app.title).toEqual('Fabcar'); 24 | })); 25 | it('should render title in a h1 tag', async(() => { 26 | const fixture = TestBed.createComponent(AppComponent); 27 | fixture.detectChanges(); 28 | const compiled = fixture.debugElement.nativeElement; 29 | expect(compiled.querySelector('h1').textContent).toEqual('FabCar'); 30 | })); 31 | 32 | it('should have a body', async(() => { 33 | const fixture = TestBed.createComponent(AppComponent); 34 | const compiled = fixture.debugElement.nativeElement; 35 | expect(compiled.querySelector('body')).toBeTruthy(); 36 | })); 37 | 38 | it('body should contain submit', async(() => { 39 | const fixture = TestBed.createComponent(AppComponent); 40 | const compiled = fixture.debugElement.nativeElement; 41 | expect(compiled.querySelector('body').querySelector('app-submit')).toBeTruthy(); 42 | })); 43 | 44 | it('body should contain queryCar', async(() => { 45 | const fixture = TestBed.createComponent(AppComponent); 46 | const compiled = fixture.debugElement.nativeElement; 47 | expect(compiled.querySelector('body').querySelector('app-query-all-cars')).toBeTruthy(); 48 | })); 49 | }); 50 | -------------------------------------------------------------------------------- /web-app/client/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | title = 'Fabcar'; 10 | } 11 | -------------------------------------------------------------------------------- /web-app/client/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { FormsModule } from '@angular/forms'; 5 | 6 | import { AppComponent } from './app.component'; 7 | import { QueryAllCarsComponent } from './query-all-cars/query-all-cars.component'; 8 | import { ApiService } from './api.service'; 9 | import { CreateCarFormComponent } from './submit-component/create-car-form/create-car-form.component'; 10 | import { ChangeCarOwnerFormComponent } from './submit-component/change-car-owner-form/change-car-owner-form.component'; 11 | import { SubmitComponent } from './submit-component/submit.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent, 16 | QueryAllCarsComponent, 17 | CreateCarFormComponent, 18 | ChangeCarOwnerFormComponent, 19 | SubmitComponent 20 | ], 21 | imports: [ 22 | BrowserModule, 23 | HttpClientModule, 24 | FormsModule 25 | ], 26 | providers: [ 27 | ApiService 28 | ], 29 | bootstrap: [AppComponent] 30 | }) 31 | export class AppModule { } 32 | -------------------------------------------------------------------------------- /web-app/client/src/app/car.ts: -------------------------------------------------------------------------------- 1 | export class Car { 2 | 3 | constructor( 4 | public color: string, 5 | public make: string, 6 | public model: string, 7 | public owner: string 8 | ) {} 9 | } 10 | -------------------------------------------------------------------------------- /web-app/client/src/app/query-all-cars/query-all-cars.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
KeyColorMakeModelOwner
{{item.Key}}{{item.Record.color}}{{item.Record.make}}{{item.Record.model}}{{item.Record.owner}}
20 | 21 | -------------------------------------------------------------------------------- /web-app/client/src/app/query-all-cars/query-all-cars.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../colors.scss'; 2 | 3 | :host { 4 | height: 100%; 5 | width: 100%; 6 | background-color: $background; 7 | display: flex; 8 | flex: 1; 9 | padding-left: 1em; 10 | 11 | table { 12 | display: flex; 13 | flex-direction: column; 14 | flex: 1; 15 | text-align: left; 16 | 17 | thead { 18 | display: flex; 19 | flex-direction: row; 20 | th { 21 | flex: 1; 22 | } 23 | } 24 | 25 | tbody { 26 | flex-direction: column; 27 | overflow-y: overlay; 28 | flex: 1; 29 | font-size: 14px; 30 | tr { 31 | display: flex; 32 | flex: 1; 33 | flex-direction: row; 34 | td { 35 | flex: 1; 36 | } 37 | } 38 | 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /web-app/client/src/app/query-all-cars/query-all-cars.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { QueryAllCarsComponent } from './query-all-cars.component'; 4 | import { ApiService } from '../api.service'; 5 | import { of, BehaviorSubject } from 'rxjs'; 6 | 7 | describe('QueryAllCarsComponent', () => { 8 | let component: QueryAllCarsComponent; 9 | let fixture: ComponentFixture; 10 | const subjectMock = new BehaviorSubject>([{ 11 | Key: 'FAKECAR', Record: {color: 'testColor', make: 'testMake', model: 'testModel', owner: 'testOwner'}}]); 12 | 13 | beforeEach(async(() => { 14 | const apiServiceStub = { 15 | cars$: subjectMock.asObservable(), 16 | queryAllCars() { 17 | const cars = [{Key: 'FAKECAR', Record: {color: 'testColor', make: 'testMake', model: 'testModel', owner: 'testOwner'}}]; 18 | return of(cars); 19 | } 20 | }; 21 | 22 | TestBed.configureTestingModule({ 23 | declarations: [ QueryAllCarsComponent ], 24 | providers: [ {provide: ApiService, useValue: apiServiceStub} ] 25 | }) 26 | .compileComponents(); 27 | })); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(QueryAllCarsComponent); 31 | component = fixture.componentInstance; 32 | fixture.detectChanges(); 33 | }); 34 | 35 | it('should create', () => { 36 | expect(component).toBeTruthy(); 37 | }); 38 | 39 | it('should contain a table', () => { 40 | const queryAllCarsElement: HTMLElement = fixture.nativeElement; 41 | const table = queryAllCarsElement.querySelector('table'); 42 | expect(table); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /web-app/client/src/app/query-all-cars/query-all-cars.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ApiService } from '../api.service'; 3 | 4 | @Component({ 5 | selector: 'app-query-all-cars', 6 | templateUrl: './query-all-cars.component.html', 7 | styleUrls: ['./query-all-cars.component.scss'] 8 | }) 9 | export class QueryAllCarsComponent implements OnInit { 10 | 11 | private cars: Array; 12 | response; 13 | constructor(private apiService: ApiService) { } 14 | 15 | ngOnInit() { 16 | this.apiService.cars$.subscribe((carsArray) => { 17 | this.cars = carsArray; 18 | }); 19 | this.apiService.queryAllCars(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/change-car-owner-form/change-car-owner-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/change-car-owner-form/change-car-owner-form.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../colors.scss'; 2 | 3 | :host { 4 | height: 100%; 5 | width: 100%; 6 | padding: 1em; 7 | background: $active-form-background; 8 | color: $primary-text; 9 | display: flex; 10 | } 11 | div { 12 | display: flex; 13 | justify-content: center; 14 | form { 15 | display: flex; 16 | justify-self: center; 17 | flex-direction: column; 18 | font-size: 0.9em; 19 | button { 20 | border: none; 21 | background-color: $button-color; 22 | color: $button-text; 23 | height: 30px; 24 | font-size: 1em; 25 | width: 204px; 26 | margin: 4px; 27 | &:disabled { 28 | background-color: $disabled-button; 29 | cursor: not-allowed; 30 | } 31 | } 32 | .form-group { 33 | margin: 0.2em; 34 | display: flex; 35 | flex-direction: column; 36 | label { 37 | margin-right: 0.5em; 38 | } 39 | input { 40 | width: 200px; 41 | height: 1.8em; 42 | } 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/change-car-owner-form/change-car-owner-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormsModule, Form } from '@angular/forms'; 3 | 4 | import { ApiService } from '../../api.service'; 5 | 6 | @Component({ 7 | selector: 'app-change-car-owner-form', 8 | templateUrl: './change-car-owner-form.component.html', 9 | styleUrls: ['./change-car-owner-form.component.scss'] 10 | }) 11 | export class ChangeCarOwnerFormComponent implements OnInit { 12 | 13 | constructor(private apiService: ApiService) { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | async onSubmit(data) { 19 | console.log(data); 20 | return await this.apiService.changeCarOwner(data.key, data.newOwner); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/create-car-form/create-car-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/create-car-form/create-car-form.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../colors.scss'; 2 | 3 | :host { 4 | height: 100%; 5 | width: 100%; 6 | padding: 1em; 7 | display: flex; 8 | background: $active-form-background; 9 | color: $primary-text; 10 | } 11 | div { 12 | display: flex; 13 | justify-content: center; 14 | form { 15 | display: flex; 16 | justify-self: center; 17 | flex-direction: column; 18 | font-size: 0.9em; 19 | .form-group { 20 | margin: 0.2em; 21 | display: flex; 22 | flex-direction: column; 23 | label { 24 | margin-right: 0.5em; 25 | } 26 | input { 27 | width: 200px; 28 | height: 1.8em; 29 | } 30 | } 31 | button { 32 | width: 204px; 33 | margin: 4px; 34 | height: 30px; 35 | font-size: 1em; 36 | border: none; 37 | background-color: $button-color; 38 | color: $button-text; 39 | &:disabled { 40 | background-color: $disabled-button; 41 | cursor: not-allowed; 42 | } 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/create-car-form/create-car-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { HttpClient } from '@angular/common/http'; 4 | 5 | import { CreateCarFormComponent } from './create-car-form.component'; 6 | import { ApiService } from '../../../api.service'; 7 | 8 | describe('CreateCarFormComponent', () => { 9 | let component: CreateCarFormComponent; 10 | let fixture: ComponentFixture; 11 | let service: ApiService; 12 | const spy: any; 13 | const http: HttpClient; 14 | 15 | beforeEach(async(() => { 16 | TestBed.configureTestingModule({ 17 | declarations: [ CreateCarFormComponent ], 18 | imports: [FormsModule], 19 | providers: [{provide: ApiService, UseValue: service}] 20 | }) 21 | .compileComponents(); 22 | })); 23 | 24 | beforeEach(() => { 25 | service = new ApiService(http); 26 | fixture = TestBed.createComponent(CreateCarFormComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/create-car-form/create-car-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormsModule, Form } from '@angular/forms'; 3 | 4 | import { ApiService } from '../../api.service'; 5 | 6 | @Component({ 7 | selector: 'app-create-car-form', 8 | templateUrl: './create-car-form.component.html', 9 | styleUrls: ['./create-car-form.component.scss'] 10 | }) 11 | export class CreateCarFormComponent implements OnInit { 12 | 13 | constructor(private apiService: ApiService) { } 14 | 15 | ngOnInit() { 16 | } 17 | 18 | async onSubmit(data) { 19 | console.log(data); 20 | return this.apiService.createCar(data.color, data.make, data.model, data.owner); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/submit.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |
6 |
7 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/submit.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../colors.scss'; 2 | 3 | :host { 4 | height: 100%; 5 | background-color: $background; 6 | display: flex; 7 | flex-direction: column; 8 | flex: 1; 9 | margin: 0.5em 2em; 10 | width: 100%; 11 | } 12 | 13 | .tabs { 14 | button { 15 | border: none; 16 | font-size: 1em; 17 | color: $primary-text; 18 | &.activeTab { 19 | background-color: $active-form-background; 20 | } 21 | &:focus { 22 | outline: none; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/submit.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Component } from '@angular/core'; 3 | 4 | import { SubmitComponent } from './submit.component'; 5 | 6 | describe('SubmitComponent', () => { 7 | let component: SubmitComponent; 8 | let fixture: ComponentFixture; 9 | 10 | @Component({selector: 'app-create-car', template: ''}) 11 | class CreateCarStubComponent {} 12 | 13 | @Component({selector: 'app-change-car-owner', template: ''}) 14 | class ChangeCarOwnerStubComponent {} 15 | 16 | beforeEach(async() => { 17 | TestBed.configureTestingModule({ 18 | declarations: [ 19 | SubmitComponent, 20 | CreateCarStubComponent, 21 | ChangeCarOwnerStubComponent 22 | ] 23 | }).compileComponents(); 24 | }); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(SubmitComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | 36 | it('should set showCreateCar to true', () => { 37 | expect(component.showCreateCar).toEqual(true); 38 | }); 39 | 40 | it('should have a createCar component', () => { 41 | const compiled = fixture.debugElement.nativeElement; 42 | expect(compiled.querySelector('app-create-car')).toBeTruthy(); 43 | }); 44 | 45 | 46 | describe('toggle', () => { 47 | beforeEach(() => { 48 | component = fixture.componentInstance; 49 | }); 50 | 51 | it('should toggle showChange to true when called with create', () => { 52 | component.showCreateCar = false; 53 | component.toggle('create'); 54 | expect(component.showCreateCar).toEqual(true); 55 | }); 56 | 57 | it('should toggle showChange to false when called with change', () => { 58 | component.showCreateCar = true; 59 | component.toggle('change'); 60 | expect(component.showCreateCar).toEqual(false); 61 | }); 62 | 63 | it('should not toggle showChange if it is already the desired value', () => { 64 | component.showCreateCar = true; 65 | component.toggle('create'); 66 | expect(component.showCreateCar).toEqual(true); 67 | 68 | component.showCreateCar = false; 69 | component.toggle('change'); 70 | expect(component.showCreateCar).toEqual(false); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /web-app/client/src/app/submit-component/submit.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { CreateCarFormComponent } from './create-car-form/create-car-form.component'; 4 | import { ChangeCarOwnerFormComponent } from './change-car-owner-form/change-car-owner-form.component'; 5 | 6 | @Component({ 7 | selector: 'app-submit', 8 | templateUrl: './submit.component.html', 9 | styleUrls: ['./submit.component.scss'] 10 | }) 11 | export class SubmitComponent implements OnInit { 12 | 13 | constructor() { } 14 | 15 | showCreateCar = true; 16 | 17 | ngOnInit() { 18 | } 19 | 20 | toggle(tabName) { 21 | if (tabName === 'change') { 22 | this.showCreateCar = false; 23 | } 24 | if (tabName === 'create') { 25 | this.showCreateCar = true; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /web-app/client/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fabcar-blockchain-sample/e1f09b814ab3d21c10ff8ee76a526310dcce9dc6/web-app/client/src/assets/.gitkeep -------------------------------------------------------------------------------- /web-app/client/src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /web-app/client/src/colors.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | @import './ibmDuocolors.scss'; 16 | 17 | $header-background: $ibm-color__gray-90; 18 | $header-text: $ibm-color__white-0; 19 | 20 | $background: $ibm-color__gray-10; 21 | $primary-text: $ibm-color__gray-70; 22 | 23 | $active-form-background: $ibm-color__gray-20; 24 | $button-color: $ibm-color__blue-60; 25 | $button-text: $ibm-color__white-0; 26 | $disabled-button: $ibm-color__gray-50; 27 | -------------------------------------------------------------------------------- /web-app/client/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /web-app/client/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false 7 | }; 8 | 9 | /* 10 | * For easier debugging in development mode, you can import the following file 11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 12 | * 13 | * This import should be commented out in production mode because it will have a negative impact 14 | * on performance if an error is thrown. 15 | */ 16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 17 | -------------------------------------------------------------------------------- /web-app/client/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/fabcar-blockchain-sample/e1f09b814ab3d21c10ff8ee76a526310dcce9dc6/web-app/client/src/favicon.ico -------------------------------------------------------------------------------- /web-app/client/src/ibmDuocolors.scss: -------------------------------------------------------------------------------- 1 | /* SCSS Color Palette Variables */ 2 | $ibm-color__black-100: #000000; 3 | $ibm-color__blue-10: #EDF4FF; 4 | $ibm-color__blue-20: #C9DEFF; 5 | $ibm-color__blue-30: #97C1FF; 6 | $ibm-color__blue-40: #6EA6FF; 7 | $ibm-color__blue-50: #418CFF; 8 | $ibm-color__blue-60: #0062FF; 9 | $ibm-color__blue-70: #054ADA; 10 | $ibm-color__blue-80: #0530AD; 11 | $ibm-color__blue-90: #061F80; 12 | $ibm-color__blue-100: #051243; 13 | $ibm-color__cool-gray-10: #F2F4F8; 14 | $ibm-color__cool-gray-20: #D5D9E0; 15 | $ibm-color__cool-gray-30: #B9BFC7; 16 | $ibm-color__cool-gray-40: #9FA5AD; 17 | $ibm-color__cool-gray-50: #868D95; 18 | $ibm-color__cool-gray-60: #697077; 19 | $ibm-color__cool-gray-70: #50565B; 20 | $ibm-color__cool-gray-80: #373D42; 21 | $ibm-color__cool-gray-90: #242A2E; 22 | $ibm-color__cool-gray-100: #13171A; 23 | $ibm-color__cyan-10: #E3F6FF; 24 | $ibm-color__cyan-20: #B3E6FF; 25 | $ibm-color__cyan-30: #6CCAFF; 26 | $ibm-color__cyan-40: #30B0FF; 27 | $ibm-color__cyan-50: #1193E8; 28 | $ibm-color__cyan-60: #0072C3; 29 | $ibm-color__cyan-70: #0058A1; 30 | $ibm-color__cyan-80: #003D73; 31 | $ibm-color__cyan-90: #002B50; 32 | $ibm-color__cyan-100: #07192B; 33 | $ibm-color__gray-10: #F3F3F3; 34 | $ibm-color__gray-20: #DCDCDC; 35 | $ibm-color__gray-30: #BEBEBE; 36 | $ibm-color__gray-40: #A4A4A4; 37 | $ibm-color__gray-50: #8C8C8C; 38 | $ibm-color__gray-60: #6F6F6F; 39 | $ibm-color__gray-70: #565656; 40 | $ibm-color__gray-80: #3D3D3D; 41 | $ibm-color__gray-90: #282828; 42 | $ibm-color__gray-100: #171717; 43 | $ibm-color__green-10: #DAFBE4; 44 | $ibm-color__green-20: #9DEEB2; 45 | $ibm-color__green-30: #56D679; 46 | $ibm-color__green-40: #3DBB61; 47 | $ibm-color__green-50: #24A249; 48 | $ibm-color__green-60: #198038; 49 | $ibm-color__green-70: #10642A; 50 | $ibm-color__green-80: #054719; 51 | $ibm-color__green-90: #01330F; 52 | $ibm-color__green-100: #081B09; 53 | $ibm-color__magenta-10: #FFF0F6; 54 | $ibm-color__magenta-20: #FFCFE1; 55 | $ibm-color__magenta-30: #FFA0C2; 56 | $ibm-color__magenta-40: #FA75A6; 57 | $ibm-color__magenta-50: #EE538B; 58 | $ibm-color__magenta-60: #D12765; 59 | $ibm-color__magenta-70: #A11950; 60 | $ibm-color__magenta-80: #760A3A; 61 | $ibm-color__magenta-90: #57002B; 62 | $ibm-color__magenta-100: #2A0A16; 63 | $ibm-color__orange-40: #FC7B1E; 64 | $ibm-color__purple-10: #F7F1FF; 65 | $ibm-color__purple-20: #E6D6FF; 66 | $ibm-color__purple-30: #D0B0FF; 67 | $ibm-color__purple-40: #BB8EFF; 68 | $ibm-color__purple-50: #A970FF; 69 | $ibm-color__purple-60: #8A3FFC; 70 | $ibm-color__purple-70: #6E32C9; 71 | $ibm-color__purple-80: #4F2196; 72 | $ibm-color__purple-90: #38146B; 73 | $ibm-color__purple-100: #1E1033; 74 | $ibm-color__red-10: #FFF0F1; 75 | $ibm-color__red-20: #FCD0D3; 76 | $ibm-color__red-30: #FFA4A9; 77 | $ibm-color__red-40: #FF767C; 78 | $ibm-color__red-50: #FB4B53; 79 | $ibm-color__red-60: #DA1E28; 80 | $ibm-color__red-70: #A51920; 81 | $ibm-color__red-80: #750E13; 82 | $ibm-color__red-90: #570408; 83 | $ibm-color__red-100: #2C080A; 84 | $ibm-color__teal-10: #DBFBFB; 85 | $ibm-color__teal-20: #87EDED; 86 | $ibm-color__teal-30: #20D5D2; 87 | $ibm-color__teal-40: #00BAB6; 88 | $ibm-color__teal-50: #009E9A; 89 | $ibm-color__teal-60: #007D79; 90 | $ibm-color__teal-70: #006161; 91 | $ibm-color__teal-80: #004548; 92 | $ibm-color__teal-90: #003137; 93 | $ibm-color__teal-100: #081A1C; 94 | $ibm-color__warm-gray-10: #F7F3F1; 95 | $ibm-color__warm-gray-20: #E0DBDA; 96 | $ibm-color__warm-gray-30: #C1BCBB; 97 | $ibm-color__warm-gray-40: #A7A2A2; 98 | $ibm-color__warm-gray-50: #8F8B8B; 99 | $ibm-color__warm-gray-60: #726E6E; 100 | $ibm-color__warm-gray-70: #595555; 101 | $ibm-color__warm-gray-80: #403C3C; 102 | $ibm-color__warm-gray-90: #2B2828; 103 | $ibm-color__warm-gray-100: #1A1717; 104 | $ibm-color__white-0: #ffffff; 105 | $ibm-color__yellow-20: #FDD13A; 106 | $ibm-color-map: ( 107 | 'black': ( 108 | 100: $ibm-color__black-100, 109 | ), 110 | 'blue': ( 111 | 10: $ibm-color__blue-10, 112 | 20: $ibm-color__blue-20, 113 | 30: $ibm-color__blue-30, 114 | 40: $ibm-color__blue-40, 115 | 50: $ibm-color__blue-50, 116 | 60: $ibm-color__blue-60, 117 | 70: $ibm-color__blue-70, 118 | 80: $ibm-color__blue-80, 119 | 90: $ibm-color__blue-90, 120 | 100: $ibm-color__blue-100, 121 | ), 122 | 'cool-gray': ( 123 | 10: $ibm-color__cool-gray-10, 124 | 20: $ibm-color__cool-gray-20, 125 | 30: $ibm-color__cool-gray-30, 126 | 40: $ibm-color__cool-gray-40, 127 | 50: $ibm-color__cool-gray-50, 128 | 60: $ibm-color__cool-gray-60, 129 | 70: $ibm-color__cool-gray-70, 130 | 80: $ibm-color__cool-gray-80, 131 | 90: $ibm-color__cool-gray-90, 132 | 100: $ibm-color__cool-gray-100, 133 | ), 134 | 'cyan': ( 135 | 10: $ibm-color__cyan-10, 136 | 20: $ibm-color__cyan-20, 137 | 30: $ibm-color__cyan-30, 138 | 40: $ibm-color__cyan-40, 139 | 50: $ibm-color__cyan-50, 140 | 60: $ibm-color__cyan-60, 141 | 70: $ibm-color__cyan-70, 142 | 80: $ibm-color__cyan-80, 143 | 90: $ibm-color__cyan-90, 144 | 100: $ibm-color__cyan-100, 145 | ), 146 | 'gray': ( 147 | 10: $ibm-color__gray-10, 148 | 20: $ibm-color__gray-20, 149 | 30: $ibm-color__gray-30, 150 | 40: $ibm-color__gray-40, 151 | 50: $ibm-color__gray-50, 152 | 60: $ibm-color__gray-60, 153 | 70: $ibm-color__gray-70, 154 | 80: $ibm-color__gray-80, 155 | 90: $ibm-color__gray-90, 156 | 100: $ibm-color__gray-100, 157 | ), 158 | 'green': ( 159 | 10: $ibm-color__green-10, 160 | 20: $ibm-color__green-20, 161 | 30: $ibm-color__green-30, 162 | 40: $ibm-color__green-40, 163 | 50: $ibm-color__green-50, 164 | 60: $ibm-color__green-60, 165 | 70: $ibm-color__green-70, 166 | 80: $ibm-color__green-80, 167 | 90: $ibm-color__green-90, 168 | 100: $ibm-color__green-100, 169 | ), 170 | 'magenta': ( 171 | 10: $ibm-color__magenta-10, 172 | 20: $ibm-color__magenta-20, 173 | 30: $ibm-color__magenta-30, 174 | 40: $ibm-color__magenta-40, 175 | 50: $ibm-color__magenta-50, 176 | 60: $ibm-color__magenta-60, 177 | 70: $ibm-color__magenta-70, 178 | 80: $ibm-color__magenta-80, 179 | 90: $ibm-color__magenta-90, 180 | 100: $ibm-color__magenta-100, 181 | ), 182 | 'orange': ( 183 | 40: $ibm-color__orange-40, 184 | ), 185 | 'purple': ( 186 | 10: $ibm-color__purple-10, 187 | 20: $ibm-color__purple-20, 188 | 30: $ibm-color__purple-30, 189 | 40: $ibm-color__purple-40, 190 | 50: $ibm-color__purple-50, 191 | 60: $ibm-color__purple-60, 192 | 70: $ibm-color__purple-70, 193 | 80: $ibm-color__purple-80, 194 | 90: $ibm-color__purple-90, 195 | 100: $ibm-color__purple-100, 196 | ), 197 | 'red': ( 198 | 10: $ibm-color__red-10, 199 | 20: $ibm-color__red-20, 200 | 30: $ibm-color__red-30, 201 | 40: $ibm-color__red-40, 202 | 50: $ibm-color__red-50, 203 | 60: $ibm-color__red-60, 204 | 70: $ibm-color__red-70, 205 | 80: $ibm-color__red-80, 206 | 90: $ibm-color__red-90, 207 | 100: $ibm-color__red-100, 208 | ), 209 | 'teal': ( 210 | 10: $ibm-color__teal-10, 211 | 20: $ibm-color__teal-20, 212 | 30: $ibm-color__teal-30, 213 | 40: $ibm-color__teal-40, 214 | 50: $ibm-color__teal-50, 215 | 60: $ibm-color__teal-60, 216 | 70: $ibm-color__teal-70, 217 | 80: $ibm-color__teal-80, 218 | 90: $ibm-color__teal-90, 219 | 100: $ibm-color__teal-100, 220 | ), 221 | 'warm-gray': ( 222 | 10: $ibm-color__warm-gray-10, 223 | 20: $ibm-color__warm-gray-20, 224 | 30: $ibm-color__warm-gray-30, 225 | 40: $ibm-color__warm-gray-40, 226 | 50: $ibm-color__warm-gray-50, 227 | 60: $ibm-color__warm-gray-60, 228 | 70: $ibm-color__warm-gray-70, 229 | 80: $ibm-color__warm-gray-80, 230 | 90: $ibm-color__warm-gray-90, 231 | 100: $ibm-color__warm-gray-100, 232 | ), 233 | 'white': ( 234 | 0: $ibm-color__white-0, 235 | ), 236 | 'yellow': ( 237 | 20: $ibm-color__yellow-20, 238 | ), 239 | ); 240 | // CSS Variables 241 | @mixin ibm-color__css-variables { 242 | :root { 243 | --ibm-color__black-100: #000000; 244 | --ibm-color__blue-10: #EDF4FF; 245 | --ibm-color__blue-20: #C9DEFF; 246 | --ibm-color__blue-30: #97C1FF; 247 | --ibm-color__blue-40: #6EA6FF; 248 | --ibm-color__blue-50: #418CFF; 249 | --ibm-color__blue-60: #0062FF; 250 | --ibm-color__blue-70: #054ADA; 251 | --ibm-color__blue-80: #0530AD; 252 | --ibm-color__blue-90: #061F80; 253 | --ibm-color__blue-100: #051243; 254 | --ibm-color__cool-gray-10: #F2F4F8; 255 | --ibm-color__cool-gray-20: #D5D9E0; 256 | --ibm-color__cool-gray-30: #B9BFC7; 257 | --ibm-color__cool-gray-40: #9FA5AD; 258 | --ibm-color__cool-gray-50: #868D95; 259 | --ibm-color__cool-gray-60: #697077; 260 | --ibm-color__cool-gray-70: #50565B; 261 | --ibm-color__cool-gray-80: #373D42; 262 | --ibm-color__cool-gray-90: #242A2E; 263 | --ibm-color__cool-gray-100: #13171A; 264 | --ibm-color__cyan-10: #E3F6FF; 265 | --ibm-color__cyan-20: #B3E6FF; 266 | --ibm-color__cyan-30: #6CCAFF; 267 | --ibm-color__cyan-40: #30B0FF; 268 | --ibm-color__cyan-50: #1193E8; 269 | --ibm-color__cyan-60: #0072C3; 270 | --ibm-color__cyan-70: #0058A1; 271 | --ibm-color__cyan-80: #003D73; 272 | --ibm-color__cyan-90: #002B50; 273 | --ibm-color__cyan-100: #07192B; 274 | --ibm-color__gray-10: #F3F3F3; 275 | --ibm-color__gray-20: #DCDCDC; 276 | --ibm-color__gray-30: #BEBEBE; 277 | --ibm-color__gray-40: #A4A4A4; 278 | --ibm-color__gray-50: #8C8C8C; 279 | --ibm-color__gray-60: #6F6F6F; 280 | --ibm-color__gray-70: #565656; 281 | --ibm-color__gray-80: #3D3D3D; 282 | --ibm-color__gray-90: #282828; 283 | --ibm-color__gray-100: #171717; 284 | --ibm-color__green-10: #DAFBE4; 285 | --ibm-color__green-20: #9DEEB2; 286 | --ibm-color__green-30: #56D679; 287 | --ibm-color__green-40: #3DBB61; 288 | --ibm-color__green-50: #24A249; 289 | --ibm-color__green-60: #198038; 290 | --ibm-color__green-70: #10642A; 291 | --ibm-color__green-80: #054719; 292 | --ibm-color__green-90: #01330F; 293 | --ibm-color__green-100: #081B09; 294 | --ibm-color__magenta-10: #FFF0F6; 295 | --ibm-color__magenta-20: #FFCFE1; 296 | --ibm-color__magenta-30: #FFA0C2; 297 | --ibm-color__magenta-40: #FA75A6; 298 | --ibm-color__magenta-50: #EE538B; 299 | --ibm-color__magenta-60: #D12765; 300 | --ibm-color__magenta-70: #A11950; 301 | --ibm-color__magenta-80: #760A3A; 302 | --ibm-color__magenta-90: #57002B; 303 | --ibm-color__magenta-100: #2A0A16; 304 | --ibm-color__orange-40: #FC7B1E; 305 | --ibm-color__purple-10: #F7F1FF; 306 | --ibm-color__purple-20: #E6D6FF; 307 | --ibm-color__purple-30: #D0B0FF; 308 | --ibm-color__purple-40: #BB8EFF; 309 | --ibm-color__purple-50: #A970FF; 310 | --ibm-color__purple-60: #8A3FFC; 311 | --ibm-color__purple-70: #6E32C9; 312 | --ibm-color__purple-80: #4F2196; 313 | --ibm-color__purple-90: #38146B; 314 | --ibm-color__purple-100: #1E1033; 315 | --ibm-color__red-10: #FFF0F1; 316 | --ibm-color__red-20: #FCD0D3; 317 | --ibm-color__red-30: #FFA4A9; 318 | --ibm-color__red-40: #FF767C; 319 | --ibm-color__red-50: #FB4B53; 320 | --ibm-color__red-60: #DA1E28; 321 | --ibm-color__red-70: #A51920; 322 | --ibm-color__red-80: #750E13; 323 | --ibm-color__red-90: #570408; 324 | --ibm-color__red-100: #2C080A; 325 | --ibm-color__teal-10: #DBFBFB; 326 | --ibm-color__teal-20: #87EDED; 327 | --ibm-color__teal-30: #20D5D2; 328 | --ibm-color__teal-40: #00BAB6; 329 | --ibm-color__teal-50: #009E9A; 330 | --ibm-color__teal-60: #007D79; 331 | --ibm-color__teal-70: #006161; 332 | --ibm-color__teal-80: #004548; 333 | --ibm-color__teal-90: #003137; 334 | --ibm-color__teal-100: #081A1C; 335 | --ibm-color__warm-gray-10: #F7F3F1; 336 | --ibm-color__warm-gray-20: #E0DBDA; 337 | --ibm-color__warm-gray-30: #C1BCBB; 338 | --ibm-color__warm-gray-40: #A7A2A2; 339 | --ibm-color__warm-gray-50: #8F8B8B; 340 | --ibm-color__warm-gray-60: #726E6E; 341 | --ibm-color__warm-gray-70: #595555; 342 | --ibm-color__warm-gray-80: #403C3C; 343 | --ibm-color__warm-gray-90: #2B2828; 344 | --ibm-color__warm-gray-100: #1A1717; 345 | --ibm-color__white-0: #ffffff; 346 | --ibm-color__yellow-20: #FDD13A; 347 | } 348 | } 349 | /* do not include CSS variables by default */ 350 | /* exclude if $ibm-colors__include-css-variables is false */ 351 | $ibm-colors__include-css-variables: false !default; 352 | @if ($ibm-colors__include-css-variables == true) { 353 | @include ibm-color__css-variables; 354 | } 355 | -------------------------------------------------------------------------------- /web-app/client/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Client 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web-app/client/src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /web-app/client/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | 14 | -------------------------------------------------------------------------------- /web-app/client/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Web Animations `@angular/platform-browser/animations` 51 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 52 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 53 | **/ 54 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 55 | 56 | /** 57 | * By default, zone.js will patch all possible macroTask and DomEvents 58 | * user can disable parts of macroTask/DomEvents patch by setting following flags 59 | */ 60 | 61 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 62 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 63 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 64 | 65 | /* 66 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 67 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 68 | */ 69 | // (window as any).__Zone_enable_cross_context_check = true; 70 | 71 | /*************************************************************************************************** 72 | * Zone JS is required by default for Angular itself. 73 | */ 74 | import 'zone.js/dist/zone'; // Included with Angular CLI. 75 | 76 | 77 | 78 | /*************************************************************************************************** 79 | * APPLICATION IMPORTS 80 | */ 81 | -------------------------------------------------------------------------------- /web-app/client/src/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700|Source+Code+Pro'); 2 | @import './colors.scss'; 3 | 4 | * { 5 | font-family: IBMPlexSans-Light; 6 | } 7 | 8 | header { 9 | font-family: IBMPlexSans-Light; 10 | } 11 | 12 | body { 13 | font-family: IBMPlexSans-Light; 14 | margin: 0px; 15 | height: 100%; 16 | } 17 | 18 | html { 19 | height: 100%; 20 | } 21 | 22 | button { 23 | font-family: IBMPlexSans-Light; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /web-app/client/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /web-app/client/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /web-app/client/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /web-app/client/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /web-app/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "es2015", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2017", 18 | "dom" 19 | ], 20 | "paths": { 21 | "core-js/es7/reflect": [ 22 | "node_modules/core-js/proposals/reflect-metadata" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /web-app/client/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-redundant-jsdoc": true, 69 | "no-shadowed-variable": true, 70 | "no-string-literal": false, 71 | "no-string-throw": true, 72 | "no-switch-case-fall-through": true, 73 | "no-trailing-whitespace": true, 74 | "no-unnecessary-initializer": true, 75 | "no-unused-expression": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "no-inputs-metadata-property": true, 121 | "no-outputs-metadata-property": true, 122 | "no-host-metadata-property": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-life-cycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /web-app/server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | module.exports = { 6 | env: { 7 | node: true, 8 | mocha: true 9 | }, 10 | parserOptions: { 11 | ecmaVersion: 8, 12 | sourceType: 'script' 13 | }, 14 | extends: "eslint:recommended", 15 | rules: { 16 | indent: ['error', 4], 17 | quotes: ['error', 'single'], 18 | semi: ['error', 'always'], 19 | 'no-unused-vars': ['error', { args: 'none' }], 20 | 'no-console': 'off', 21 | curly: 'error', 22 | eqeqeq: 'error', 23 | 'no-throw-literal': 'error', 24 | strict: 'error', 25 | 'no-var': 'error', 26 | 'dot-notation': 'error', 27 | 'no-tabs': 'error', 28 | 'no-trailing-spaces': 'error', 29 | 'no-use-before-define': 'error', 30 | 'no-useless-call': 'error', 31 | 'no-with': 'error', 32 | 'operator-linebreak': 'error', 33 | yoda: 'error', 34 | 'quote-props': ['error', 'as-needed'] 35 | } 36 | }; -------------------------------------------------------------------------------- /web-app/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "connection_file": "1 Org Local Fabric - Org1_connection.json", 3 | "appAdmin": "app-admin", 4 | "appAdminSecret": "app-adminpw", 5 | "orgMSPID": "Org1MSP", 6 | "caName": "Org1CA", 7 | "userName": "user1", 8 | "gatewayDiscovery": { "enabled": true, "asLocalhost": true } 9 | } 10 | -------------------------------------------------------------------------------- /web-app/server/deregisterUser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { FileSystemWallet, Gateway } = require('fabric-network'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | // capture network variables from config.json 12 | const configPath = path.join(process.cwd(), 'config.json'); 13 | const configJSON = fs.readFileSync(configPath, 'utf8'); 14 | const config = JSON.parse(configJSON); 15 | let connection_file = config.connection_file; 16 | let appAdmin = config.appAdmin; 17 | let userName = config.userName; 18 | let gatewayDiscovery = config.gatewayDiscovery; 19 | 20 | const ccpPath = path.join(process.cwd(), connection_file); 21 | const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); 22 | const ccp = JSON.parse(ccpJSON); 23 | 24 | async function main() { 25 | try { 26 | 27 | // Create a new file system based wallet for managing identities. 28 | const walletPath = path.join(process.cwd(), 'wallet'); 29 | const wallet = new FileSystemWallet(walletPath); 30 | console.log(`Wallet path: ${walletPath}`); 31 | 32 | // Check to see if we've already enrolled the user. 33 | const userExists = await wallet.exists(userName); 34 | //TODO: enable once removal works successfully. 35 | if (!userExists) { 36 | console.log('An identity for the user ' + userName + ' does not exist in the wallet.'); 37 | return; 38 | } 39 | 40 | // Check to see if we've already enrolled the admin user. 41 | const adminExists = await wallet.exists(appAdmin); 42 | if (!adminExists) { 43 | console.log('An identity for the admin user ' + appAdmin + ' does not exist in the wallet'); 44 | console.log('Run the enrollAdmin.js application before retrying'); 45 | return; 46 | } 47 | 48 | // Create a new gateway for connecting to our peer node. 49 | const gateway = new Gateway(); 50 | await gateway.connect(ccp, { wallet, identity: appAdmin, discovery: gatewayDiscovery }); 51 | 52 | // Get the CA client object from the gateway for interacting with the CA. 53 | const ca = gateway.getClient().getCertificateAuthority(); 54 | const identityService = ca.newIdentityService(); 55 | const adminIdentity = gateway.getCurrentIdentity(); 56 | 57 | // Revoke the user, then delete the user from wallet. 58 | await ca.revoke({ enrollmentID: userName }, adminIdentity); 59 | identityService.delete(userName, adminIdentity, true).then(function() { 60 | wallet.delete(userName); 61 | console.log('Successfully deregistered the user ' + userName + ' and deleted it from the wallet.'); 62 | }); 63 | 64 | } catch (error) { 65 | console.error('Failed to deregister user ' + userName + ': ' + error); 66 | process.exit(1); 67 | } 68 | } 69 | 70 | main(); 71 | -------------------------------------------------------------------------------- /web-app/server/enrollAdmin.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const FabricCAServices = require('fabric-ca-client'); 8 | const { FileSystemWallet, X509WalletMixin } = require('fabric-network'); 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | 12 | // capture network variables from config.json 13 | const configPath = path.join(process.cwd(), 'config.json'); 14 | const configJSON = fs.readFileSync(configPath, 'utf8'); 15 | const config = JSON.parse(configJSON); 16 | let connection_file = config.connection_file; 17 | let appAdmin = config.appAdmin; 18 | let appAdminSecret = config.appAdminSecret; 19 | let orgMSPID = config.orgMSPID; 20 | let caName = config.caName; 21 | 22 | const ccpPath = path.join(process.cwd(), connection_file); 23 | const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); 24 | const ccp = JSON.parse(ccpJSON); 25 | 26 | 27 | async function main() { 28 | try { 29 | // Create a new CA client for interacting with the CA. 30 | const caURL = ccp.certificateAuthorities[caName].url; 31 | const ca = new FabricCAServices(caURL); 32 | 33 | // Create a new file system based wallet for managing identities. 34 | const walletPath = path.join(process.cwd(), 'wallet'); 35 | const wallet = new FileSystemWallet(walletPath); 36 | 37 | // Check to see if we've already enrolled the admin user. 38 | const adminExists = await wallet.exists(appAdmin); 39 | if (adminExists) { 40 | console.log('An identity for the admin user ' + appAdmin + ' already exists in the wallet'); 41 | return; 42 | } 43 | 44 | // Enroll the admin user, and import the new identity into the wallet. 45 | const enrollment = await ca.enroll({ enrollmentID: appAdmin, enrollmentSecret: appAdminSecret }); 46 | const identity = X509WalletMixin.createIdentity(orgMSPID, enrollment.certificate, enrollment.key.toBytes()); 47 | wallet.import(appAdmin, identity); 48 | console.log('msg: Successfully enrolled admin user ' + appAdmin + ' and imported it into the wallet'); 49 | 50 | } catch (error) { 51 | console.error('Failed to enroll admin user ' + appAdmin + ': ' + error); 52 | process.exit(1); 53 | } 54 | } 55 | 56 | main(); 57 | -------------------------------------------------------------------------------- /web-app/server/mychannel_fabcar_profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mychannel", 3 | "description": "Network on IBP v2", 4 | "version": "1.0.0", 5 | "client": { 6 | "organization": "org1msp" 7 | }, 8 | "organizations": { 9 | "org1msp": { 10 | "mspid": "org1msp", 11 | "peers": [ 12 | "184.173.1.18:30382" 13 | ], 14 | "certificateAuthorities": [ 15 | "184.173.1.18:31317" 16 | ] 17 | } 18 | }, 19 | "orderers": { 20 | "184.173.1.18:32331": { 21 | "url": "grpcs://184.173.1.18:32331", 22 | "tlsCACerts": { 23 | "pem": "-----BEGIN CERTIFICATE-----\nMIICJzCCAc6gAwIBAgIUM0ZSbZkSy38Qtoh2Kgn5J657v2wwCgYIKoZIzj0EAwIw\nZTELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRYwFAYDVQQDEw1PcmRlcmVy\nQ0EtdGxzMB4XDTE5MDYxMjIyNTkwMFoXDTM0MDYwODIyNTkwMFowZTELMAkGA1UE\nBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBlcmxl\nZGdlcjEPMA0GA1UECxMGRmFicmljMRYwFAYDVQQDEw1PcmRlcmVyQ0EtdGxzMFkw\nEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3miEpM7NdEUyzFOb6STZ80kBkDTlU68R\nm7Fd5vxQCbIzjpv7c3oHpKwelbOUoxnSfnOxguI8foL8tjc9PI7bnqNcMFowDgYD\nVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFAzcfoYz\n2AjE2jJ3mI9cg81ZDrxUMBUGA1UdEQQOMAyHBLitARKHBApMxb4wCgYIKoZIzj0E\nAwIDRwAwRAIgbDfL/vQ7sjeNafdwX/B254blbze15D9lUI7esqGUmSUCIDJGlx8s\nbSQqkXWzCcHhk5oaNL0fkSNCFuimNRX5MwWV\n-----END CERTIFICATE-----\n" 24 | } 25 | } 26 | }, 27 | "peers": { 28 | "184.173.1.18:30382": { 29 | "url": "grpcs://184.173.1.18:30382", 30 | "tlsCACerts": { 31 | "pem": "-----BEGIN CERTIFICATE-----\nMIICIjCCAcigAwIBAgIUXZxjcA/A7+mZFWy7541G0lOuHP0wCgYIKoZIzj0EAwIw\nYjELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK\nEwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGRmFicmljMRMwEQYDVQQDEwpPcmcxQ0Et\ndGxzMB4XDTE5MDYxMjIyNDkwMFoXDTM0MDYwODIyNDkwMFowYjELMAkGA1UEBhMC\nVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQKEwtIeXBlcmxlZGdl\ncjEPMA0GA1UECxMGRmFicmljMRMwEQYDVQQDEwpPcmcxQ0EtdGxzMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAECQ/tUywslvuBBhvjQm3bp38sRL/m0n61dLdrz0Sq\njelpWrz2IfPHxyNy/PEqg3KAbZ9Ns0T28txS78qHwGA5aKNcMFowDgYDVR0PAQH/\nBAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYEFHikN/47QzqnbIT5\nPQsABQzEw9i+MBUGA1UdEQQOMAyHBLitARKHBApMxb4wCgYIKoZIzj0EAwIDSAAw\nRQIhALgrnuG7NsJYhhjFgWUMQgb1QIAG7k4GjDV+yneKOqPYAiBHJuhnSSppruyP\njapUGV0ZvcAReGW2Ak/nOKh++JQjMg==\n-----END CERTIFICATE-----\n" 32 | }, 33 | "grpcOptions": { 34 | "ssl-target-name-override": "184.173.1.18" 35 | } 36 | } 37 | }, 38 | "certificateAuthorities": { 39 | "184.173.1.18:31317": { 40 | "url": "https://184.173.1.18:31317", 41 | "caName": "ca", 42 | "tlsCACerts": { 43 | "pem": "-----BEGIN CERTIFICATE-----\r\nMIICdTCCAd6gAwIBAgIJbttyjhapoyJIMA0GCSqGSIb3DQEBBQUAMHIxFTATBgNV\r\nBAMTDDE4NC4xNzMuMS4xODELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENh\r\ncm9saW5hMRAwDgYDVQQHEwdSYWxlaWdoMQwwCgYDVQQKEwNJQk0xEzARBgNVBAsT\r\nCkJsb2NrY2hhaW4wHhcNMTkwNjEyMjI1MzI1WhcNMjAwNjExMjI1MzI1WjByMRUw\r\nEwYDVQQDEwwxODQuMTczLjEuMTgxCzAJBgNVBAYTAlVTMRcwFQYDVQQIEw5Ob3J0\r\naCBDYXJvbGluYTEQMA4GA1UEBxMHUmFsZWlnaDEMMAoGA1UEChMDSUJNMRMwEQYD\r\nVQQLEwpCbG9ja2NoYWluMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiLWoc\r\n7hqRkbzWeSkDw1UX3D7CaCPecoHfSC0alNCCPSLN7uPX1AZHf2FmcIRMzAnsPUWi\r\nz/LwLqX+c8gJ8g9oph954n3/7vj/kKNDEtfWB/3xnj54H/LXlBun5CHxkl43Czwf\r\n0ot8qo8KW/UdIFPffQCL2MA6JcJ02DiRr6YgXwIDAQABoxMwETAPBgNVHREECDAG\r\nhwS4rQESMA0GCSqGSIb3DQEBBQUAA4GBADKrn0y7phlXaioy6SNZ9aiXPlAt52Ey\r\nnPlpapKTFIOp4KtOiMCXSc9jkMiZKML/YaQKDvo36GdDfoLplSnwrlWXrsMU0MuS\r\nLAjc5LS/2NRAtCWaaolROtvVH7c6UjhVs91nBc0hb7CwmFN8JFFPANKKu12kyJF8\r\nbGfjKECDYQud\r\n-----END CERTIFICATE-----\r\n" 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /web-app/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "./node_modules/nodemon/bin/nodemon.js src/app.js", 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "lint": "eslint ." 10 | }, 11 | "dependencies": { 12 | "body-parser": "^1.19.0", 13 | "cors": "^2.8.5", 14 | "eslint": "^6.8.0", 15 | "express": "^4.17.1", 16 | "fabric-ca-client": "~1.4.5", 17 | "fabric-network": "~1.4.5", 18 | "handlebars": "^4.7.6", 19 | "morgan": "^1.9.1", 20 | "nodemon": "^2.0.2", 21 | "tar": ">=5.0.5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /web-app/server/registerUser.js: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: Apache-2.0 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const { FileSystemWallet, Gateway, X509WalletMixin } = require('fabric-network'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | 11 | // capture network variables from config.json 12 | const configPath = path.join(process.cwd(), 'config.json'); 13 | const configJSON = fs.readFileSync(configPath, 'utf8'); 14 | const config = JSON.parse(configJSON); 15 | let connection_file = config.connection_file; 16 | let appAdmin = config.appAdmin; 17 | let orgMSPID = config.orgMSPID; 18 | let userName = config.userName; 19 | let gatewayDiscovery = config.gatewayDiscovery; 20 | 21 | const ccpPath = path.join(process.cwd(), connection_file); 22 | const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); 23 | const ccp = JSON.parse(ccpJSON); 24 | 25 | async function main() { 26 | try { 27 | 28 | // Create a new file system based wallet for managing identities. 29 | const walletPath = path.join(process.cwd(), 'wallet'); 30 | const wallet = new FileSystemWallet(walletPath); 31 | console.log(`Wallet path: ${walletPath}`); 32 | 33 | // Check to see if we've already enrolled the user. 34 | const userExists = await wallet.exists(userName); 35 | if (userExists) { 36 | console.log('An identity for the user ' + userName + ' already exists in the wallet'); 37 | return; 38 | } 39 | 40 | // Check to see if we've already enrolled the admin user. 41 | const adminExists = await wallet.exists(appAdmin); 42 | if (!adminExists) { 43 | console.log('An identity for the admin user ' + appAdmin + ' does not exist in the wallet'); 44 | console.log('Run the enrollAdmin.js application before retrying'); 45 | return; 46 | } 47 | 48 | // Create a new gateway for connecting to our peer node. 49 | const gateway = new Gateway(); 50 | await gateway.connect(ccp, { wallet, identity: appAdmin, discovery: gatewayDiscovery }); 51 | 52 | // Get the CA client object from the gateway for interacting with the CA. 53 | const ca = gateway.getClient().getCertificateAuthority(); 54 | const adminIdentity = gateway.getCurrentIdentity(); 55 | // Register the user, enroll the user, and import the new identity into the wallet. 56 | const secret = await ca.register({ enrollmentID: userName, role: 'client' }, adminIdentity); 57 | const enrollment = await ca.enroll({ enrollmentID: userName, enrollmentSecret: secret }); 58 | const userIdentity = X509WalletMixin.createIdentity(orgMSPID, enrollment.certificate, enrollment.key.toBytes()); 59 | wallet.import(userName, userIdentity); 60 | console.log('Successfully registered and enrolled the user ' + userName + ' and imported it into the wallet'); 61 | 62 | } catch (error) { 63 | console.error('Failed to register user ' + userName + ': ' + error); 64 | process.exit(1); 65 | } 66 | } 67 | 68 | main(); 69 | -------------------------------------------------------------------------------- /web-app/server/src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const express = require('express'); 4 | const bodyParser = require('body-parser'); 5 | const cors = require('cors'); 6 | const morgan = require('morgan'); 7 | 8 | let network = require('./fabric/network.js'); 9 | 10 | const app = express(); 11 | app.use(morgan('combined')); 12 | app.use(bodyParser.json()); 13 | app.use(cors()); 14 | 15 | 16 | app.get('/queryAllCars', (req, res) => { 17 | network.queryAllCars() 18 | .then((response) => { 19 | let carsRecord = JSON.parse(response); 20 | res.send(carsRecord); 21 | }); 22 | }); 23 | 24 | app.get('/querySingleCar', (req, res) => { 25 | console.log(req.query.key); 26 | network.querySingleCar(req.query.key) 27 | .then((response) => { 28 | let carsRecord = JSON.parse(response); 29 | res.send(carsRecord); 30 | }); 31 | }); 32 | 33 | app.post('/createCar', (req, res) => { 34 | console.log(req.body); 35 | network.queryAllCars() 36 | .then((response) => { 37 | console.log(response); 38 | let carsRecord = JSON.parse(response); 39 | let numCars = carsRecord.length; 40 | let newKey = 'CAR' + numCars; 41 | network.createCar(newKey, req.body.make, req.body.model, req.body.color, req.body.owner) 42 | .then((response) => { 43 | res.send(response); 44 | }); 45 | }); 46 | }); 47 | 48 | app.post('/changeCarOwner', (req, res) => { 49 | network.changeCarOwner(req.body.key, req.body.newOwner) 50 | .then((response) => { 51 | res.send(response); 52 | }); 53 | }); 54 | 55 | app.listen(process.env.PORT || 8081); -------------------------------------------------------------------------------- /web-app/server/src/fabric/network.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const { FileSystemWallet, Gateway } = require('fabric-network'); 5 | const fs = require('fs'); 6 | const path = require('path'); 7 | 8 | 9 | // capture network variables from config.json 10 | const configPath = path.join(process.cwd(), '/config.json'); 11 | const configJSON = fs.readFileSync(configPath, 'utf8'); 12 | const config = JSON.parse(configJSON); 13 | let connection_file = config.connection_file; 14 | let userName = config.userName; 15 | let gatewayDiscovery = config.gatewayDiscovery; 16 | 17 | // connect to the connection file 18 | const ccpPath = path.join(process.cwd(), connection_file); 19 | const ccpJSON = fs.readFileSync(ccpPath, 'utf8'); 20 | const ccp = JSON.parse(ccpJSON); 21 | 22 | // create car transaction 23 | exports.createCar = async function(key, make, model, color, owner) { 24 | let response = {}; 25 | try { 26 | 27 | // Create a new file system based wallet for managing identities. 28 | const walletPath = path.join(process.cwd(), '/wallet'); 29 | const wallet = new FileSystemWallet(walletPath); 30 | console.log(`Wallet path: ${walletPath}`); 31 | 32 | // Check to see if we've already enrolled the user. 33 | const userExists = await wallet.exists(userName); 34 | if (!userExists) { 35 | console.log('An identity for the user ' + userName + ' does not exist in the wallet'); 36 | console.log('Run the registerUser.js application before retrying'); 37 | response.error = 'An identity for the user ' + userName + ' does not exist in the wallet. Register ' + userName + ' first'; 38 | return response; 39 | } 40 | 41 | // Create a new gateway for connecting to our peer node. 42 | const gateway = new Gateway(); 43 | await gateway.connect(ccp, { wallet, identity: userName, discovery: gatewayDiscovery }); 44 | 45 | // Get the network (channel) our contract is deployed to. 46 | const network = await gateway.getNetwork('mychannel'); 47 | 48 | // Get the contract from the network. 49 | const contract = network.getContract('fabcar'); 50 | 51 | // Submit the specified transaction. 52 | // createCar transaction - requires 5 argument, ex: ('createCar', 'CAR12', 'Honda', 'Accord', 'Black', 'Tom') 53 | await contract.submitTransaction('createCar', key, make, model, color, owner); 54 | console.log('Transaction has been submitted'); 55 | 56 | // Disconnect from the gateway. 57 | await gateway.disconnect(); 58 | 59 | response.msg = 'createCar Transaction has been submitted'; 60 | return response; 61 | 62 | } catch (error) { 63 | console.error(`Failed to submit transaction: ${error}`); 64 | response.error = error.message; 65 | return response; 66 | } 67 | }; 68 | 69 | // change car owner transaction 70 | exports.changeCarOwner = async function(key, newOwner) { 71 | let response = {}; 72 | try { 73 | 74 | // Create a new file system based wallet for managing identities. 75 | const walletPath = path.join(process.cwd(), '/wallet'); 76 | const wallet = new FileSystemWallet(walletPath); 77 | console.log(`Wallet path: ${walletPath}`); 78 | 79 | // Check to see if we've already enrolled the user. 80 | const userExists = await wallet.exists(userName); 81 | if (!userExists) { 82 | console.log('An identity for the user ' + userName + ' does not exist in the wallet'); 83 | console.log('Run the registerUser.js application before retrying'); 84 | response.error = 'An identity for the user ' + userName + ' does not exist in the wallet. Register ' + userName + ' first'; 85 | return response; 86 | } 87 | 88 | // Create a new gateway for connecting to our peer node. 89 | const gateway = new Gateway(); 90 | await gateway.connect(ccp, { wallet, identity: userName, discovery: gatewayDiscovery }); 91 | 92 | // Get the network (channel) our contract is deployed to. 93 | const network = await gateway.getNetwork('mychannel'); 94 | 95 | // Get the contract from the network. 96 | const contract = network.getContract('fabcar'); 97 | 98 | // Submit the specified transaction. 99 | // changeCarOwner transaction - requires 2 args , ex: ('changeCarOwner', 'CAR10', 'Dave') 100 | await contract.submitTransaction('changeCarOwner', key, newOwner); 101 | console.log('Transaction has been submitted'); 102 | 103 | // Disconnect from the gateway. 104 | await gateway.disconnect(); 105 | 106 | response.msg = 'changeCarOwner Transaction has been submitted'; 107 | return response; 108 | 109 | } catch (error) { 110 | console.error(`Failed to submit transaction: ${error}`); 111 | response.error = error.message; 112 | return response; 113 | } 114 | }; 115 | 116 | // query all cars transaction 117 | exports.queryAllCars = async function() { 118 | 119 | let response = {}; 120 | try { 121 | console.log('queryAllCars'); 122 | 123 | // Create a new file system based wallet for managing identities. 124 | const walletPath = path.join(process.cwd(), '/wallet'); 125 | const wallet = new FileSystemWallet(walletPath); 126 | console.log(`Wallet path: ${walletPath}`); 127 | 128 | // Check to see if we've already enrolled the user. 129 | const userExists = await wallet.exists(userName); 130 | if (!userExists) { 131 | console.log('An identity for the user ' + userName + ' does not exist in the wallet'); 132 | console.log('Run the registerUser.js application before retrying'); 133 | response.error = 'An identity for the user ' + userName + ' does not exist in the wallet. Register ' + userName + ' first'; 134 | return response; 135 | } 136 | 137 | // Create a new gateway for connecting to our peer node. 138 | const gateway = new Gateway(); 139 | await gateway.connect(ccp, { wallet, identity: userName, discovery: gatewayDiscovery }); 140 | 141 | // Get the network (channel) our contract is deployed to. 142 | const network = await gateway.getNetwork('mychannel'); 143 | 144 | // Get the contract from the network. 145 | const contract = network.getContract('fabcar'); 146 | 147 | // Evaluate the specified transaction. 148 | // queryAllCars transaction - requires no arguments, ex: ('queryAllCars') 149 | const result = await contract.evaluateTransaction('queryAllCars'); 150 | //console.log('check6'); 151 | //console.log(`Transaction has been evaluated, result is: ${result.toString()}`); 152 | 153 | return result; 154 | 155 | } catch (error) { 156 | console.error(`Failed to evaluate transaction: ${error}`); 157 | response.error = error.message; 158 | return response; 159 | } 160 | }; 161 | 162 | // query the car identified by key 163 | exports.querySingleCar = async function(key) { 164 | 165 | let response = {}; 166 | try { 167 | console.log('querySingleCar'); 168 | 169 | // Create a new file system based wallet for managing identities. 170 | const walletPath = path.join(process.cwd(), '/wallet'); 171 | const wallet = new FileSystemWallet(walletPath); 172 | console.log(`Wallet path: ${walletPath}`); 173 | 174 | // Check to see if we've already enrolled the user. 175 | const userExists = await wallet.exists(userName); 176 | if (!userExists) { 177 | console.log('An identity for the user ' + userName + ' does not exist in the wallet'); 178 | console.log('Run the registerUser.js application before retrying'); 179 | response.error = 'An identity for the user ' + userName + ' does not exist in the wallet. Register ' + userName + ' first'; 180 | return response; 181 | } 182 | 183 | // Create a new gateway for connecting to our peer node. 184 | const gateway = new Gateway(); 185 | await gateway.connect(ccp, { wallet, identity: userName, discovery: gatewayDiscovery }); 186 | 187 | // Get the network (channel) our contract is deployed to. 188 | const network = await gateway.getNetwork('mychannel'); 189 | 190 | // Get the contract from the network. 191 | const contract = network.getContract('fabcar'); 192 | 193 | // Evaluate the specified transaction. 194 | // queryCar transaction - requires 1 argument, ex: 'querySingleCar('CAR0')' 195 | console.log(key); 196 | const result = await contract.evaluateTransaction('querySingleCar', key); 197 | //console.log(`Transaction has been evaluated, result is: ${result.toString()}`); 198 | 199 | return result; 200 | 201 | } catch (error) { 202 | console.error(`Failed to evaluate transaction: ${error}`); 203 | response.error = error.message; 204 | return response; 205 | } 206 | }; 207 | --------------------------------------------------------------------------------