diff --git a/package.json b/package.json index d156c9f..aad186e 100644 --- a/package.json +++ b/package.json @@ -31,4 +31,4 @@ ], "author": "Hyperledger Composer", "license": "Apache-2.0" -} \ No newline at end of file +} diff --git a/packages/vehicle-manufacture-network/.eslintignore b/packages/vehicle-manufacture-network/.eslintignore new file mode 100644 index 0000000..9083c5c --- /dev/null +++ b/packages/vehicle-manufacture-network/.eslintignore @@ -0,0 +1,5 @@ +coverage +dist +go +node_modules +out \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/.eslintrc.yml b/packages/vehicle-manufacture-network/.eslintrc.yml new file mode 100755 index 0000000..3438670 --- /dev/null +++ b/packages/vehicle-manufacture-network/.eslintrc.yml @@ -0,0 +1,45 @@ +env: + es6: true + node: true + mocha: true +extends: 'eslint:recommended' +parserOptions: + ecmaVersion: 8 + sourceType: + - script +rules: + indent: + - error + - 4 + linebreak-style: + - error + - unix + quotes: + - error + - single + semi: + - error + - always + no-unused-vars: + - error + - args: none + no-var: error + no-console: off + curly: error + eqeqeq: error + no-throw-literal: error + dot-notation: error + no-tabs: error + no-trailing-spaces: error + no-useless-call: error + no-with: error + operator-linebreak: error + require-jsdoc: + - error + - require: + ClassDeclaration: true + MethodDefinition: true + FunctionDeclaration: true + yoda: error + no-confusing-arrow: 2 + no-constant-condition: 2 diff --git a/packages/vehicle-manufacture-network/.gitignore b/packages/vehicle-manufacture-network/.gitignore new file mode 100644 index 0000000..6707743 --- /dev/null +++ b/packages/vehicle-manufacture-network/.gitignore @@ -0,0 +1,50 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# JSDoc +out + +# Mac files. +**/.DS_Store + +*.swp + +# Build generated files should be ignored by git, but not by npm. +dist + +node_modules \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/.npmignore b/packages/vehicle-manufacture-network/.npmignore new file mode 100644 index 0000000..74bbb1c --- /dev/null +++ b/packages/vehicle-manufacture-network/.npmignore @@ -0,0 +1,48 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# JSDoc +out + +# Mac files. +**/.DS_Store + +*.swp + +# Build generated files should be ignored by git, but not by npm. +# dist diff --git a/packages/vehicle-manufacture-network/README.md b/packages/vehicle-manufacture-network/README.md new file mode 100644 index 0000000..fc9c2dc --- /dev/null +++ b/packages/vehicle-manufacture-network/README.md @@ -0,0 +1,132 @@ +# Vehicle Manufacture Network + +> This network tracks the manufacture of vehicles from an initial order request through to their completion by the manufacturer. A regulator is able to provide oversight throughout this whole process. + +## Models within this business network + +### Participants +`Person` `Manufacturer` `Regulator` + +### Assets + +`Order` `Vehicle` + +### Transactions + +`PlaceOrder` `UpdateOrderStatus` `SetupDemo` + +### Events + +`PlaceOrderEvent` `UpdateOrderStatusEvent` + +## Example use of this business network +A `Person` uses a manufacturer's application to build their desired car and order it. The application submits a `PlaceOrder` transaction which creates a new `Order` asset containing the details of the vehicle the `Person` wishes to have made for them. The `Manufacturer` begins work on the car and as it proceeds through stages of production the `Manufacturer` submits `UpdateOrderStatus` transactions to mark the change in status for the `Order` e.g. updating the status from PLACED to SCHEDULED_FOR_MANUFACTURE. Once the `Manufacturer` has completed production for the `Order` they register the car by submitting an `UpdateOrderStatus` transaction with the status VIN_ASSIGNED (also providing the VIN to register against) and a `Vehicle` asset is formally added to the registry using the details specified in the `Order`. Once the car is registered then the `Manufacturer` submits an `UpdateOrderStatus` transaction with a status of OWNER_ASSIGNED at which point the owner field of the `Vehicle` is set to match the orderer field of the `Order`. The regulator would perform oversight over this whole process. + +## Testing this network within playground +Navigate to the **Test** tab and then submit a `SetupDemo` transaction: + +``` +{ + "$class": "org.acme.vehicle_network.SetupDemo" +} +``` + +This will generate three `Manufacturer` participants, fourteen `Person` participants, a single `Regulator` participant and thirteen `Vehicle` assets. + +Next order your car (an orange Arium Gamora) by submitting a PlaceOrder transaction: + +``` +{ + "$class": "org.acme.vehicle_network.PlaceOrder", + "orderId": "1234", + "vehicleDetails": { + "$class": "org.acme.vehicle_network.VehicleDetails", + "make": "resource:org.acme.vehicle_network.Manufacturer#Arium", + "modelType": "Gamora", + "colour": "Sunburst Orange" + }, + "options": { + "trim": "executive", + "interior": "red rum", + "extras": ["tinted windows", "extended warranty"] + }, + "orderer": "resource:org.acme.vehicle_network.Person#Paul" +} +``` + +This `PlaceOrder` transaction generates a new `Order` asset in the registry and emits a `PlaceOrderEvent` event. + +Now simulate the order being accepted by the manufacturer by submitting an `UpdateOrderStatus` transaction: + +``` +{ + "$class": "org.acme.vehicle_network.UpdateOrderStatus", + "orderStatus": "SCHEDULED_FOR_MANUFACTURE", + "order": "resource:org.acme.vehicle_network.Order#1234" +} +``` + +This `UpdateOrderStatus` transaction updates the orderStatus of the `Order` with orderId 1234 in the asset registry and emits an `UpdateOrderStatusEvent` event. + +Simulate the manufacturer registering the vehicle with the regulator by submitting an `UpdateOrderStatus` transaction: + +``` +{ + "$class": "org.acme.vehicle_network.UpdateOrderStatus", + "orderStatus": "VIN_ASSIGNED", + "order": "resource:org.acme.vehicle_network.Order#1234", + "vin": 'abc123' +} +``` + +This `UpdateOrderStatus` transaction updates the orderStatus of the `Order` with orderId 1234 in the asset registry, create a new `Vehicle` based of that `Order` in the asset registry and emits an `UpdateOrderStatusEvent` event. At this stage as the vehicle does not have an owner assigned to it, its status is declared as OFF_THE_ROAD. + +Next assign the owner of the vehicle using an `UpdateOrderStatus` transaction: + +``` +{ + "$class": "org.acme.vehicle_network.UpdateOrderStatus", + "orderStatus": "OWNER_ASSIGNED", + "order": "resource:org.acme.vehicle_network.Order#1234", + "vin": 'abc123' +} +``` + +This `UpdateOrderStatus` transaction updates the orderStatus of the `Order` with orderId 1234 in the asset registry, update the `Vehicle` asset with VIN abc123 to have an owner of Paul (who we intially marked as the orderer in the `PlaceOrder` transaction) and status of ACTIVE and also emits an `UpdateOrderStatusEvent` event. + +Finally complete the ordering process by marking the order as `DELIVERED` through submitting another `UpdateOrderStatus` transaction: + +``` +{ + "$class": "org.acme.vehicle_network.UpdateOrderStatus", + "orderStatus": "DELIVERED", + "order": "resource:org.acme.vehicle_network.Order#1234" +} +``` + +This `UpdateOrderStatus` transaction updates the orderStatus of the `Order` with orderId 1234 in the asset registry and emits an `UpdateOrderStatusEvent` event. + +This Business Network definition has been used to create demo applications that simulate the scenario outlined above. You can find more detail on these at https://github.com/hyperledger/composer-sample-applications/tree/master/packages/vehicle-manufacture + +## Permissions in this business network for modelled participants +Within this network permissions are outlines for the participants outlining what they can and can't do. The rules in the permissions.acl file explicitly ALLOW participants to perform actions. Actions not written for a participant in that file are blocked. +### Regulator +`RegulatorAdminUser` - Gives the regulator permission to perform ALL actions on ALL resources + +### Manufacturer +`ManufacturerUpdateOrder` - Allows a manufacturer to UPDATE an Order asset's data only using an UpdateOrderStatus transaction. The manufacturer must also be specified as the *vehicleDetails.make* in the Order asset. + +`ManufacturerUpdateOrderStatus` - Allows a manufacturer to CREATE and READ UpdateOrderStatus transactions that refer to an order that they are specified as the *vehicleDetails.make* in. + +`ManufacturerReadOrder` - Allows a manufacturer to READ an Order asset that they are specified as the *vehicleDetails.make* in. + +`ManufacturerCreateVehicle` - Allows a manufacturer to CREATE a vehicle asset only using a UpdateOrderStatus transaction. The transaction must have an *orderStatus* of VIN_ASSIGNED and the Order asset have the manufacturer specified as the *vehicleDetails.make*. + +`ManufacturerReadVehicle` - Allows a manufacturer to READ a Vehicle asset that they are specified as the *vehicleDetails.make* in. + +### Person +`PersonMakeOrder` - Gives the person permission to CREATE an Order asset only using a PlaceOrder transaction. The person must also be specified as the *orderer* in the Order asset. + +`PersonPlaceOrder` - Gives the person permission to CREATE and READ PlaceOrder transactions that refer to an order that they are specified as *orderer* in. + +`PersonReadOrder` - Gives the person permission to READ an Order asset that they are specified as the *orderer* in. \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/config/default.json b/packages/vehicle-manufacture-network/config/default.json new file mode 100644 index 0000000..8bad574 --- /dev/null +++ b/packages/vehicle-manufacture-network/config/default.json @@ -0,0 +1,4 @@ +{ + + "config":"" +} diff --git a/packages/vehicle-manufacture-network/header.txt b/packages/vehicle-manufacture-network/header.txt new file mode 100644 index 0000000..e1971c4 --- /dev/null +++ b/packages/vehicle-manufacture-network/header.txt @@ -0,0 +1,13 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/packages/vehicle-manufacture-network/index.js b/packages/vehicle-manufacture-network/index.js new file mode 100644 index 0000000..e1971c4 --- /dev/null +++ b/packages/vehicle-manufacture-network/index.js @@ -0,0 +1,13 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/packages/vehicle-manufacture-network/jsdoc.json b/packages/vehicle-manufacture-network/jsdoc.json new file mode 100644 index 0000000..ca000e4 --- /dev/null +++ b/packages/vehicle-manufacture-network/jsdoc.json @@ -0,0 +1,35 @@ +{ + "tags": { + "allowUnknownTags": true, + "dictionaries": ["jsdoc","closure"] + }, + "source": { + "include": [ + "./lib" + ], + "includePattern": ".+\\.js(doc|x)?$" + }, + "plugins": ["plugins/markdown"], + "templates": { + "logoFile": "", + "cleverLinks": false, + "monospaceLinks": false, + "dateFormat": "ddd MMM Do YYYY", + "outputSourceFiles": true, + "outputSourcePath": true, + "systemName": "Perishable Goods Network", + "footer": "", + "copyright": "Released under the Apache License v2.0", + "navType": "vertical", + "theme": "spacelab", + "linenums": true, + "collapseSymbols": false, + "inverseNav": true, + "protocol": "html://", + "methodHeadingReturns": false + }, + "markdown": { + "parser": "gfm", + "hardwrap": true + } +} diff --git a/packages/vehicle-manufacture-network/lib/script.js b/packages/vehicle-manufacture-network/lib/script.js new file mode 100644 index 0000000..2933802 --- /dev/null +++ b/packages/vehicle-manufacture-network/lib/script.js @@ -0,0 +1,244 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* global getFactory getAssetRegistry getParticipantRegistry emit */ + +// MANUFACTURER FUNCTIONS +/** + * Place an order for a vehicle + * @param {org.acme.vehicle_network.PlaceOrder} placeOrder - the PlaceOrder transaction + * @transaction + */ +async function placeOrder(orderRequest) { // eslint-disable-line no-unused-vars + console.log('placeOrder'); + + const factory = getFactory(); + const namespace = 'org.acme.vehicle_network'; + + const order = factory.newResource(namespace, 'Order', orderRequest.orderId); + order.vehicleDetails = orderRequest.vehicleDetails; + order.orderStatus = 'PLACED'; + order.orderer = factory.newRelationship(namespace, 'Person', orderRequest.orderer.getIdentifier()); + order.options = orderRequest.options; + + // save the order + const assetRegistry = await getAssetRegistry(order.getFullyQualifiedType()); + await assetRegistry.add(order); + + // emit the event + const placeOrderEvent = factory.newEvent(namespace, 'PlaceOrderEvent'); + placeOrderEvent.orderId = order.orderId; + placeOrderEvent.vehicleDetails = order.vehicleDetails; + placeOrderEvent.orderer = order.orderer; + emit(placeOrderEvent); +} + +/** + * Update the status of an order + * @param {org.acme.vehicle_network.UpdateOrderStatus} updateOrderStatus - the UpdateOrderStatus transaction + * @transaction + */ +async function updateOrderStatus(updateOrderRequest) { // eslint-disable-line no-unused-vars + console.log('updateOrderStatus'); + + const factory = getFactory(); + const namespace = 'org.acme.vehicle_network'; + + // get vehicle registry + const vehicleRegistry = await getAssetRegistry(namespace + '.Vehicle'); + if (updateOrderRequest.orderStatus === 'VIN_ASSIGNED') { + if (!updateOrderRequest.vin) { + throw new Error('Value for VIN was expected'); + } + // create a vehicle + const vehicle = factory.newResource(namespace, 'Vehicle', updateOrderRequest.vin ); + vehicle.vehicleDetails = updateOrderRequest.order.vehicleDetails; + vehicle.vehicleStatus = 'OFF_THE_ROAD'; + await vehicleRegistry.add(vehicle); + } else if(updateOrderRequest.orderStatus === 'OWNER_ASSIGNED') { + if (!updateOrderRequest.vin) { + throw new Error('Value for VIN was expected'); + } + + // assign the owner of the vehicle to be the person who placed the order + const vehicle = await vehicleRegistry.get(updateOrderRequest.vin); + vehicle.vehicleStatus = 'ACTIVE'; + vehicle.owner = factory.newRelationship(namespace, 'Person', updateOrderRequest.order.orderer.username); + await vehicleRegistry.update(vehicle); + } + + // update the order + const order = updateOrderRequest.order; + order.orderStatus = updateOrderRequest.orderStatus; + const orderRegistry = await getAssetRegistry(namespace + '.Order'); + await orderRegistry.update(order); + + // emit the event + const updateOrderStatusEvent = factory.newEvent(namespace, 'UpdateOrderStatusEvent'); + updateOrderStatusEvent.orderStatus = updateOrderRequest.order.orderStatus; + updateOrderStatusEvent.order = updateOrderRequest.order; + emit(updateOrderStatusEvent); +} + +// DEMO SETUP FUNCTIONS +/** + * Create the participants to use in the demo + * @param {org.acme.vehicle_network.SetupDemo} setupDemo - the SetupDemo transaction + * @transaction + */ +async function setupDemo() { // eslint-disable-line no-unused-vars + console.log('setupDemo'); + + const factory = getFactory(); + const namespace = 'org.acme.vehicle_network'; + + let people = ['Paul', 'Andy', 'Hannah', 'Sam', 'Caroline', 'Matt', 'Fenglian', 'Mark', 'James', 'Dave', 'Rob', 'Kai', 'Ellis', 'LesleyAnn']; + let manufacturers; + + const vehicles = { + 'Arium': { + 'Nova': [ + { + 'vin': 'ea290d9f5a6833a65', + 'colour': 'Royal Purple', + 'vehicleStatus': 'ACTIVE' + } + ], + 'Nebula': [ + { + 'vin': '39fd242c2bbe80f11', + 'colour': 'Statement Blue', + 'vehicleStatus': 'ACTIVE' + } + ] + }, + 'Morde': { + 'Putt': [ + { + 'vin': '835125e50bca37ca1', + 'colour': 'Black', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': '0812e6d8d486e0464', + 'colour': 'Red', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': 'c4aa418f26d4a0403', + 'colour': 'Silver', + 'vehicleStatus': 'ACTIVE' + } + ], + 'Pluto': [ + { + 'vin': '7382fbfc083f696e5', + 'colour': 'White', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': '01a9cd3f8f5db5ef7', + 'colour': 'Green', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': '97f305df4c2881e71', + 'colour': 'Grey', + 'vehicleStatus': 'ACTIVE' + } + ] + }, + 'Ridge': { + 'Cannon': [ + { + 'vin': 'af462063fb901d0e6', + 'colour': 'Red', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': '3ff3395ecfd38f787', + 'colour': 'White', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': 'de701fcf2a78d8086', + 'colour': 'Silver', + 'vehicleStatus': 'ACTIVE' + } + ], + 'Rancher': [ + { + 'vin': '2fcdd7b5131e81fd0', + 'colour': 'Blue', + 'vehicleStatus': 'ACTIVE' + }, + { + 'vin': '79540e5384c970321', + 'colour': 'White', + 'vehicleStatus': 'ACTIVE' + } + ] + } + }; + + // convert array names of people to be array of participant resources of type Person with identifier of that name + people = people.map(function (person) { + return factory.newResource(namespace, 'Person', person); + }); + + // create array of Manufacturer particpant resources identified by the top level keys in vehicles const + manufacturers = Object.keys(vehicles).map(function (manufacturer) { + const manufacturerResource = factory.newResource(namespace, 'Manufacturer', manufacturer); + manufacturerResource.name = manufacturer; + return manufacturerResource; + }); + + // create a Regulator participant resource + const regulator = factory.newResource(namespace, 'Regulator', 'VDA'); + regulator.name = 'VDA'; + + // add the regulator + const regulatorRegistry = await getParticipantRegistry(namespace + '.Regulator'); + await regulatorRegistry.add(regulator); + + // add the manufacturers + const manufacturerRegistry = await getParticipantRegistry(namespace + '.Manufacturer'); + await manufacturerRegistry.addAll(manufacturers); + + // add the persons + const personRegistry = await getParticipantRegistry(namespace + '.Person'); + await personRegistry.addAll(people); + + // add the vehicles + const vehicleRegistry = await getAssetRegistry(namespace + '.Vehicle'); + const vehicleResources = []; + + for (const manufacturer in vehicles) { + for (const model in vehicles[manufacturer]) { + const vehicconstemplatesForModel = vehicles[manufacturer][model]; + vehicconstemplatesForModel.forEach(function(vehicconstemplate) { + const vehicle = factory.newResource(namespace, 'Vehicle', vehicconstemplate.vin); + vehicle.owner = people[vehicleResources.length+1]; + vehicle.vehicleStatus = vehicconstemplate.vehicleStatus; + vehicle.vehicleDetails = factory.newConcept(namespace, 'VehicleDetails'); + vehicle.vehicleDetails.make = factory.newResource(namespace, 'Manufacturer', manufacturer); + vehicle.vehicleDetails.modelType = model; + vehicle.vehicleDetails.colour = vehicconstemplate.colour; + + vehicleResources.push(vehicle); + }); + } + } + await vehicleRegistry.addAll(vehicleResources); +} \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/models/org.acme.vehicle_network.cto b/packages/vehicle-manufacture-network/models/org.acme.vehicle_network.cto new file mode 100644 index 0000000..92799ff --- /dev/null +++ b/packages/vehicle-manufacture-network/models/org.acme.vehicle_network.cto @@ -0,0 +1,96 @@ +/** + * New model file + */ + +namespace org.acme.vehicle_network + +// BASE DEFINTIONS + +concept VehicleDetails { + --> Manufacturer make + o String modelType + o String colour +} + +abstract participant Company identified by companyId { + o String companyId + o String name +} + +participant Person identified by username { + o String username + o String email optional +} + + +// MANUFACTURE DEFINITIONS +participant Manufacturer extends Company { +} + +enum OrderStatus { + o PLACED + o SCHEDULED_FOR_MANUFACTURE + o VIN_ASSIGNED + o OWNER_ASSIGNED + o DELIVERED +} + +concept Options { + o String trim + o String interior + o String[] extras +} + +asset Order identified by orderId { + o String orderId + o VehicleDetails vehicleDetails + o OrderStatus orderStatus + o Options options + --> Person orderer +} + +transaction PlaceOrder { + o String orderId + o VehicleDetails vehicleDetails + o Options options + --> Person orderer +} + +event PlaceOrderEvent { + o String orderId + o VehicleDetails vehicleDetails + --> Person orderer +} + +transaction UpdateOrderStatus { + o OrderStatus orderStatus + o String vin optional + --> Order order +} + +event UpdateOrderStatusEvent { + o OrderStatus orderStatus + o Order order +} + + +// REGULATOR DEFINITIONS +participant Regulator extends Company { +} + +enum VehicleStatus { + o ACTIVE + o OFF_THE_ROAD + o SCRAPPED +} + +asset Vehicle identified by vin { + o String vin + o VehicleDetails vehicleDetails + o VehicleStatus vehicleStatus + --> Person owner optional +} + +// DEMO SPECIFIC DEFINITIONS +transaction SetupDemo { +} \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/networkimage.svg b/packages/vehicle-manufacture-network/networkimage.svg new file mode 100644 index 0000000..dc57ef9 --- /dev/null +++ b/packages/vehicle-manufacture-network/networkimage.svg @@ -0,0 +1 @@ +Asset 17 \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/networkimageanimated.svg b/packages/vehicle-manufacture-network/networkimageanimated.svg new file mode 100644 index 0000000..05de0b9 --- /dev/null +++ b/packages/vehicle-manufacture-network/networkimageanimated.svg @@ -0,0 +1 @@ +Asset 17 \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/package.json b/packages/vehicle-manufacture-network/package.json new file mode 100644 index 0000000..f612b6c --- /dev/null +++ b/packages/vehicle-manufacture-network/package.json @@ -0,0 +1,61 @@ +{ + "engines": { + "composer": "^0.17.0" + }, + "name": "vehicle-manufacture-network", + "description": "Vehicle manufacture network", + "networkImage": "https://hyperledger.github.io/composer-sample-networks/packages/vehicle-manufacture-network/networkimage.svg", + "networkImageanimated": "https://hyperledger.github.io/composer-sample-networks/packages/vehicle-manufacture-network/networkimageanimated.svg", + "version": "0.2.1", + "scripts": { + "prepublish": "mkdirp ./dist && composer archive create --sourceType dir --sourceName . -a ./dist/vehicle-manufacture-network.bna", + "pretest": "npm run lint", + "lint": "eslint .", + "postlint": "npm run licchk", + "licchk": "license-check", + "postlicchk": "npm run doc", + "doc": "jsdoc --pedantic --recurse -c jsdoc.json", + "test": "mocha -t 0 --recursive" + }, + "repository": { + "type": "git", + "url": "https://github.com/hyperledger/composer-sample-networks.git" + }, + "keywords": [ + "vehicle", + "manufacture", + "composer", + "composer-network" + ], + "author": "Hyperledger Composer", + "license": "Apache 2.0", + "devDependencies": { + "browserfs": "^1.2.0", + "chai-as-promised": "^6.0.0", + "chai": "^3.5.0", + "composer-admin": "^0.17.0-0", + "composer-cli": "^0.17.0-0", + "composer-client": "^0.17.0-0", + "composer-connector-embedded": "^0.17.0-0", + "eslint": "^3.6.1", + "istanbul": "^0.4.5", + "jsdoc": "^3.4.1", + "license-check": "^1.1.5", + "mkdirp": "^0.5.1", + "mocha": "^3.2.0", + "moment": "^2.17.1" + }, + "license-check-config": { + "src": [ + "**/*.js", + "!./coverage/**/*", + "!./node_modules/**/*", + "!./out/**/*", + "!./scripts/**/*" + ], + "path": "header.txt", + "blocking": true, + "logInfo": false, + "logError": true + } +} \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/permissions.acl b/packages/vehicle-manufacture-network/permissions.acl new file mode 100644 index 0000000..d9fa7fd --- /dev/null +++ b/packages/vehicle-manufacture-network/permissions.acl @@ -0,0 +1,107 @@ +rule PersonMakeOrder { + description: "Allow Persons to create and view orders" + participant(p): "org.acme.vehicle_network.Person" + operation: CREATE + resource(o): "org.acme.vehicle_network.Order" + transaction(tx): "org.acme.vehicle_network.PlaceOrder" + condition: (o.orderer.getIdentifier() == p.getIdentifier()) + action: ALLOW +} + +rule PersonPlaceOrder { + description: "Allow Persons to place orders and view they've done this" + participant(p): "org.acme.vehicle_network.Person" + operation: CREATE, READ + resource(o): "org.acme.vehicle_network.PlaceOrder" + condition: (o.orderer.getIdentifier() == p.getIdentifier()) + action: ALLOW +} + +rule PersonReadOrder { + description: "Allow Persons to place orders and view they've done this" + participant(p): "org.acme.vehicle_network.Person" + operation: READ + resource(o): "org.acme.vehicle_network.Order" + condition: (o.orderer.getIdentifier() == p.getIdentifier()) + action: ALLOW +} + +rule ManufacturerUpdateOrder { + description: "Allow manufacturers to view and update their own orders" + participant(m): "org.acme.vehicle_network.Manufacturer" + operation: UPDATE + resource(o): "org.acme.vehicle_network.Order" + transaction(tx): "org.acme.vehicle_network.UpdateOrderStatus" + condition: (o.vehicleDetails.make.getIdentifier() == m.getIdentifier()) + action: ALLOW +} + +rule ManufacturerUpdateOrderStatus { + description: "Allow manufacturers to update order statuses and view they've done this" + participant(m): "org.acme.vehicle_network.Manufacturer" + operation: CREATE, READ + resource(o): "org.acme.vehicle_network.UpdateOrderStatus" + condition: (o.order.vehicleDetails.make.getIdentifier() == m.getIdentifier()) + action: ALLOW +} + +rule ManufacturerReadOrder { + description: "Allow manufacturers to view and update their own orders" + participant(m): "org.acme.vehicle_network.Manufacturer" + operation: READ + resource(o): "org.acme.vehicle_network.Order" + condition: (o.vehicleDetails.make.getIdentifier() == m.getIdentifier()) + action: ALLOW +} + +rule ManufacturerCreateVehicles { + description: "Allow manufacturers to create and view their vehicles" + participant(m): "org.acme.vehicle_network.Manufacturer" + operation: CREATE + resource(v): "org.acme.vehicle_network.Vehicle" + transaction(tx): "org.acme.vehicle_network.UpdateOrderStatus" + condition: (v.vehicleDetails.make.getIdentifier() == m.getIdentifier() && tx.orderStatus == "VIN_ASSIGNED") + action: ALLOW +} + +rule ManufacturerReadVehicles { + description: "Allow manufacturers to create and view their vehicles" + participant(m): "org.acme.vehicle_network.Manufacturer" + operation: READ + resource(v): "org.acme.vehicle_network.Vehicle" + condition: (v.vehicleDetails.make.getIdentifier() == m.getIdentifier()) + action: ALLOW +} + +rule RegulatorAdminUser { + description: "Let the regulator do anything" + participant: "org.acme.vehicle_network.Regulator" + operation: ALL + resource: "**" + action: ALLOW +} + +rule ParticipantsSeeSelves { + description: "Let participants see themselves" + participant(p): "org.hyperledger.composer.system.Participant" + operation: ALL + resource(r): "org.hyperledger.composer.system.Participant" + condition: (r.getIdentifier() == p.getIdentifier()) + action: ALLOW +} + +rule NetworkAdminUser { + description: "Grant business network administrators full access to user resources" + participant: "org.hyperledger.composer.system.NetworkAdmin" + operation: ALL + resource: "**" + action: ALLOW +} + +rule System { + description: "Grant all full access to system resources" + participant: "org.**" + operation: ALL + resource: "org.hyperledger.composer.system.**" + action: ALLOW +} \ No newline at end of file diff --git a/packages/vehicle-manufacture-network/test/script.js b/packages/vehicle-manufacture-network/test/script.js new file mode 100644 index 0000000..b58b21b --- /dev/null +++ b/packages/vehicle-manufacture-network/test/script.js @@ -0,0 +1,328 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const AdminConnection = require('composer-admin').AdminConnection; +const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; +const BusinessNetworkDefinition = require('composer-common').BusinessNetworkDefinition; +const IdCard = require('composer-common').IdCard; +const MemoryCardStore = require('composer-common').MemoryCardStore; +const path = require('path'); + +const chai = require('chai'); +chai.should(); +chai.use(require('chai-as-promised')); + +const namespace = 'org.acme.vehicle_network'; +const orderId = '1000-1000-1000-1000'; +const vin = '1a2b3c4d5e6f7g8h9'; + +describe('Manufacture network', () => { + const cardStore = new MemoryCardStore(); + let adminConnection; + let businessNetworkConnection; + let factory; + + before(async () => { + // Embedded connection used for local testing + const connectionProfile = { + name: 'embedded', + 'x-type': 'embedded' + }; + // Embedded connection does not need real credentials + const credentials = { + certificate: 'FAKE CERTIFICATE', + privateKey: 'FAKE PRIVATE KEY' + }; + + // PeerAdmin identity used with the admin connection to deploy business networks + const deployerMetadata = { + version: 1, + userName: 'PeerAdmin', + roles: [ 'PeerAdmin', 'ChannelAdmin' ] + }; + + const deployerCard = new IdCard(deployerMetadata, connectionProfile); + deployerCard.setCredentials(credentials); + + const deployerCardName = 'PeerAdmin'; + adminConnection = new AdminConnection({ cardStore: cardStore }); + + await adminConnection.importCard(deployerCardName, deployerCard); + await adminConnection.connect(deployerCardName); + }); + + beforeEach(async () => { + businessNetworkConnection = new BusinessNetworkConnection({ cardStore: cardStore }); + + const adminUserName = 'admin'; + let adminCardName; + const businessNetworkDefinition = await BusinessNetworkDefinition.fromDirectory(path.resolve(__dirname, '..')); + + // Install the Composer runtime for the new business network + await adminConnection.install(businessNetworkDefinition.getName()); + + // Start the business network and configure an network admin identity + const startOptions = { + networkAdmins: [ + { + userName: adminUserName, + enrollmentSecret: 'adminpw' + } + ] + }; + const adminCards = await adminConnection.start(businessNetworkDefinition, startOptions); + + // Import the network admin identity for us to use + adminCardName = `${adminUserName}@${businessNetworkDefinition.getName()}`; + + await adminConnection.importCard(adminCardName, adminCards.get(adminUserName)); + + // Connect to the business network using the network admin identity + await businessNetworkConnection.connect(adminCardName); + + factory = businessNetworkConnection.getBusinessNetwork().getFactory(); + }); + + describe('PlaceOrder', () => { + it('should be able to place an order', async () => { + + // create the orderer + const orderer = factory.newResource(namespace, 'Person', 'Andy'); + + // create the manufacturer + const manufacturer = factory.newResource(namespace, 'Manufacturer', 'Arium'); + manufacturer.name = 'Arium'; + + // create the vehicle details for the order + const vehicleDetails = factory.newConcept(namespace, 'VehicleDetails'); + vehicleDetails.make = factory.newRelationship(namespace, 'Manufacturer', manufacturer.$identifier); + vehicleDetails.modelType = 'nova'; + vehicleDetails.colour = 'statement blue'; + + // create the order options + const options = factory.newConcept(namespace, 'Options'); + options.trim = 'executive'; + options.interior = 'rotor grey'; + options.extras = ['tinted windows', 'extended warranty']; + + // create the transactionObject + + const placeOrderTx = factory.newTransaction(namespace, 'PlaceOrder'); + placeOrderTx.orderId = orderId; + placeOrderTx.vehicleDetails = vehicleDetails; + placeOrderTx.options = options; + placeOrderTx.orderer = factory.newRelationship(namespace, 'Person', orderer.$identifier); + + const personRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Person'); + await personRegistry.add(orderer); + + const manufacturerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Manufacturer'); + await manufacturerRegistry.add(manufacturer); + + await businessNetworkConnection.submitTransaction(placeOrderTx); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + const order = await orderRegistry.get(placeOrderTx.orderId); + + order.orderId.should.deep.equal(placeOrderTx.orderId); + order.vehicleDetails.should.deep.equal(vehicleDetails); + order.options.should.deep.equal(options); + order.orderStatus.should.deep.equal('PLACED'); + order.orderer.should.deep.equal(placeOrderTx.orderer); + }); + }); + + describe('UpdateOrderStatus', () => { + let order; + let orderer; + let manufacturer; + let vehicleDetails; + beforeEach(async () => { + // create the orderer + orderer = factory.newResource(namespace, 'Person', 'Andy'); + + // create the manufacturer + manufacturer = factory.newResource(namespace, 'Manufacturer', 'Arium'); + manufacturer.name = 'Arium'; + + // create the vehicle details for the order + vehicleDetails = factory.newConcept(namespace, 'VehicleDetails'); + vehicleDetails.make = factory.newRelationship(namespace, 'Manufacturer', manufacturer.$identifier); + vehicleDetails.modelType = 'nova'; + vehicleDetails.colour = 'statement blue'; + + // create the order options + const options = factory.newConcept(namespace, 'Options'); + options.trim = 'executive'; + options.interior = 'rotor grey'; + options.extras = ['tinted windows', 'extended warranty']; + + // create the order to update + order = factory.newResource(namespace, 'Order', orderId); + order.vehicleDetails = vehicleDetails; + order.options = options; + order.orderStatus = 'PLACED'; + order.orderer = factory.newRelationship(namespace, 'Person', orderer.$identifier); + + const personRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Person'); + await personRegistry.add(orderer); + + const manufacturerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Manufacturer'); + await manufacturerRegistry.add(manufacturer); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + await orderRegistry.add(order); + }); + + it('should update the status of the order to SCHEDULED_FOR_MANUFACTURE', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'SCHEDULED_FOR_MANUFACTURE'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + + await businessNetworkConnection.submitTransaction(updateOrderStatusTx); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + const retrievedOrder = await orderRegistry.get(orderId); + + retrievedOrder.orderStatus.should.deep.equal(updateOrderStatusTx.orderStatus); + }); + + it('should update the status of the order to VIN_ASSIGNED and create a Vehicle', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'VIN_ASSIGNED'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + updateOrderStatusTx.vin = vin; + + await businessNetworkConnection.submitTransaction(updateOrderStatusTx); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + const retrievedOrder = await orderRegistry.get(orderId); + + retrievedOrder.orderStatus.should.deep.equal(updateOrderStatusTx.orderStatus); + + const vehicleRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Vehicle'); + const vehicle = await vehicleRegistry.get(vin); + + vehicle.vin.should.deep.equal(vin); + vehicle.vehicleDetails.should.deep.equal(vehicleDetails); + vehicle.vehicleStatus.should.deep.equal('OFF_THE_ROAD'); + }); + + it('should update the status of the order to OWNER_ASSIGNED and update the Vehicle', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'OWNER_ASSIGNED'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + updateOrderStatusTx.vin = vin; + + const vehicle = factory.newResource(namespace, 'Vehicle', vin); + vehicle.vehicleDetails = vehicleDetails; + vehicle.vehicleStatus = 'OFF_THE_ROAD'; + + const vehicleRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Vehicle'); + await vehicleRegistry.add(vehicle); + + await businessNetworkConnection.submitTransaction(updateOrderStatusTx); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + const retrievedOrder = await orderRegistry.get(orderId); + + retrievedOrder.orderStatus.should.deep.equal(updateOrderStatusTx.orderStatus); + + const retrievedVehicle = await vehicleRegistry.get(vin); + + retrievedVehicle.vin.should.deep.equal(vin); + retrievedVehicle.vehicleStatus.should.deep.equal('ACTIVE'); + retrievedVehicle.owner.should.deep.equal(order.orderer); + }); + + it('should update the status of the order to DELIVERED', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'DELIVERED'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + + await businessNetworkConnection.submitTransaction(updateOrderStatusTx); + + const orderRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Order'); + const retrievedOrder = await orderRegistry.get(orderId); + + retrievedOrder.orderStatus.should.deep.equal(updateOrderStatusTx.orderStatus); + }); + + it('should throw an error if the vin is not passed when orderStatus set to VIN_ASSIGNED', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'VIN_ASSIGNED'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + + return businessNetworkConnection.submitTransaction(updateOrderStatusTx).should.be.rejectedWith('Value for VIN was expected'); + }); + + it('should throw an error if the vin is not passed when orderStatus set to OWNER_ASSIGNED', async () => { + const updateOrderStatusTx = factory.newTransaction(namespace, 'UpdateOrderStatus'); + updateOrderStatusTx.orderStatus = 'OWNER_ASSIGNED'; + updateOrderStatusTx.order = factory.newRelationship(namespace, 'Order', order.$identifier); + + return businessNetworkConnection.submitTransaction(updateOrderStatusTx).should.be.rejectedWith('Value for VIN was expected'); + }); + }); + + describe('SetupDemo', () => { + it('should add specified participants and vehicles for the demo scenario', async () => { + const setupDemoTx = factory.newTransaction(namespace, 'SetupDemo'); + + const expectedPeopleNames = ['Paul', 'Andy', 'Hannah', 'Sam', 'Caroline', 'Matt', 'Fenglian', 'Mark', 'James', 'Dave', 'Rob', 'Kai', 'Ellis', 'LesleyAnn']; + const expectedPeople = expectedPeopleNames.map((expectedPerson) => { + return factory.newResource(namespace, 'Person', expectedPerson); + }); + + const expectedManufacturerNames = ['Arium', 'Morde', 'Ridge']; + const expectedManufacturers = expectedManufacturerNames.map((expectedManufacturer) => { + const manufacturer = factory.newResource(namespace, 'Manufacturer', expectedManufacturer); + manufacturer.name = expectedManufacturer; + return manufacturer; + }); + + const expectedVins = ['ea290d9f5a6833a65', '39fd242c2bbe80f11', '835125e50bca37ca1', '0812e6d8d486e0464', 'c4aa418f26d4a0403', '7382fbfc083f696e5', '01a9cd3f8f5db5ef7', '97f305df4c2881e71', 'af462063fb901d0e6', '3ff3395ecfd38f787', 'de701fcf2a78d8086', '2fcdd7b5131e81fd0', '79540e5384c970321']; + + await businessNetworkConnection.submitTransaction(setupDemoTx); + + const personRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Person'); + const people = await personRegistry.getAll(); + + people.length.should.deep.equal(14); + people.forEach((person) => { + expectedPeople.should.include(person); + }); + + const manufacturerRegistry = await businessNetworkConnection.getParticipantRegistry(namespace + '.Manufacturer'); + const manufacturers = await manufacturerRegistry.getAll(); + + manufacturers.length.should.deep.equal(3); + manufacturers.forEach((manufacturer) => { + expectedManufacturers.should.include(manufacturer); + }); + + const vehicleRegistry = await businessNetworkConnection.getAssetRegistry(namespace + '.Vehicle'); + const vehicles = await vehicleRegistry.getAll(); + + vehicles.length.should.deep.equal(13); + vehicles.forEach((vehicle) => { + expectedVins.should.include(vehicle.vin); + expectedManufacturerNames.should.include(vehicle.vehicleDetails.make.$identifier); + expectedPeopleNames.slice(1, 14).should.include(vehicle.owner.$identifier); // paul shouldn't have a vehicle + }); + }); + }); +}); \ No newline at end of file