.
72 |
73 | ## Example: "On the Record"
74 |
75 | "On the Record" is a simple logging app, wrapped as a messaging board.
76 |
77 |
78 |
79 |
80 |
81 | ### Use cases
82 |
83 | - Immutable logging of data
84 | - Notarization of data, text, emails
85 |
86 | ### Functionality
87 |
88 | #### Create assets
89 | - with arbitrary payload
90 | - and an unlimited amount
91 |
92 | #### Retrieve assets
93 | - that you currently own (like UTXO's)
94 | - by searching the asset data/payload
95 | - state indicator (in backlog vs. on bigchain)
96 |
97 | #### What this app doesn't provide
98 |
99 | - Proper user and key management
100 | - Transfer of assets
101 |
102 | ## Example: Share Trader
103 |
104 | Share Trader is a simple share allocation and trade app. Each square represents an asset that can be traded amongst accounts.
105 |
106 |
107 |
108 |
109 |
110 | ### Use cases
111 |
112 | - Reservation of tickets, seats in a concert/transport, ...
113 | - Trade of limited issued assets
114 |
115 | ### Functionality
116 |
117 | #### Create assets
118 | - assets are created following a structured payload
119 | - the amount is limited
120 |
121 | #### Transfer assets
122 | - easy transfer of assets between accounts by:
123 | - clicking on an account first. This will give the assets for that account
124 | - clicking on an asset of that account. Transfer actions will appear on the right side.
125 |
126 | #### Retrieve assets
127 | - that you currently own (like UTXO's)
128 | - all assets on bigchain
129 | - state indicator (blinks if asset has various owners)
130 |
131 | #### What this app doesn't provide
132 |
133 | - Proper user and key management
134 | - Proper signing of transfers
135 | - Proper search by payload
136 |
137 | ## Example: Interledger
138 |
139 | TODO
140 |
141 | ## Acknowledgements:
142 |
143 | Special thanks to the BigchainDB/ascribe.io team for their insights and code contributions:
144 |
145 | @r-marques, @vrde, @ttmc, @rhsimplex, @SohKai, @sbellem, @TimDaub
146 |
--------------------------------------------------------------------------------
/apps_config.py:
--------------------------------------------------------------------------------
1 | APPS = [
2 | {
3 | 'name': 'ontherecord',
4 | 'num_accounts': 3,
5 | 'num_assets': 0,
6 | 'ledger': 0,
7 | 'payload_func': (
8 | lambda x: {
9 | 'app': 'ontherecord',
10 | 'content': x
11 | }
12 | )
13 | },
14 | {
15 | 'name': 'sharetrader',
16 | 'num_accounts': 5,
17 | 'num_assets': 64,
18 | 'ledger': 0,
19 | 'payload_func': (
20 | lambda i: {
21 | 'app': 'sharetrader',
22 | 'content': {
23 | 'x': int(i / 8),
24 | 'y': int(i % 8)
25 | }
26 | }
27 | )
28 | },
29 | {
30 | 'name': 'interledger',
31 | 'accounts': [
32 | {
33 | 'name': 'alice',
34 | 'ledgers': [
35 | {
36 | 'id': 0,
37 | 'num_assets': 3
38 | }
39 | ]
40 | },
41 | {
42 | 'name': 'bob',
43 | 'ledgers': [
44 | {
45 | 'id': 1,
46 | 'num_assets': 3
47 | }
48 | ]
49 | },
50 | {
51 | 'name': 'chloe',
52 | 'ledgers': [
53 | {
54 | 'id': 0,
55 | 'num_assets': 3
56 | },
57 | {
58 | 'id': 1,
59 | 'num_assets': 3
60 | }
61 | ]
62 | }
63 | ],
64 | 'payload_func': (
65 | lambda x: {
66 | 'app': 'interledger',
67 | 'content': x
68 | }
69 | )
70 | }
71 | ]
72 |
--------------------------------------------------------------------------------
/client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | 'presets': ['react', 'es2015'],
3 | 'plugins': [
4 | 'transform-object-assign',
5 | 'transform-react-display-name',
6 | [ 'transform-runtime', {
7 | 'polyfill': false,
8 | 'regenerator': true
9 | } ]
10 | ],
11 | 'sourceMaps': true,
12 |
13 | 'env': {
14 | 'demo': {
15 | 'plugins': [
16 | [ 'react-transform', {
17 | 'transforms': [{
18 | 'transform': 'react-transform-hmr',
19 | 'imports': ['react'],
20 | 'locals': ['module']
21 | }]
22 | } ]
23 | ]
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/client/.eslintignore:
--------------------------------------------------------------------------------
1 | build/*
2 | dist/*
3 | node_modules/*
4 |
--------------------------------------------------------------------------------
/client/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "ascribe-react"
3 | }
4 |
--------------------------------------------------------------------------------
/client/app_index_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title || 'BigchainDB Examples' %>
9 |
10 |
11 | <% if (!htmlWebpackPlugin.options.PRODUCTION) { %>
12 |
19 | <% } %>
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/client/browserlist:
--------------------------------------------------------------------------------
1 | # Supported browsers by this project.
2 | # For format, see https://github.com/ai/browserslist
3 | Chrome >= 30
4 | Safari >= 6.1
5 | Firefox >= 35
6 | Opera >= 32
7 | Explorer >= 10
8 | iOS >= 8
9 | Android >= 2.3
10 | Last 2 versions
11 |
--------------------------------------------------------------------------------
/client/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | BigchainDB Examples
7 |
8 |
9 |
10 |
11 |
12 |
13 | Add window
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/demo/start.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | BigchainDB Examples
9 |
10 |
11 |
18 |
19 |
20 |
21 | BigchainDB Examples
22 | You can check these out!
23 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/client/demo/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | height: 100vh;
3 | margin: 0;
4 | overflow: hidden;
5 | }
6 |
7 | .button--add-frame {
8 | bottom: 15px;
9 | height: 45px;
10 | position: absolute;
11 | right: 15px;
12 | z-index: 100;
13 | }
14 |
15 | .button--back-frame {
16 | height: 25px;
17 | position: absolute;
18 | left: 10px;
19 | top: 10px;
20 | }
21 |
22 | .button--close-frame {
23 | height: 25px;
24 | position: absolute;
25 | right: 10px;
26 | top: 10px;
27 | }
28 |
29 | .frame-container {
30 | /* Floating frames containers left will allow any frames to be fluidly added or removed */
31 | float: left;
32 |
33 | position: relative;
34 | }
35 |
--------------------------------------------------------------------------------
/client/demo/windowing.js:
--------------------------------------------------------------------------------
1 | // ESLint doesn't provide a good way of turning of the ES6 features... so we'll have to do it
2 | // manually.
3 | /* eslint-disable no-var, prefer-arrow-callback, prefer-template, strict */
4 | /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
5 |
6 | 'use strict';
7 |
8 | /**
9 | * Windowing script to dynamically create, resize, and destroy iframe instances of the BigchainDB
10 | * examples React app. Up to four windows can be open at the same time with one window shown
11 | * initially.
12 | *
13 | * Targets an element with the id `add-frame-handler` to become the button for adding new frames.
14 | * All new frames will be appended into the body.
15 | */
16 | (function windowing() {
17 | var APP_SOURCE = './start.html';
18 | var ADD_FRAME_HANDLER_QUERY = '#add-frame-handler';
19 | var IFRAME_QUERY = 'iframe[src="' + APP_SOURCE + '"]';
20 | var INITIAL_WINDOWS = 1;
21 | var MAX_FRAMES = 4;
22 | var ORIG_DOCUMENT_TITLE = document.title;
23 |
24 | var addFrameHandler = document.querySelector(ADD_FRAME_HANDLER_QUERY);
25 | var frames = [];
26 | var nextFrameNum = 0;
27 |
28 | /** Functions **/
29 | function addFrame() {
30 | // Create a new iframe, decorate it some handlers for closing, etc, and wrap it in a
31 | // container
32 | var newFrameContainer = decorateFrameWithCapabilities(createFrame());
33 |
34 | frames.push(newFrameContainer);
35 | adjustFrameSizing(frames);
36 |
37 | if (frames.length === MAX_FRAMES) {
38 | addFrameHandler.disabled = true;
39 | }
40 |
41 | // Finally, push new window into DOM body
42 | document.body.appendChild(newFrameContainer, addFrameHandler);
43 | }
44 |
45 | // Adjust sizing of each frame based on the total number of frames
46 | function adjustFrameSizing(totalFrames) {
47 | // Size windows into a 2-column grid
48 | var numGridCells = totalFrames.length % 2 ? (totalFrames.length + 1) : totalFrames.length;
49 | var baseFrameHeight = 100 / (numGridCells / 2);
50 | var baseFrameWidth = 50;
51 | var baseFrameHeightPercentage = baseFrameHeight + '%';
52 | var baseFrameWidthPercentage = baseFrameWidth + '%';
53 |
54 | totalFrames.forEach(function resizeFrame(frame, ii) {
55 | var overflowWidthPercentage;
56 |
57 | if (ii === totalFrames.length - 1 && totalFrames.length % 2) {
58 | // When there are an odd number of frames, make the last frame overflow to cover
59 | // the leftover bottom area of the screen
60 | overflowWidthPercentage = (2 * baseFrameWidth) + '%';
61 |
62 | frame.style.height = baseFrameHeightPercentage;
63 | frame.style.width = overflowWidthPercentage;
64 | } else {
65 | frame.style.height = baseFrameHeightPercentage;
66 | frame.style.width = baseFrameWidthPercentage;
67 | }
68 | });
69 |
70 | // Remove iframe borders if only one frame is visible
71 | if (totalFrames.length === 1) {
72 | totalFrames[0].querySelector(IFRAME_QUERY).style.borderWidth = 0;
73 | } else {
74 | // Reset first frame's borders in case they were removed
75 | totalFrames[0].querySelector(IFRAME_QUERY).style.borderWidth = '';
76 | }
77 | }
78 |
79 | // Creates a new iframe
80 | function createFrame() {
81 | var frame = document.createElement('iframe');
82 | frame.id = getNextFrameId();
83 | frame.name = frame.id;
84 | frame.src = APP_SOURCE;
85 |
86 | // Frames are always 100% of their containers
87 | frame.height = '100%';
88 | frame.width = '100%';
89 |
90 | return frame;
91 | }
92 |
93 | // Wrap the iframe with a container, add back and close functionality, and attach event listeners
94 | function decorateFrameWithCapabilities(frame) {
95 | var container = document.createElement('div');
96 | var backButton = document.createElement('button');
97 | var closeButton = document.createElement('button');
98 |
99 | // Set up container
100 | container.className = 'frame-container';
101 |
102 | // Set up backButton
103 | backButton.className = 'button--back-frame';
104 | backButton.innerHTML = 'Back';
105 | backButton.onclick = function goBackInFrame() {
106 | // Only allow the back button to function if we're not on the start page
107 | if (frame.contentWindow.location.pathname !== APP_SOURCE.substring(1)) {
108 | frame.contentWindow.history.back();
109 | }
110 | };
111 |
112 | // Set up close button
113 | closeButton.className = 'button--close-frame';
114 | closeButton.innerHTML = 'Close window ' + frame.name.replace(/example-frame-/, '');
115 | closeButton.onclick = function closeFrame() {
116 | var removeIndex = frames.indexOf(container);
117 | var nextDevtoolFrame;
118 |
119 | if (removeIndex > -1) {
120 | frames.splice(removeIndex, 1);
121 | }
122 |
123 | // __REACT_DEVTOOLS_HOLDER__ holds the window of the iframe that is attached to the
124 | // devtools during development mode. If we are closing that window, attach the devtools
125 | // to the next iframe.
126 | // eslint-disable-next-line no-underscore-dangle
127 | if (window.__REACT_DEVTOOLS_HOLDER__ === frame.contentWindow) {
128 | nextDevtoolFrame = frames[0] && frames[0].querySelector(IFRAME_QUERY);
129 |
130 | if (nextDevtoolFrame) {
131 | attachFrameWithReactDevtools(nextDevtoolFrame);
132 | } else {
133 | detachCurrentFrameFromReactDevtools();
134 | }
135 | }
136 |
137 | // Remove the frame from the DOM, adjust remaining frames' sizes, and allow more windows
138 | // to be created
139 | container.parentNode.removeChild(container);
140 |
141 | adjustFrameSizing(frames);
142 | addFrameHandler.disabled = false;
143 | };
144 |
145 | // Set up frame listeners
146 | frame.onfocus = function frameOnFocus() {
147 | document.title = frame.contentDocument.title;
148 | };
149 | frame.onblur = function frameOnBlur() {
150 | document.title = ORIG_DOCUMENT_TITLE;
151 | };
152 |
153 | container.appendChild(backButton);
154 | container.appendChild(closeButton);
155 | container.appendChild(frame);
156 |
157 | return container;
158 | }
159 |
160 | // Gets next frame id
161 | function getNextFrameId() {
162 | return 'example-frame-' + (++nextFrameNum);
163 | }
164 |
165 | /**
166 | * Devtool utils
167 | *
168 | * Use __REACT_DEVTOOLS_HOLDER__ to determine which frame the devtool is attached to
169 | */
170 | // Deregister the current frame from React devtools
171 | function detachCurrentFrameFromReactDevtools() {
172 | /* eslint-disable no-underscore-dangle */
173 | if (window.__REACT_DEVTOOLS_HOLDER__) {
174 | window.__REACT_DEVTOOLS_HOLDER__.__REACT_DEVTOOLS_GLOBAL_HOOK__ = null;
175 | }
176 |
177 | window.__REACT_DEVTOOLS_HOLDER__ = null;
178 | /* eslint-enable no-underscore-dangle */
179 | }
180 |
181 | // Register frame with React devtools
182 | function attachFrameWithReactDevtools(frame) {
183 | /* eslint-disable no-underscore-dangle */
184 | // If the frame's hasn't loaded far enough yet to have a window, then we'll give up
185 | if (frame.contentWindow) {
186 | frame.contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__ = __REACT_DEVTOOLS_GLOBAL_HOOK__;
187 |
188 | window.__REACT_DEVTOOLS_HOLDER__ = frame.contentWindow;
189 | } else {
190 | console.error('Tried to attach React devtools to window: ' + frame.name + ' but ' +
191 | 'frame was not loaded yet. React devtools will not be available.');
192 | }
193 | /* eslint-enable no-underscore-dangle */
194 | }
195 |
196 | /** Initialization **/
197 | // Initialize initial iframe windows
198 | (function initializeWindows(numWindows) {
199 | var ii;
200 |
201 | for (ii = 0; ii < numWindows; ++ii) {
202 | addFrame();
203 | }
204 | }(INITIAL_WINDOWS));
205 |
206 | // Attach action listener to addFrameHandler
207 | addFrameHandler.onclick = addFrame;
208 | }());
209 |
--------------------------------------------------------------------------------
/client/interledger/js/app.js:
--------------------------------------------------------------------------------
1 | // Install necessary polyfills (see supported browsers) into global
2 | import 'core-js/es6';
3 | import 'core-js/stage/4';
4 | import 'isomorphic-fetch';
5 |
6 | import React from 'react';
7 | import ReactDOM from 'react-dom';
8 |
9 | import Interledger from './components/interledger';
10 |
11 | import '../../lib/css/scss/main.scss';
12 |
13 |
14 | const App = () => (
15 |
16 |
17 |
18 | );
19 |
20 | ReactDOM.render( , document.getElementById('bigchaindb-example-app'));
21 |
--------------------------------------------------------------------------------
/client/interledger/js/components/account_detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classnames from 'classnames';
4 | import { Col } from 'react-bootstrap/lib';
5 |
6 | import Assets from './assets';
7 |
8 |
9 | const AccountDetail = React.createClass({
10 | propTypes: {
11 | account: React.PropTypes.object,
12 | accountList: React.PropTypes.array,
13 | activeAccount: React.PropTypes.object,
14 | activeAsset: React.PropTypes.object,
15 | assetList: React.PropTypes.object,
16 | handleAssetClick: React.PropTypes.func,
17 | handleClick: React.PropTypes.func
18 | },
19 |
20 | render() {
21 | const {
22 | account,
23 | accountList,
24 | activeAccount,
25 | activeAsset,
26 | assetList,
27 | handleAssetClick,
28 | handleClick
29 | } = this.props;
30 |
31 | if (account && assetList && Array.isArray(assetList[account.vk])) {
32 | const assetListForAccount = assetList[account.vk];
33 | return (
34 |
37 |
38 |
39 | {account.name}
40 |
41 |
42 | {account.vk}
43 |
44 |
45 | {account.api}
46 |
47 |
54 |
55 |
56 | );
57 | }
58 | return null;
59 | }
60 | });
61 |
62 | export default AccountDetail;
63 |
--------------------------------------------------------------------------------
/client/interledger/js/components/asset_row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 | import classnames from 'classnames';
4 | import { safeInvoke } from 'js-utility-belt/es6';
5 |
6 | import AssetActionPanel from '../../../lib/js/react/components/asset_action_panel';
7 | import AssetDetail from '../../../lib/js/react/components/asset_detail';
8 |
9 | import inBacklog from '../../../lib/js/utils/bigchaindb/in_backlog';
10 |
11 |
12 | const AssetRow = React.createClass({
13 | propTypes: {
14 | account: React.PropTypes.object.isRequired,
15 | asset: React.PropTypes.object.isRequired,
16 | accountList: React.PropTypes.array,
17 | actionMap: React.PropTypes.object,
18 | handleAccountClick: React.PropTypes.func,
19 | handleAssetClick: React.PropTypes.func,
20 | isActive: React.PropTypes.bool
21 | },
22 |
23 | getDefaultProps() {
24 | return {
25 | actionMap: {
26 | 'single-owner-transfer': {
27 | actionName: 'ESCROW',
28 | actionMessage: 'Escrow asset with:',
29 | selectAccounts: true
30 | },
31 | 'multi-owner-execute': {
32 | actionName: 'EXECUTE',
33 | actionMessage: 'Execute escrow of asset:',
34 | selectAccounts: false
35 | },
36 | 'multi-owner-abort': {
37 | actionName: 'ABORT',
38 | actionMessage: 'Abort escrow of asset:',
39 | selectAccounts: false
40 | }
41 | }
42 | };
43 | },
44 |
45 | getInitialState() {
46 | return {
47 | connectors: null,
48 | expiresIn: null
49 | };
50 | },
51 |
52 | componentDidMount() {
53 | if (this.getOperation() !== 'transfer') {
54 | this.intervalId = window.setInterval(this.setExpiryTime, 1000);
55 | }
56 | },
57 |
58 | componentWillUnmount() {
59 | window.clearInterval(this.intervalId);
60 | },
61 |
62 | setExpiryTime() {
63 | const {
64 | asset
65 | } = this.props;
66 | const expires = moment.unix(parseFloat(asset.expiryTime));
67 | const expiresIn = moment.utc(expires.diff(moment.utc()));
68 | this.setState({
69 | expiresIn: expiresIn > 0 ? expiresIn : -1
70 | });
71 | },
72 |
73 | handleAssetClick() {
74 | const {
75 | account,
76 | handleAccountClick,
77 | asset,
78 | handleAssetClick
79 | } = this.props;
80 |
81 | safeInvoke(handleAssetClick, asset);
82 | safeInvoke(handleAccountClick, account);
83 | },
84 |
85 | handleDestinationAccountSelection(destinationAccount) {
86 | const {
87 | account,
88 | asset
89 | } = this.props;
90 |
91 | account.ledger.getConnectors().then((res) => {
92 | this.setState({
93 | connectors: res.connectors
94 | });
95 | });
96 |
97 | // quoting should happen here
98 | // const quotes = connectors.map((connector) => connector.getQuote(asset, destinationAccount));
99 | },
100 |
101 | handleActionClick(selectedAccount) {
102 | const {
103 | connectors
104 | } = this.state;
105 |
106 | const {
107 | account,
108 | asset
109 | } = this.props;
110 |
111 | const idToTransfer = {
112 | txid: asset.id,
113 | cid: 0
114 | };
115 |
116 | if (asset.type === 'single-owner') {
117 | const transfer = {
118 | account: selectedAccount.ledger.id === account.ledger.id ?
119 | selectedAccount : connectors[0],
120 | asset: idToTransfer,
121 | destinationAccount: selectedAccount,
122 | executionCondition: 'cc:0:3:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU:0',
123 | expiresAt: moment().unix() + 100
124 | };
125 |
126 | account.ledger.send(transfer);
127 | } else if (asset.type === 'multi-owner') {
128 | const transfer = {
129 | account,
130 | asset: idToTransfer
131 | };
132 | if (this.getOperation() === 'execute') {
133 | const conditionFulfillment = 'cf:0:';
134 | account.ledger.fulfillCondition(transfer, conditionFulfillment);
135 | } else {
136 | account.ledger.fulfillCondition(transfer);
137 | }
138 | }
139 | },
140 |
141 | getOperation() {
142 | const {
143 | account,
144 | asset
145 | } = this.props;
146 |
147 | let operation = 'transfer';
148 | if (asset.hasOwnProperty('executeCondition') &&
149 | account.vk === asset.executeCondition.public_key) {
150 | operation = 'execute';
151 | } else if (asset.hasOwnProperty('abortCondition') &&
152 | account.vk === asset.abortCondition.public_key) {
153 | operation = 'abort';
154 | }
155 | return operation;
156 | },
157 |
158 | render() {
159 | const {
160 | account,
161 | accountList,
162 | actionMap,
163 | asset,
164 | isActive
165 | } = this.props;
166 |
167 | const {
168 | expiresIn
169 | } = this.state;
170 |
171 | const assetInBacklog = inBacklog(asset);
172 | const operation = this.getOperation();
173 |
174 | let actionsPanel = null;
175 | if (isActive && accountList && !assetInBacklog) {
176 | const actionType = actionMap[`${asset.type}-${operation}`];
177 | actionsPanel = (
178 |
186 | );
187 | }
188 |
189 |
190 | let escrowDetails = null;
191 | if (expiresIn) {
192 | const isExpired = expiresIn === -1;
193 | escrowDetails = (
194 |
195 | {isExpired ? 'EXPIRED' : `Expires in ${expiresIn.format('HH:mm:ss')}`}
196 |
197 | );
198 | }
199 |
200 |
201 | return (
202 |
206 |
210 | {escrowDetails}
211 | {actionsPanel}
212 |
213 |
214 | );
215 | }
216 | });
217 |
218 | export default AssetRow;
219 |
--------------------------------------------------------------------------------
/client/interledger/js/components/assets.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import AssetRow from './asset_row';
4 | import Spinner from '../../../lib/js/react/components/spinner';
5 |
6 |
7 | const Assets = ({
8 | account,
9 | accountList,
10 | assetList,
11 | activeAsset,
12 | handleAccountClick,
13 | handleAssetClick
14 | }) => {
15 | if (assetList && assetList.length) {
16 | return (
17 |
18 | {assetList.map((asset) => {
19 | const isActive = !!activeAsset && activeAsset.id === asset.id;
20 |
21 | return (
22 |
30 | );
31 | })}
32 |
33 | );
34 | } else {
35 | return (
36 |
37 |
38 |
39 | );
40 | }
41 | };
42 |
43 | Assets.propTypes = {
44 | account: React.PropTypes.object,
45 | accountList: React.PropTypes.array,
46 | activeAsset: React.PropTypes.object,
47 | assetClasses: React.PropTypes.object,
48 | assetList: React.PropTypes.array,
49 | handleAccountClick: React.PropTypes.func,
50 | handleAssetClick: React.PropTypes.func
51 | };
52 |
53 | export default Assets;
54 |
--------------------------------------------------------------------------------
/client/interledger/js/components/interledger.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Navbar } from 'react-bootstrap/lib';
4 |
5 | import AccountList from '../../../lib/js/react/components/account_list';
6 | import AccountDetail from './account_detail';
7 |
8 | import AssetActions from '../../../lib/js/react/actions/asset_actions';
9 |
10 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection';
11 |
12 |
13 | const Interledger = React.createClass({
14 | propTypes: {
15 | // Injected through BigchainDBConnection
16 | accountList: React.PropTypes.array,
17 | activeAccount: React.PropTypes.object,
18 | activeAsset: React.PropTypes.object,
19 | assetList: React.PropTypes.object,
20 | handleAccountChange: React.PropTypes.func,
21 | handleAssetChange: React.PropTypes.func
22 | },
23 |
24 | fetchAssetList({ account }) {
25 | if (account) {
26 | AssetActions.fetchAssetList({
27 | account
28 | });
29 | }
30 | },
31 |
32 | render() {
33 | const {
34 | accountList,
35 | activeAccount,
36 | activeAsset,
37 | assetList,
38 | handleAccountChange,
39 | handleAssetChange
40 | } = this.props;
41 |
42 | return (
43 |
44 |
45 | Interledger
46 |
47 |
64 |
65 | );
66 | }
67 | });
68 |
69 | export default BigchainDBConnection(Interledger);
70 |
--------------------------------------------------------------------------------
/client/interledger/scss/custom_style.scss:
--------------------------------------------------------------------------------
1 | @import "../../lib/css/scss/variables";
2 |
3 | .interledger {
4 |
5 | .asset-container {
6 | border: 1px solid lighten($fg-color, 70%);
7 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
8 | margin-top: 1em;
9 |
10 | &:hover {
11 | border: 1px solid $ascribe-pink;
12 | cursor: pointer;
13 | }
14 |
15 | &.inBacklog {
16 | background: rgba(black, .05);
17 | &:hover {
18 | border: 1px solid lighten($ascribe-pink, 50%);
19 | cursor: default;
20 | }
21 | }
22 | }
23 |
24 | .asset-container-actions {
25 | margin-top: 1.7em
26 | }
27 |
28 | .asset-container-id {
29 | color: lighten($fg-color, 50%);
30 | }
31 |
32 | .asset-escrow-details {
33 | color: rgba(black, 0.8);
34 | font-size: 0.8em;
35 | font-style: italic;
36 | margin-bottom: -1em;
37 | margin-top: 1em;
38 | text-align: right;
39 |
40 | &.isExpired {
41 | color: rgba(red, 0.8);
42 | }
43 | }
44 |
45 | .card {
46 | overflow: visible;
47 | }
48 |
49 | .ledger-0 {
50 | color: darken($ascribe-blue, 10%);
51 | }
52 | .ledger-1 {
53 | color: $ascribe-pink;
54 | }
55 | .ledger-2 {
56 | color: $ascribe-dark-blue;
57 | }
58 | .ledger-3 {
59 | color: $ascribe-black;
60 | }
61 |
62 | #wrapper {
63 | margin-top: 80px;
64 | padding-left: 0 !important;
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/account.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | .list-row,
4 | .list-item {
5 | font-size: .9em;
6 | text-transform: uppercase;
7 | padding: 1em;
8 | background-color: $bg-color;
9 | border-bottom: 1px solid lighten($fg-color, 80%);
10 | color: lighten($fg-color, 30%);
11 | }
12 |
13 | .list-row {
14 | &:hover {
15 | color: $bg-color !important;
16 | .list-row-detail {
17 | color: $bg-color !important;
18 | }
19 | background-color: lighten($bg-color--hover, 30%);
20 | cursor: pointer;
21 | }
22 | }
23 |
24 | .list-row.active {
25 | color: $bg-color !important;
26 | .list-row-detail {
27 | color: $bg-color !important;
28 | }
29 | background-color: $bg-color--hover;
30 | }
31 |
32 | .list-row-name {
33 | font-weight: bold;
34 | }
35 |
36 | .list-row-detail {
37 | font-size: 0.8em;
38 | font-style: italic;
39 | color: lighten($fg-color, 50%);
40 | }
41 |
42 | .list-row-name,
43 | .list-row-detail {
44 | text-overflow: ellipsis;
45 | overflow: hidden;
46 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/asset.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 |
4 | .asset-container {
5 | border-bottom: 1px solid lighten($fg-color, 50%);
6 | border-left: 5px solid transparent;
7 | padding: 1em;
8 | }
9 |
10 | .asset-container-actions {
11 | margin-top: 1em
12 | }
13 |
14 | .asset-container-id,
15 | .asset-container-timestamp {
16 | font-size: 0.8em;
17 | text-transform: uppercase;
18 | }
19 |
20 | .asset-container-id {
21 | margin-bottom: 1em;
22 | overflow: hidden;
23 | font-size: 0.75em;
24 | font-style: italic;
25 | text-overflow: ellipsis;
26 | }
27 |
28 | .asset-container-timestamp {
29 | color: lighten($fg-color, 50%);
30 | margin-bottom: -.6em;
31 | text-align: right;
32 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/card.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | .card {
4 | max-width: $row--wide;
5 | background: $bg-color;
6 | box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.4);
7 | padding: 20px 40px;
8 | margin: 0 auto 40px;
9 | overflow: hidden;
10 |
11 | .desc {
12 | font-size: 35px;
13 | line-height: 150%;
14 | margin: 0 0 25px 0;
15 |
16 | @media (max-width: 600px) {
17 | font-size: 20px;
18 | }
19 |
20 | strong {
21 | background-color: black;
22 | color: $fg-color;
23 | padding: 4px 20px;
24 | }
25 | }
26 | }
27 |
28 | .card--summary {
29 | .desc {
30 | float: left;
31 | width: 60%;
32 | @media (max-width: 600px) {
33 | float: none;
34 | width: 100%;
35 | display: block;
36 | margin-left: auto;
37 | margin-right: auto;
38 | }
39 | }
40 |
41 | .preview {
42 | margin-top: -10px;
43 | float: right;
44 | width: 30%;
45 | max-width: 400px;
46 | @media (max-width: 600px) {
47 | float: none;
48 | width: 100%;
49 | margin-bottom: 30px;
50 | }
51 | }
52 | }
53 |
54 | .card {
55 | .disclaimer {
56 | padding-top: 30px;
57 | text-align: right;
58 | font-size: 12px;
59 | }
60 | }
61 |
62 | .card--claim {
63 | box-shadow: none;
64 | background-color: transparent;
65 | .desc {
66 | font-size: 20px;
67 | text-align: center;
68 | a {
69 | color: inherit;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/client/lib/css/scss/custom_bootstrap.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | .pagination {
4 | margin-right: 2em;
5 | }
6 | .pagination > li > a,
7 | .pagination > li > span {
8 | color: lighten($fg-color, 60%);
9 | }
10 |
11 | .pagination > li > a:hover,
12 | .pagination > li > span:hover {
13 | color: lighten($fg-color, 30%);
14 | border-color: lighten($fg-color, 40%);
15 | }
16 |
17 | .pagination > .active > a,
18 | .pagination > .active > a:hover,
19 | .pagination > .active > a:focus,
20 | .pagination > .active > span,
21 | .pagination > .active > span:hover,
22 | .pagination > .active > span:focus {
23 | background-color: $bg-color;
24 | border-color: lighten($fg-color, 30%);
25 | color: $fg-color;
26 | }
27 |
28 | .btn-default {
29 | border-radius: 0;
30 | border: 1px solid lighten($fg-color, 20%);
31 | margin: .2em;
32 | background: $bg-color;
33 | color: lighten($fg-color, 20%);
34 | }
35 |
36 | .btn-default:focus,
37 | .btn-default.focus,
38 | .btn-default:active,
39 | .btn-default.active,
40 | .btn-default.active:focus,
41 | .btn-default.active:hover,
42 | .btn-default:hover {
43 | background: lighten($fg-color, 20%);
44 | color: $bg-color;
45 | border: 1px solid $bg-color;
46 | }
47 |
48 | .btn-secondary{
49 | border-radius: 0;
50 | border: 1px solid $bg-color;
51 | margin: .2em;
52 | background: lighten($fg-color, 20%);
53 | color: $bg-color;
54 | }
55 |
56 | .label-primary {
57 | background-color: lighten($fg-color, 40%);
58 | color: $bg-color !important;
59 | border: 1px solid lighten($fg-color, 10%);
60 | margin-right: .3em;
61 | max-width: 100%;
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | overflow: hidden;
65 | display: inline-block;
66 | padding: .5em 0.8em .4em 0.5em
67 | }
68 |
69 | .dropdown-menu {
70 | > li > a {
71 | color: $fg-color;
72 | }
73 | background-color: $bg-color;
74 | }
75 |
76 | .dropdown-menu > li > a:hover {
77 | color: $bg-color;
78 | background-color: $bg-color--hover;
79 | cursor: pointer;
80 | }
81 |
82 | .btn-group.open .dropdown-toggle {
83 | box-shadow: none;
84 | }
85 |
86 | .open > .btn-default.dropdown-toggle:hover,
87 | .open > .btn-default.dropdown-toggle:focus,
88 | .open > .btn-default.dropdown-toggle.focus{
89 | color: $fg-color;
90 | background-color: lighten($bg-color, 7%);
91 | border: 1px solid $fg-color;
92 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/main.scss:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Lato:300);
2 | // TODO: this should be removed
3 | @import url(https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css);
4 |
5 | @import './style';
6 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
7 | @import "~bootstrap-sass/assets/stylesheets/bootstrap";
8 | @import 'custom_bootstrap';
9 | @import 'normalize.css';
10 | @import 'account';
11 | @import 'asset';
12 | @import 'card';
13 | @import 'search';
14 | @import 'sidebar';
15 | @import 'spinner';
16 | @import 'style';
17 | @import '../../../on_the_record/scss/custom_style';
18 | @import '../../../share_trader/scss/custom_style';
19 | @import '../../../interledger/scss/custom_style';
20 |
--------------------------------------------------------------------------------
/client/lib/css/scss/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability of focused elements when they are also in an
95 | * active/hover state.
96 | */
97 |
98 | a:active,
99 | a:hover {
100 | outline: 0;
101 | }
102 |
103 | /* Text-level semantics
104 | ========================================================================== */
105 |
106 | /**
107 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
108 | */
109 |
110 | abbr[title] {
111 | border-bottom: 1px dotted;
112 | }
113 |
114 | /**
115 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
116 | */
117 |
118 | b,
119 | strong {
120 | font-weight: bold;
121 | }
122 |
123 | /**
124 | * Address styling not present in Safari and Chrome.
125 | */
126 |
127 | dfn {
128 | font-style: italic;
129 | }
130 |
131 | /**
132 | * Address variable `h1` font-size and margin within `section` and `article`
133 | * contexts in Firefox 4+, Safari, and Chrome.
134 | */
135 |
136 | h1 {
137 | font-size: 2em;
138 | margin: 0.67em 0;
139 | }
140 |
141 | /**
142 | * Address styling not present in IE 8/9.
143 | */
144 |
145 | mark {
146 | background: #ff0;
147 | color: #000;
148 | }
149 |
150 | /**
151 | * Address inconsistent and variable font size in all browsers.
152 | */
153 |
154 | small {
155 | font-size: 80%;
156 | }
157 |
158 | /**
159 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
160 | */
161 |
162 | sub,
163 | sup {
164 | font-size: 75%;
165 | line-height: 0;
166 | position: relative;
167 | vertical-align: baseline;
168 | }
169 |
170 | sup {
171 | top: -0.5em;
172 | }
173 |
174 | sub {
175 | bottom: -0.25em;
176 | }
177 |
178 | /* Embedded content
179 | ========================================================================== */
180 |
181 | /**
182 | * Remove border when inside `a` element in IE 8/9/10.
183 | */
184 |
185 | img {
186 | border: 0;
187 | }
188 |
189 | /**
190 | * Correct overflow not hidden in IE 9/10/11.
191 | */
192 |
193 | svg:not(:root) {
194 | overflow: hidden;
195 | }
196 |
197 | /* Grouping content
198 | ========================================================================== */
199 |
200 | /**
201 | * Address margin not present in IE 8/9 and Safari.
202 | */
203 |
204 | figure {
205 | margin: 1em 40px;
206 | }
207 |
208 | /**
209 | * Address differences between Firefox and other browsers.
210 | */
211 |
212 | hr {
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
354 | */
355 |
356 | input[type="search"] {
357 | -webkit-appearance: textfield; /* 1 */
358 | box-sizing: content-box; /* 2 */
359 | }
360 |
361 | /**
362 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
363 | * Safari (but not Chrome) clips the cancel button when the search input has
364 | * padding (and `textfield` appearance).
365 | */
366 |
367 | input[type="search"]::-webkit-search-cancel-button,
368 | input[type="search"]::-webkit-search-decoration {
369 | -webkit-appearance: none;
370 | }
371 |
372 | /**
373 | * Define consistent border, margin, and padding.
374 | */
375 |
376 | fieldset {
377 | border: 1px solid #c0c0c0;
378 | margin: 0 2px;
379 | padding: 0.35em 0.625em 0.75em;
380 | }
381 |
382 | /**
383 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
384 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
385 | */
386 |
387 | legend {
388 | border: 0; /* 1 */
389 | padding: 0; /* 2 */
390 | }
391 |
392 | /**
393 | * Remove default vertical scrollbar in IE 8/9/10/11.
394 | */
395 |
396 | textarea {
397 | overflow: auto;
398 | }
399 |
400 | /**
401 | * Don't inherit the `font-weight` (applied by a rule above).
402 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
403 | */
404 |
405 | optgroup {
406 | font-weight: bold;
407 | }
408 |
409 | /* Tables
410 | ========================================================================== */
411 |
412 | /**
413 | * Remove most spacing between table cells.
414 | */
415 |
416 | table {
417 | border-collapse: collapse;
418 | border-spacing: 0;
419 | }
420 |
421 | td,
422 | th {
423 | padding: 0;
424 | }
425 |
--------------------------------------------------------------------------------
/client/lib/css/scss/search.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | .searchbar {
4 | margin: 0 auto;
5 | }
6 |
7 | .searchbar input[type="url"] {
8 | width: 80%;
9 | display: inline-block;
10 | @media (max-width: 480px) {
11 | width: 70%;
12 | }
13 | }
14 |
15 | .searchbar input[type="submit"] {
16 | width: calc(20% - 15px);
17 | padding-left: 0;
18 | padding-right: 0;
19 | margin-left: 15px;
20 | margin-right: 0;
21 | display: inline-block;
22 | @media (max-width: 480px) {
23 | width: 30%;
24 | margin-left: 0px;
25 | }
26 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/sidebar.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 |
4 | #wrapper {
5 | padding-left: $sidebar--wide;
6 | transition: all 0.4s ease 0s;
7 | margin-top: $navbar-height;
8 | }
9 |
10 | #sidebar-wrapper {
11 | margin-left: -$sidebar--wide;
12 | left: $sidebar--wide;
13 | width: $sidebar--wide;
14 | height: 100%;
15 | overflow-y: auto;
16 | z-index: 1200;
17 | transition: all 0.4s ease 0s;
18 | }
19 |
20 | .sidebar-nav {
21 | position: fixed;
22 | top: $navbar-height;
23 | width: $sidebar--wide;
24 | margin: 0;
25 | padding: 2em 0;
26 | z-index: 1100;
27 | height: 100vh;
28 | border-right: 1px solid #888888;
29 | ul {
30 | list-style: none;
31 | padding: 0;
32 | }
33 | .dropdown-menu {
34 | overflow-y: scroll;
35 | max-height: 50vh;
36 | }
37 |
38 | }
39 |
40 | @media (max-width: 930px) {
41 |
42 | #wrapper {
43 | padding-left: 0;
44 | }
45 |
46 | #sidebar-wrapper {
47 | left: 0;
48 | }
49 |
50 | #wrapper.active {
51 | position: relative;
52 | left: $sidebar--wide;
53 | }
54 |
55 | #wrapper.active #sidebar-wrapper {
56 | left: $sidebar--wide;
57 | width: $sidebar--wide;
58 | transition: all 0.4s ease 0s;
59 | }
60 |
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/client/lib/css/scss/spinner.scss:
--------------------------------------------------------------------------------
1 | @import 'variables';
2 |
3 |
4 | [class^="spinner-wrapper-"]{
5 | margin: auto;
6 | }
7 |
8 | .spinner-wrapper-blue {
9 | color: $ascribe-blue;
10 | .spinner-circle {
11 | border-color: $ascribe-blue;
12 | }
13 | }
14 |
15 | .spinner-wrapper-dark-blue {
16 | color: $ascribe-dark-blue;
17 | .spinner-circle {
18 | border-color: $ascribe-dark-blue;
19 | }
20 | }
21 |
22 | .spinner-wrapper-pink {
23 | color: $ascribe-pink;
24 | .spinner-circle {
25 | border-color: $ascribe-pink;
26 | }
27 | }
28 |
29 | .spinner-wrapper-loop {
30 | -webkit-animation: spinner-color-loop 20s infinite;
31 | -moz-animation: spinner-color-loop 20s infinite;
32 | -o-animation: spinner-color-loop 20s infinite;
33 | -ms-animation: spinner-color-loop 20s infinite;
34 | animation: spinner-color-loop 20s;
35 |
36 | }
37 |
38 | .spinner-wrapper-black {
39 | color: $ascribe-black;
40 | .spinner-circle {
41 | border-color: $ascribe-black;
42 | }
43 | }
44 |
45 | .spinner-wrapper-white {
46 | color: $ascribe-white;
47 | .spinner-circle {
48 | border-color: $ascribe-white;
49 | }
50 | }
51 |
52 | .spinner-wrapper-lg {
53 | width: $ascribe--spinner-size-lg;
54 | height: $ascribe--spinner-size-lg;
55 | }
56 |
57 | .spinner-wrapper-md {
58 | width: $ascribe--spinner-size-md;
59 | height: $ascribe--spinner-size-md;
60 | }
61 |
62 | .spinner-wrapper-sm {
63 | width: $ascribe--spinner-size-sm;
64 | height: $ascribe--spinner-size-sm;
65 | }
66 |
67 | .spinner-circle {
68 | border-radius: 50%;
69 | border-style: solid;
70 |
71 | -webkit-animation: spin 1s infinite linear;
72 | -moz-animation: spin 1s infinite linear;
73 | -o-animation: spin 1s infinite linear;
74 | -ms-animation: spin 1s infinite linear;
75 | animation: spin 1s infinite linear;
76 | }
77 | .spinner-wrapper-lg .spinner-inner,
78 | .spinner-wrapper-lg .spinner-circle {
79 | width: $ascribe--spinner-size-lg;
80 | height: $ascribe--spinner-size-lg;
81 | }
82 |
83 | .spinner-wrapper-md .spinner-inner,
84 | .spinner-wrapper-md .spinner-circle {
85 | width: $ascribe--spinner-size-md;
86 | height: $ascribe--spinner-size-md;
87 | }
88 |
89 | .spinner-wrapper-sm .spinner-inner,
90 | .spinner-wrapper-sm .spinner-circle {
91 | width: $ascribe--spinner-size-sm;
92 | height: $ascribe--spinner-size-sm;
93 | }
94 |
95 | .spinner-wrapper-lg .spinner-circle,
96 | .spinner-wrapper-sm .spinner-circle,
97 | .spinner-wrapper-md .spinner-circle {
98 | border-width: 1px 1px 1px 0;
99 | }
100 |
101 | //.spinner-inner {
102 | // position: relative;
103 | // text-align: center;
104 | //}
105 |
106 | .spinner-inner {
107 | display: none;
108 | }
109 |
110 | .spinner-wrapper-lg .spinner-inner {
111 | font-size: $ascribe--spinner-size-lg;
112 | line-height: $ascribe--spinner-size-lg;
113 | top: -52px;
114 | }
115 |
116 | .spinner-wrapper-md .spinner-inner {
117 | font-size: $ascribe--spinner-size-md;
118 | line-height: $ascribe--spinner-size-md;
119 | top: -34px;
120 | }
121 |
122 | .spinner-wrapper-sm .spinner-inner {
123 | font-size: $ascribe--spinner-size-sm;
124 | line-height: $ascribe--spinner-size-sm;
125 | top: -15px;
126 | }
127 |
128 | @-webkit-keyframes spin {
129 | from {-webkit-transform: rotate(0deg);}
130 | to {-webkit-transform: rotate(359deg);}
131 | }
132 |
133 | @-moz-keyframes spin {
134 | from {-moz-transform: rotate(0deg);}
135 | to {-moz-transform: rotate(359deg);}
136 | }
137 |
138 | @-o-keyframes spin {
139 | from {-o-transform: rotate(0deg);}
140 | to {-o-transform: rotate(359deg);}
141 | }
142 |
143 | @keyframes spin{
144 | from {transform: rotate(0deg);}
145 | to {transform: rotate(359deg);}
146 | }
--------------------------------------------------------------------------------
/client/lib/css/scss/style.scss:
--------------------------------------------------------------------------------
1 | @import "variables";
2 |
3 | /**
4 | * Colors and backgrounds
5 | */
6 |
7 | $row: 100%;
8 | $row--wide: 900px;
9 | $gutter: 20px;
10 | $small: 12px;
11 |
12 | $color--brand: #c90000;
13 | $color--background-secondary: #EEE;
14 |
15 | html {
16 | font-family: 'Lato', sans-serif;
17 | }
18 |
19 | body {
20 | background-color: $bg-color;
21 | }
22 |
23 | .app {
24 | margin: auto;
25 | //max-width: $content--max-width + $sidebar--wide;
26 | height: 100%;
27 | }
28 |
29 | a {
30 | color: $fg-color;
31 | }
32 |
33 | #page-content-wrapper {
34 | width: 100%;
35 | }
36 |
37 | .page-content {
38 | padding: 0 1em;
39 | }
40 |
41 | .content-text {
42 | margin-top: 2em;
43 | text-align: center;
44 | }
45 |
46 | .hero,
47 | .page--result {
48 | background-color: $color--background-secondary;
49 | }
50 |
51 | .logo-brand {
52 | > span:first-of-type {
53 | font-weight: 700;
54 | }
55 | }
56 |
57 | .navbar-fixed-bottom {
58 | margin-left: $sidebar--wide;
59 | }
60 |
61 | @media (max-width: 930px) {
62 | .navbar-fixed-bottom {
63 | margin-left: 0;
64 | }
65 | }
66 |
67 | /**
68 | * Layout
69 | */
70 |
71 | .row {
72 | max-width: $row;
73 | margin-left: auto;
74 | margin-right: auto;
75 | }
76 |
77 | .vertical-align-outer {
78 | position: absolute;
79 | display: table;
80 | width: 100%;
81 | height: 100%;
82 | }
83 |
84 | .vertical-align-inner {
85 | display: table-cell;
86 | vertical-align: middle;
87 | }
88 |
89 | input {
90 | width: 100%;
91 | font-size: 20px;
92 | line-height: 130%;
93 | border: 1px solid #BBB;
94 | padding: 1em;
95 | }
96 |
97 | #app-container {
98 | margin-top: 50vh;
99 | text-align: center;
100 | background: $fg-color;
101 | color: black;
102 | }
103 |
104 | #container {
105 | background-color: $color--background;
106 | }
107 |
108 | .content-header-wrapper {
109 | background-color: $bg-color;
110 | width: 100%;
111 | }
112 |
113 | .content-header {
114 | width: 100%;
115 | max-width: $content--max-width;
116 | position: fixed;
117 | background-color: $bg-color;
118 | height: 70px;
119 | box-shadow: 0 2px 2px -2px rgba(0, 0, 0, .5);
120 | z-index: 1200;
121 | }
122 |
--------------------------------------------------------------------------------
/client/lib/css/scss/variables.scss:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Colors and backgrounds
4 | */
5 |
6 | /*79589F*/
7 | $ascribe-black: #222;
8 | $ascribe-dark-blue: #003C69;
9 | $ascribe-blue: #65CFE9;
10 | $ascribe-light-blue: #D3DEE4;
11 | $ascribe-pink: #D10074;
12 | $ascribe-white: white;
13 |
14 | $color--brand: #c90000;
15 | $color--background: #FAFAFA;
16 | $color--background-secondary: #FAFAFA;
17 |
18 | $bg-color: #FAFAFA;
19 | $fg-color: black;
20 | $bg-color--hover: lighten($fg-color, 20%);
21 |
22 | /**
23 | * Sizes
24 | */
25 |
26 | $ascribe--spinner-color: $ascribe-blue;
27 |
28 | $ascribe--spinner-size-lg: 50px;
29 | $ascribe--spinner-size-md: 30px;
30 | $ascribe--spinner-size-sm: 15px;
31 |
32 | $navbar-height: 70px;
33 | $header-height: 70px;
34 |
35 | $row: 700px;
36 | $row--wide: 900px;
37 | $gutter: 20px;
38 | $small: 12px;
39 |
40 | $content--max-width: 1100px;
41 | $sidebar--wide: 250px;
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/client/lib/js/constants/api_urls.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-template */
2 | import { API_PATH } from './application_constants';
3 |
4 |
5 | const ApiUrls = {
6 | 'accounts': API_PATH + 'accounts/',
7 | 'accounts_detail': API_PATH + 'accounts/%(accountId)s/',
8 | 'assets_for_account': API_PATH + 'accounts/%(accountId)s/assets/',
9 | 'assets': API_PATH + 'assets/',
10 | 'assets_detail': API_PATH + 'assets/%(assetId)s/',
11 | 'assets_transfer': API_PATH + 'assets/%(assetId)s/%(cid)s/transfer/',
12 | 'assets_escrow': API_PATH + 'assets/%(assetId)s/%(cid)s/escrow/',
13 | 'assets_escrow_fulfill': API_PATH + 'assets/%(assetId)s/%(cid)s/escrow/fulfill/'
14 | };
15 |
16 |
17 | export default ApiUrls;
18 |
--------------------------------------------------------------------------------
/client/lib/js/constants/application_constants.js:
--------------------------------------------------------------------------------
1 | export const FLASK_BASE_URL = process.env.FLASK_BASE_URL;
2 | export const API_PATH = `${FLASK_BASE_URL}/api/`;
3 |
4 | export default {
5 | API_PATH,
6 | FLASK_BASE_URL,
7 | };
8 |
--------------------------------------------------------------------------------
/client/lib/js/plugins/ledger_utils.js:
--------------------------------------------------------------------------------
1 | import BigchainDBLedgerPlugin from 'ilp-plugin-bigchaindb';
2 |
3 | const connectToBigchainDBLedger = (account) => {
4 | const ledgerPlugin = new BigchainDBLedgerPlugin({
5 | auth: {
6 | account: {
7 | id: account.vk,
8 | key: account.sk,
9 | uri: {
10 | api: `http://${account.ledger.api}`,
11 | ws: `ws://${account.ledger.ws}/users/${account.vk}`
12 | }
13 | }
14 | },
15 | ledgerId: account.ledger.id
16 | });
17 |
18 | ledgerPlugin.connect().catch(console.error);
19 | return ledgerPlugin;
20 | };
21 |
22 |
23 | export default connectToBigchainDBLedger;
24 |
--------------------------------------------------------------------------------
/client/lib/js/react/actions/account_actions.js:
--------------------------------------------------------------------------------
1 | import alt from '../alt';
2 |
3 |
4 | class AccountActions {
5 | constructor() {
6 | this.generateActions(
7 | 'flushAccount',
8 | 'fetchAccount',
9 | 'successFetchAccount',
10 | 'flushAccountList',
11 | 'fetchAccountList',
12 | 'successFetchAccountList',
13 | 'errorAccount',
14 | 'postAccount',
15 | 'successPostAccount'
16 | );
17 | }
18 | }
19 |
20 | export default alt.createActions(AccountActions);
21 |
--------------------------------------------------------------------------------
/client/lib/js/react/actions/asset_actions.js:
--------------------------------------------------------------------------------
1 | import alt from '../alt';
2 |
3 |
4 | class AssetActions {
5 | constructor() {
6 | this.generateActions(
7 | 'flushAsset',
8 | 'fetchAsset',
9 | 'successFetchAsset',
10 | 'transferAsset',
11 | 'escrowAsset',
12 | 'fulfillEscrowAsset',
13 | 'flushAssetList',
14 | 'fetchAssetList',
15 | 'successFetchAssetList',
16 | 'errorAsset',
17 | 'postAsset',
18 | 'successPostAsset'
19 | );
20 | }
21 | }
22 |
23 | export default alt.createActions(AssetActions);
24 |
--------------------------------------------------------------------------------
/client/lib/js/react/alt.js:
--------------------------------------------------------------------------------
1 | import Alt from 'alt';
2 |
3 |
4 | const alt = new Alt();
5 |
6 | export default alt;
7 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/account_detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import classnames from 'classnames';
4 | import { Row } from 'react-bootstrap/lib';
5 |
6 |
7 | const AccountDetail = ({
8 | account,
9 | isActive,
10 | handleClick
11 | }) => {
12 | return (
13 |
16 |
17 | {account.name}
18 |
19 |
20 | {account.vk}
21 |
22 |
23 | );
24 | };
25 |
26 | AccountDetail.propTypes = {
27 | account: React.PropTypes.object,
28 | handleClick: React.PropTypes.func,
29 | isActive: React.PropTypes.bool
30 | };
31 |
32 | export default AccountDetail;
33 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/account_list.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { safeInvoke } from 'js-utility-belt/es6';
3 | import classnames from 'classnames';
4 |
5 | import AccountActions from '../actions/account_actions';
6 | import AccountStore from '../stores/account_store';
7 |
8 | import Spinner from './spinner';
9 |
10 | const AccountList = React.createClass({
11 | propTypes: {
12 | activeAccount: React.PropTypes.object,
13 | appName: React.PropTypes.string,
14 | children: React.PropTypes.node,
15 | className: React.PropTypes.string,
16 | handleAccountClick: React.PropTypes.func
17 | },
18 |
19 | getInitialState() {
20 | return AccountStore.getState();
21 | },
22 |
23 | componentDidMount() {
24 | AccountStore.listen(this.onChange);
25 | this.fetchAccountList();
26 | },
27 |
28 | componentWillUnmount() {
29 | AccountStore.unlisten(this.onChange);
30 | },
31 |
32 | onChange(state) {
33 | this.setState(state);
34 | },
35 |
36 | fetchAccountList() {
37 | const { appName } = this.props;
38 | AccountActions.flushAccountList();
39 | AccountActions.fetchAccountList({ app: appName });
40 | },
41 |
42 | render() {
43 | const {
44 | activeAccount,
45 | children,
46 | className,
47 | handleAccountClick
48 | } = this.props;
49 |
50 | const { accountList } = this.state;
51 |
52 | if (accountList && accountList.length > 0) {
53 | return (
54 |
55 | {accountList
56 | .sort((a, b) => {
57 | if (a.name < b.name) return -1;
58 | if (a.name > b.name) return 1;
59 | return 0;
60 | })
61 | .map(account => (
62 |
67 | {children}
68 |
69 | ))}
70 |
71 | );
72 | } else {
73 | return (
74 |
75 |
76 |
77 | );
78 | }
79 | }
80 | });
81 |
82 | const AccountWrapper = React.createClass({
83 | propTypes: {
84 | account: React.PropTypes.object,
85 | children: React.PropTypes.node,
86 | handleClick: React.PropTypes.func,
87 | isActive: React.PropTypes.bool
88 | },
89 |
90 | handleClick() {
91 | const { account, handleClick } = this.props;
92 | safeInvoke(handleClick, account);
93 | },
94 |
95 | render() {
96 | const {
97 | account,
98 | isActive,
99 | children
100 | } = this.props;
101 |
102 | return (
103 |
104 | {
105 | React.Children.map(children, (child) =>
106 | React.cloneElement(child, {
107 | account,
108 | isActive,
109 | handleClick: this.handleClick
110 | })
111 | )
112 | }
113 |
114 | );
115 | }
116 | });
117 |
118 | export default AccountList;
119 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/asset_action_panel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button, DropdownButton, MenuItem } from 'react-bootstrap/lib';
4 | import { safeInvoke } from 'js-utility-belt/es6';
5 |
6 |
7 | const AssetActionPanel = React.createClass({
8 | propTypes: {
9 | accountList: React.PropTypes.array.isRequired,
10 | activeAccount: React.PropTypes.object.isRequired,
11 | handleActionClick: React.PropTypes.func.isRequired,
12 | actionMessage: React.PropTypes.string,
13 | actionName: React.PropTypes.string,
14 | handleAccountSelection: React.PropTypes.func,
15 | selectAccounts: React.PropTypes.bool
16 | },
17 |
18 | getDefaultProps() {
19 | return {
20 | actionMessage: 'Transfer asset to:',
21 | actionName: 'TRANSFER',
22 | selectAccounts: true
23 | };
24 | },
25 |
26 | getInitialState() {
27 | return {
28 | selectedAccount: null
29 | };
30 | },
31 |
32 | setSelectedAccount(account) {
33 | this.setState({
34 | selectedAccount: account
35 | });
36 |
37 | safeInvoke(this.props.handleAccountSelection, account);
38 | },
39 |
40 | render() {
41 | const {
42 | accountList,
43 | actionMessage,
44 | actionName,
45 | activeAccount,
46 | handleActionClick,
47 | selectAccounts
48 | } = this.props;
49 |
50 | const {
51 | selectedAccount
52 | } = this.state;
53 |
54 | const transferButton = (!selectAccounts || selectedAccount) ?
55 | handleActionClick(selectedAccount)}>
58 | {actionName}
59 | : null;
60 |
61 | const accountDropdown = selectAccounts ?
62 |
68 | {
69 | accountList
70 | .filter((account) => account !== activeAccount)
71 | .map((account) => (
72 | this.setSelectedAccount(account)}>
75 | {account.name}
76 |
77 | ))
78 | }
79 | : null;
80 |
81 | return (
82 |
83 |
{actionMessage}
84 | {accountDropdown}
85 | {transferButton}
86 |
87 | );
88 | }
89 | });
90 |
91 |
92 | export default AssetActionPanel;
93 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/asset_detail.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Row, Glyphicon } from 'react-bootstrap/lib';
4 | import classnames from 'classnames';
5 | import moment from 'moment';
6 |
7 |
8 | const AssetDetail = React.createClass({
9 | propTypes: {
10 | asset: React.PropTypes.object.isRequired,
11 | assetContent: React.PropTypes.string,
12 | children: React.PropTypes.node,
13 | className: React.PropTypes.string,
14 | inBacklog: React.PropTypes.bool,
15 | onClick: React.PropTypes.func
16 | },
17 |
18 | getAssetContent() {
19 | const {
20 | asset,
21 | assetContent
22 | } = this.props;
23 |
24 | if (assetContent) {
25 | return assetContent;
26 | }
27 | // TODO: Validate
28 | const { data: { payload: { content } } = {} } = asset.transaction;
29 | return content || '-';
30 | },
31 |
32 | render() {
33 | const {
34 | asset,
35 | children,
36 | className,
37 | inBacklog,
38 | onClick
39 | } = this.props;
40 |
41 | const assetContent = this.getAssetContent();
42 | const validGlyph = inBacklog ? : ;
43 | const timestamp =
44 | moment(parseInt(asset.transaction.timestamp, 10) * 1000).toDate().toGMTString();
45 |
46 | return (
47 |
48 |
49 |
50 | {asset.id}
51 |
52 |
53 | {assetContent}
54 |
55 |
56 | {`${timestamp} `}{validGlyph}
57 |
58 | {children}
59 |
60 |
61 | );
62 | }
63 | });
64 |
65 | export default AssetDetail;
66 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/bigchaindb_connection.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { safeInvoke, safeMerge } from 'js-utility-belt/es6';
4 |
5 | import AssetStore from '../stores/asset_store';
6 | import AccountStore from '../stores/account_store';
7 |
8 |
9 | export default function BigchainDBConnection(Component) {
10 | return React.createClass({
11 | displayName: `BigchainDBConnection(${Component.displayName || Component})`,
12 |
13 | getInitialState() {
14 | const accountStore = AccountStore.getState();
15 | const assetStore = AssetStore.getState();
16 |
17 | return safeMerge(
18 | {
19 | activeAccount: null,
20 | activeAsset: null
21 | },
22 | accountStore,
23 | assetStore
24 | );
25 | },
26 |
27 | componentDidMount() {
28 | AccountStore.listen(this.onAccountStoreChange);
29 | AssetStore.listen(this.onChange);
30 | },
31 |
32 | componentWillUnmount() {
33 | AccountStore.unlisten(this.onAccountStoreChange);
34 | AssetStore.unlisten(this.onChange);
35 | },
36 |
37 | onChange(state) {
38 | this.setState(state);
39 | },
40 |
41 | onAccountStoreChange(state) {
42 | const { oldAccountList } = this.state;
43 | state.accountList.forEach((account) => {
44 | if (account.ledger &&
45 | (!oldAccountList ||
46 | (oldAccountList && oldAccountList.indexOf(account) === -1))) {
47 | account.ledger.on('incoming', this.handleLedgerChanges);
48 | }
49 | });
50 |
51 | this.setState(state);
52 | },
53 |
54 | handleAccountChange(activeAccount) {
55 | this.setState({
56 | activeAccount
57 | });
58 | },
59 |
60 | handleLedgerChanges(changes) {
61 | console.log('incoming: ', changes);
62 |
63 | if (changes && changes.client && this.refs.component) {
64 | const {
65 | accountList
66 | } = this.state;
67 |
68 | const account = accountList.filter((account) => account.vk === changes.client)[0];
69 | safeInvoke(this.refs.component.fetchAssetList, {
70 | account
71 | });
72 | }
73 | },
74 |
75 | handleAssetChange(asset) {
76 | this.setState({
77 | activeAsset: asset
78 | });
79 | },
80 |
81 | resetActiveAccount() {
82 | this.handleAccountChange(null);
83 | },
84 |
85 | render() {
86 | return (
87 |
93 | );
94 | }
95 | });
96 | }
97 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/search.js:
--------------------------------------------------------------------------------
1 | import React from 'react/';
2 |
3 | import { FormGroup, InputGroup, FormControl, Glyphicon } from 'react-bootstrap/lib/';
4 |
5 |
6 | const Search = React.createClass({
7 | propTypes: {
8 | handleSearch: React.PropTypes.func,
9 | initialQuery: React.PropTypes.string
10 | },
11 |
12 | getInitialState() {
13 | return {
14 | timer: null,
15 | searchQuery: this.props.initialQuery,
16 | loading: false
17 | };
18 | },
19 |
20 | startTimer(searchQuery) {
21 | const { timer } = this.state;
22 | // The timer waits for the specified threshold time in milliseconds
23 | // and then calls `evaluateTimer`.
24 | // If another letter has been called in the mean time (timespan < `threshold`),
25 | // the present timer gets cleared and a new one is added to `this.state`.
26 | // This means that `evaluateTimer`, will only be called when the threshold has actually
27 | // passed, (tdaub)
28 | clearTimeout(timer); // apparently `clearTimeout` can be called with null, without throwing errors
29 | const newTimer = setTimeout(this.evaluateTimer(searchQuery), 500);
30 |
31 | this.setState({ timer: newTimer });
32 | },
33 |
34 | evaluateTimer(searchQuery) {
35 | return () => {
36 | this.setState({ timer: null, loading: true }, () => {
37 | this.handleSearch(searchQuery);
38 | });
39 | };
40 | },
41 |
42 | handleSearchThrottled(event) {
43 | // On each letter entry we're updating the state of the component
44 | // and start a timer, which we're also pushing to the state
45 | // of the component
46 | const value = event.target.value;
47 | this.startTimer(value);
48 | this.setState({ searchQuery: value });
49 | },
50 |
51 | handleSearch(searchQuery) {
52 | const { handleSearch } = this.props;
53 | handleSearch(searchQuery);
54 | },
55 |
56 | render() {
57 | const { searchQuery } = this.state;
58 | return (
59 |
60 |
61 |
62 |
63 |
69 |
70 |
71 |
72 | );
73 | }
74 | });
75 |
76 | export default Search;
77 |
--------------------------------------------------------------------------------
/client/lib/js/react/components/spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classNames from 'classnames';
3 |
4 |
5 | const Spinner = React.createClass({
6 | propTypes: {
7 | classNames: React.PropTypes.string,
8 | color: React.PropTypes.oneOf(
9 | ['black', 'blue', 'dark-blue', 'light-blue', 'pink', 'white', 'loop']
10 | ),
11 | size: React.PropTypes.oneOf(
12 | ['sm', 'md', 'lg']
13 | )
14 | },
15 |
16 | getDefaultProps() {
17 | return {
18 | inline: false,
19 | size: 'md',
20 | color: 'black'
21 | };
22 | },
23 |
24 | render() {
25 | const {
26 | classNames: classes,
27 | color,
28 | size
29 | } = this.props;
30 |
31 | return (
32 |
39 | );
40 | }
41 | });
42 |
43 | export default Spinner;
44 |
--------------------------------------------------------------------------------
/client/lib/js/react/sources/account_source.js:
--------------------------------------------------------------------------------
1 | import AccountActions from '../actions/account_actions';
2 |
3 | import request from '../../utils/request';
4 |
5 |
6 | const AccountSource = {
7 | lookupAccount: {
8 | remote(state) {
9 | return request('accounts_detail', {
10 | urlTemplateSpec: {
11 | accountId: state.accountMeta.idToFetch
12 | }
13 | });
14 | },
15 |
16 | success: AccountActions.successFetchAccount,
17 | error: AccountActions.errorAccount
18 | },
19 |
20 | lookupAccountList: {
21 | remote(state) {
22 | return request('accounts', {
23 | query: {
24 | app: state.accountMeta.app
25 | }
26 | });
27 | },
28 |
29 | success: AccountActions.successFetchAccountList,
30 | error: AccountActions.errorAccount
31 | },
32 |
33 | postAccount: {
34 | remote(state) {
35 | return request('accounts', {
36 | method: 'POST',
37 | jsonBody: state.accountMeta.payloadToPost
38 | });
39 | },
40 |
41 | success: AccountActions.successPostAccount,
42 | error: AccountActions.errorAccount
43 | }
44 | };
45 |
46 | export default AccountSource;
47 |
--------------------------------------------------------------------------------
/client/lib/js/react/sources/asset_source.js:
--------------------------------------------------------------------------------
1 | import request from '../../utils/request';
2 |
3 | import AssetActions from '../actions/asset_actions';
4 |
5 |
6 | const AssetSource = {
7 | lookupAssetList: {
8 | remote(state) {
9 | const { account, search } = state.assetMeta;
10 | // fetch assets for account
11 | const url = `${account.api}/accounts/${account.vk}/assets/`;
12 | return request(url, {
13 | query: { search }
14 | });
15 | },
16 |
17 | success: AssetActions.successFetchAssetList,
18 | error: AssetActions.errorAsset
19 | },
20 |
21 | postAsset: {
22 | remote(state) {
23 | const { account, payloadToPost } = state.assetMeta;
24 | const url = `${account.api}/assets/`;
25 | return request(url, {
26 | method: 'POST',
27 | jsonBody: payloadToPost
28 | });
29 | },
30 |
31 | success: AssetActions.successPostAsset,
32 | error: AssetActions.errorAsset
33 | },
34 |
35 | transferAsset: {
36 | remote(state) {
37 | const { account, idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta;
38 | const url = `${account.api}/assets/${assetId}/${cid}/transfer/`;
39 | return request(url, {
40 | method: 'POST',
41 | jsonBody: payloadToPost,
42 | urlTemplateSpec: { assetId, cid }
43 | });
44 | },
45 |
46 | success: AssetActions.successPostAsset,
47 | error: AssetActions.errorAsset
48 | },
49 |
50 | escrowAsset: {
51 | remote(state) {
52 | const { idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta;
53 |
54 | return request('assets_escrow', {
55 | method: 'POST',
56 | jsonBody: payloadToPost,
57 | urlTemplateSpec: { assetId, cid }
58 | });
59 | },
60 |
61 | success: AssetActions.successPostAsset,
62 | error: AssetActions.errorAsset
63 | },
64 |
65 | fulfillEscrowAsset: {
66 | remote(state) {
67 | const { idToTransfer: { cid, txid: assetId }, payloadToPost } = state.assetMeta;
68 |
69 | return request('assets_escrow_fulfill', {
70 | method: 'POST',
71 | jsonBody: payloadToPost,
72 | urlTemplateSpec: { assetId, cid }
73 | });
74 | },
75 |
76 | success: AssetActions.successPostAsset,
77 | error: AssetActions.errorAsset
78 | }
79 | };
80 |
81 | export default AssetSource;
82 |
--------------------------------------------------------------------------------
/client/lib/js/react/stores/account_store.js:
--------------------------------------------------------------------------------
1 | import alt from '../alt';
2 |
3 | import AccountActions from '../actions/account_actions';
4 | import AccountSource from '../sources/account_source';
5 |
6 | import AssetActions from '../actions/asset_actions';
7 |
8 | import connectToBigchainDBLedger from '../../plugins/ledger_utils';
9 |
10 |
11 | class AccountStore {
12 | constructor() {
13 | this.account = null;
14 | this.accountList = null;
15 | this.accountMeta = {
16 | err: null,
17 | payloadToPost: null,
18 | idToFetch: null,
19 | app: null
20 | };
21 | this.bindActions(AccountActions);
22 | this.registerAsync(AccountSource);
23 | }
24 |
25 | onFetchAccount(idToFetch) {
26 | this.accountMeta.idToFetch = idToFetch;
27 | this.getInstance().lookupAccount();
28 | }
29 |
30 | onSuccessFetchAccount(account) {
31 | if (account) {
32 | this.account = this.postProcessAccount(account);
33 | this.accountMeta.err = null;
34 | this.accountMeta.idToFetch = null;
35 | this.accountMeta.app = null;
36 | } else {
37 | this.accountMeta.err = new Error('Problem fetching the account');
38 | }
39 | }
40 |
41 | onFetchAccountList({ app }) {
42 | this.accountMeta.app = app;
43 | this.getInstance().lookupAccountList();
44 | }
45 |
46 | onSuccessFetchAccountList(accountList) {
47 | if (accountList) {
48 | this.accountList = accountList.accounts.map((account) => this.postProcessAccount(account));
49 | this.accountMeta.err = null;
50 | this.accountMeta.app = null;
51 | } else {
52 | this.accountMeta.err = new Error('Problem fetching the account list');
53 | }
54 | }
55 |
56 | postProcessAccount(account) {
57 | const processedAccount = Object.assign({}, account);
58 |
59 | // ledger bindings
60 | processedAccount.ledger = connectToBigchainDBLedger(account);
61 | processedAccount.api = `http://${account.ledger.api}/api`;
62 |
63 | // connectors
64 | processedAccount.ledger.getConnectors()
65 | .then((res) => {
66 | processedAccount.connectors = res.connectors;
67 | processedAccount.isConnector =
68 | res.connectors.filter((connector) => connector.vk === account.vk).length > 0;
69 | });
70 |
71 | // assets
72 | AssetActions.fetchAssetList.defer({
73 | account: processedAccount
74 | });
75 |
76 | return processedAccount;
77 | }
78 |
79 | onPostAccount(payloadToPost) {
80 | this.accountMeta.payloadToPost = payloadToPost;
81 | this.getInstance().postAccount();
82 | }
83 |
84 | onSuccessPostAccount(account) {
85 | if (account) {
86 | this.account = account;
87 | this.accountMeta.err = null;
88 | this.accountMeta.payloadToPost = null;
89 | this.accountMeta.app = null;
90 | } else {
91 | this.accountMeta.err = new Error('Problem posting to the account');
92 | }
93 | }
94 |
95 | onFlushAccount() {
96 | this.account = null;
97 | this.accountMeta.err = null;
98 | this.accountMeta.payloadToPost = null;
99 | this.accountMeta.idToFetch = null;
100 | this.accountMeta.app = null;
101 | }
102 |
103 | onFlushAccountList() {
104 | this.accountList = null;
105 | this.accountMeta.err = null;
106 | this.accountMeta.app = null;
107 | }
108 |
109 | onErrorAccount(err) {
110 | this.accountMeta.err = err;
111 | }
112 | }
113 |
114 | export default alt.createStore(AccountStore, 'AccountStore');
115 |
--------------------------------------------------------------------------------
/client/lib/js/react/stores/asset_store.js:
--------------------------------------------------------------------------------
1 | import { safeMerge } from 'js-utility-belt/es6';
2 | import alt from '../alt';
3 |
4 | import parseEscrowData from '../../utils/cryptoconditions/parse_escrow_data';
5 |
6 | import AssetActions from '../actions/asset_actions';
7 | import AssetSource from '../sources/asset_source';
8 |
9 | class AssetStore {
10 | constructor() {
11 | this.asset = null;
12 | this.assetList = {};
13 | this.assetMeta = {
14 | err: null,
15 | isFetchingList: false,
16 | payloadToPost: null,
17 | idToFetch: null,
18 | idToTransfer: null,
19 | account_: null,
20 | search: null
21 | };
22 | this.bindActions(AssetActions);
23 | this.registerAsync(AssetSource);
24 | }
25 |
26 | onFetchAssetList({ account, search, blockWhenFetching }) {
27 | if (!blockWhenFetching ||
28 | (blockWhenFetching && !this.assetMeta.isFetchingList)) {
29 | this.assetMeta.account = account;
30 | this.assetMeta.search = search;
31 | this.assetMeta.isFetchingList = true;
32 | this.getInstance().lookupAssetList();
33 | }
34 | }
35 |
36 | onSuccessFetchAssetList(assetList) {
37 | if (assetList) {
38 | const { assets, account } = assetList;
39 | if (account && assets) {
40 | if (assets.hasOwnProperty('bigchain')) {
41 | this.assetList[account] =
42 | assets.bigchain
43 | .concat(assets.backlog)
44 | .map(this.postProcessAsset)
45 | .sort((a, b) => a.transaction.timestamp - b.transaction.timestamp);
46 | }
47 | }
48 | this.assetMeta.err = null;
49 | this.assetMeta.account = null;
50 | } else {
51 | this.assetMeta.err = new Error('Problem fetching the asset list');
52 | }
53 | this.assetMeta.isFetchingList = false;
54 | }
55 |
56 | postProcessAsset(asset) {
57 | const condition = asset.transaction.conditions[0].condition;
58 |
59 | if (Array.isArray(condition.details.subfulfillments)) {
60 | asset.type = 'multi-owner';
61 | return safeMerge(
62 | asset,
63 | parseEscrowData(condition.details)
64 | );
65 | } else {
66 | asset.type = 'single-owner';
67 | }
68 |
69 | return asset;
70 | }
71 |
72 | onFlushAssetList(account) {
73 | this.assetList[account] = [];
74 | this.assetMeta.account = null;
75 | this.assetMeta.search = null;
76 | this.assetMeta.isFetchingList = false;
77 | }
78 |
79 | onPostAsset({ account, payloadToPost }) {
80 | this.assetMeta.account = account;
81 | this.assetMeta.payloadToPost = payloadToPost;
82 | this.getInstance().postAsset();
83 | }
84 |
85 | onTransferAsset({ account, idToTransfer, payloadToPost }) {
86 | this.assetMeta.account = account;
87 | this.assetMeta.idToTransfer = idToTransfer;
88 | this.assetMeta.payloadToPost = payloadToPost;
89 | this.getInstance().transferAsset();
90 | }
91 |
92 | onEscrowAsset({ idToTransfer, payloadToPost }) {
93 | this.assetMeta.idToTransfer = idToTransfer;
94 | this.assetMeta.payloadToPost = payloadToPost;
95 | this.getInstance().escrowAsset();
96 | }
97 |
98 | onFulfillEscrowAsset({ idToTransfer, payloadToPost }) {
99 | this.assetMeta.idToTransfer = idToTransfer;
100 | this.assetMeta.payloadToPost = payloadToPost;
101 | this.getInstance().fulfillEscrowAsset();
102 | }
103 |
104 | onSuccessPostAsset(asset) {
105 | if (asset) {
106 | this.asset = asset;
107 | this.assetMeta.err = null;
108 | this.assetMeta.idToTransfer = null;
109 | this.assetMeta.payloadToPost = null;
110 | } else {
111 | this.assetMeta.err = new Error('Problem posting to the asset');
112 | }
113 | }
114 |
115 | onFlushAsset() {
116 | this.asset = null;
117 | this.assetMeta.err = null;
118 | this.assetMeta.payloadToPost = null;
119 | this.assetMeta.idToFetch = null;
120 | this.assetMeta.search = null;
121 | this.assetMeta.isFetchingList = false;
122 | }
123 |
124 | onErrorAsset(err) {
125 | this.assetMeta.err = err;
126 | this.assetMeta.isFetchingList = false;
127 | }
128 | }
129 |
130 | export default alt.createStore(AssetStore, 'AssetStore');
131 |
--------------------------------------------------------------------------------
/client/lib/js/utils/bigchaindb/in_backlog.js:
--------------------------------------------------------------------------------
1 | // TODO: improve backlog identifier: asset.hasOwnProperty('assignee')
2 |
3 | const inBacklog = (asset) => {
4 | return asset.hasOwnProperty('assignee');
5 | };
6 |
7 | export default inBacklog;
8 |
--------------------------------------------------------------------------------
/client/lib/js/utils/cryptoconditions/filter_by_type.js:
--------------------------------------------------------------------------------
1 |
2 | const filterByType = ({ condition, typeId, maxDepth }) => {
3 | let res = [];
4 | if (condition.hasOwnProperty('type_id') && condition.type_id === typeId) {
5 | res.push(condition);
6 | }
7 |
8 | if (condition.hasOwnProperty('subfulfillments') && (maxDepth || maxDepth == null)) {
9 | res = res.concat(...Object.values(condition.subfulfillments).map((subcondition) => {
10 | return filterByType({
11 | typeId,
12 | condition: subcondition,
13 | maxDepth: maxDepth && maxDepth - 1
14 | });
15 | }));
16 | }
17 |
18 | return res;
19 | };
20 |
21 |
22 | export default filterByType;
23 |
--------------------------------------------------------------------------------
/client/lib/js/utils/cryptoconditions/parse_escrow_data.js:
--------------------------------------------------------------------------------
1 | import filterByType from './filter_by_type';
2 | import TypeIds from './type_ids';
3 |
4 |
5 | const filterChildrenByTypes = ({ condition, types }) => {
6 | if (condition.hasOwnProperty('subfulfillments')) {
7 | const children = Object.values(types)
8 | .map((type) => {
9 | return filterByType({
10 | condition,
11 | typeId: type,
12 | maxDepth: 1
13 | });
14 | })
15 | .reduce((a, b) => a.concat(b));
16 | if (children.length >= types.length) {
17 | return children;
18 | }
19 | }
20 | return null;
21 | };
22 |
23 |
24 | export default function parseEscrowData(condition) {
25 | const expiryCondition = filterByType({
26 | condition,
27 | typeId: TypeIds.timeout
28 | });
29 |
30 | const thresholdConditions = filterByType({
31 | condition,
32 | typeId: TypeIds.threshold
33 | });
34 |
35 | let executeCondition = null;
36 | let abortCondition = null;
37 |
38 | Object.values(thresholdConditions).forEach((thresholdCondition) => {
39 | if (!executeCondition) {
40 | const filteredExecuteCondition = filterChildrenByTypes({
41 | condition: thresholdCondition,
42 | types: [TypeIds.ed25519, TypeIds.timeout]
43 | });
44 | executeCondition = filteredExecuteCondition ?
45 | filterByType({
46 | condition: thresholdCondition,
47 | typeId: TypeIds.ed25519
48 | })[0] : null;
49 | }
50 | if (!abortCondition) {
51 | const filteredAbortCondition = filterChildrenByTypes({
52 | condition: thresholdCondition,
53 | types: [TypeIds.ed25519, TypeIds.inverter]
54 | });
55 | abortCondition = filteredAbortCondition ?
56 | filterByType({
57 | condition: thresholdCondition,
58 | typeId: TypeIds.ed25519
59 | })[0] : null;
60 | }
61 | });
62 |
63 | return {
64 | expiryTime: (expiryCondition.length > 0) ? expiryCondition[0].expire_time : null,
65 | executeCondition,
66 | abortCondition
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/client/lib/js/utils/cryptoconditions/type_ids.js:
--------------------------------------------------------------------------------
1 |
2 | const TypeIds = {
3 | 'preimage': 0,
4 | 'prefix': 1,
5 | 'threshold': 2,
6 | 'rsa': 3,
7 | 'ed25519': 4,
8 | 'inverter': 98,
9 | 'timeout': 99
10 | };
11 |
12 |
13 | export default TypeIds;
14 |
--------------------------------------------------------------------------------
/client/lib/js/utils/request.js:
--------------------------------------------------------------------------------
1 | import { request as baseRequest, sanitize } from 'js-utility-belt/es6';
2 |
3 | import ApiUrls from '../constants/api_urls';
4 |
5 |
6 | const DEFAULT_REQUEST_CONFIG = {
7 | credentials: 'include',
8 | headers: {
9 | 'Accept': 'application/json',
10 | 'Content-Type': 'application/json'
11 | }
12 | };
13 |
14 | /**
15 | * Small wrapper around js-utility-belt's request that provides url resolving, default settings, and
16 | * response handling.
17 | */
18 | export default function request(url, config = {}) {
19 | // Load default fetch configuration and remove any falsy query parameters
20 | const requestConfig = Object.assign({}, DEFAULT_REQUEST_CONFIG, config, {
21 | query: config.query && sanitize(config.query)
22 | });
23 | let apiUrl = url;
24 |
25 | if (!url) {
26 | return Promise.reject(new Error('Request was not given a url.'));
27 | } else if (!url.match(/^http/)) {
28 | apiUrl = ApiUrls[url];
29 | if (!apiUrl) {
30 | return Promise.reject(new Error(`Request could not find a url mapping for "${url}"`));
31 | }
32 | }
33 |
34 | return baseRequest(apiUrl, requestConfig)
35 | .then((res) => res.json())
36 | .catch((err) => {
37 | console.error(err);
38 | throw err;
39 | });
40 | }
41 |
--------------------------------------------------------------------------------
/client/on_the_record/js/app.js:
--------------------------------------------------------------------------------
1 | // Install necessary polyfills (see supported browsers) into global
2 | import 'core-js/es6';
3 | import 'core-js/stage/4';
4 | import 'isomorphic-fetch';
5 |
6 | import React from 'react';
7 | import ReactDOM from 'react-dom';
8 |
9 | import OnTheRecord from './components/on_the_record';
10 |
11 | import '../../lib/css/scss/main.scss';
12 |
13 |
14 | const App = () => (
15 |
16 |
17 |
18 | );
19 |
20 | ReactDOM.render( , document.getElementById('bigchaindb-example-app'));
21 |
--------------------------------------------------------------------------------
/client/on_the_record/js/components/asset_history.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import AssetDetail from '../../../lib/js/react/components/asset_detail';
4 | import inBacklog from '../../../lib/js/utils/bigchaindb/in_backlog';
5 |
6 |
7 | const AssetHistory = ({
8 | assetList
9 | }) => {
10 | if (assetList.length === 0) {
11 | return (
12 |
13 | No messages found on BigchainDB. Start typing...
14 |
15 | );
16 | }
17 |
18 | return (
19 |
20 | {assetList
21 | .map(asset => (
22 |
27 | ))}
28 |
29 | );
30 | };
31 |
32 | AssetHistory.propTypes = {
33 | assetList: React.PropTypes.array.isRequired
34 | };
35 |
36 | export default AssetHistory;
37 |
--------------------------------------------------------------------------------
/client/on_the_record/js/components/assets.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Scroll from 'react-scroll';
4 |
5 | import AssetActions from '../../../lib/js/react/actions/asset_actions';
6 |
7 | import AssetHistory from './asset_history';
8 |
9 |
10 | const Assets = React.createClass({
11 |
12 | propTypes: {
13 | activeAccount: React.PropTypes.object,
14 | assetList: React.PropTypes.array
15 | },
16 |
17 | getInitialState() {
18 | return { value: "" };
19 | },
20 |
21 | handleInputSubmit(event) {
22 | event.preventDefault();
23 | const { activeAccount } = this.props;
24 | const { value } = this.state;
25 |
26 | const payloadToPost = {
27 | to: activeAccount.vk,
28 | content: value
29 | };
30 | AssetActions.postAsset({
31 | payloadToPost,
32 | account: activeAccount
33 | });
34 |
35 | this.setState({ value: "" });
36 |
37 | Scroll.animateScroll.scrollToBottom();
38 | },
39 |
40 | handleInputChange(event) {
41 | this.setState({ value: event.target.value });
42 | },
43 |
44 | render() {
45 | const {
46 | activeAccount,
47 | assetList
48 | } = this.props;
49 |
50 | const { value } = this.state;
51 |
52 | if (!assetList || !activeAccount) {
53 | return (
54 |
55 | Select account from the list...
56 |
57 | );
58 | }
59 |
60 | return (
61 |
73 | );
74 | }
75 | });
76 |
77 | export default Assets;
78 |
--------------------------------------------------------------------------------
/client/on_the_record/js/components/on_the_record.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Navbar } from 'react-bootstrap/lib';
4 |
5 | import Scroll from 'react-scroll';
6 |
7 | import AccountList from '../../../lib/js/react/components/account_list';
8 | import AccountDetail from '../../../lib/js/react/components/account_detail';
9 |
10 | import Assets from './assets';
11 | import Search from '../../../lib/js/react/components/search';
12 |
13 | import AssetActions from '../../../lib/js/react/actions/asset_actions';
14 |
15 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection';
16 |
17 |
18 | const OnTheRecord = React.createClass({
19 | propTypes: {
20 | // Injected through BigchainDBConnection
21 | activeAccount: React.PropTypes.object,
22 | assetList: React.PropTypes.object,
23 | assetMeta: React.PropTypes.object,
24 | handleAccountChange: React.PropTypes.func
25 | },
26 |
27 | getInitialState() {
28 | return {
29 | search: null
30 | };
31 | },
32 |
33 | fetchAssetList({ account, search }) {
34 | if (account) {
35 | AssetActions.fetchAssetList({
36 | account,
37 | search,
38 | blockWhenFetching: true
39 | });
40 | Scroll.animateScroll.scrollToBottom();
41 | }
42 | },
43 |
44 | handleAccountChangeAndScroll(account) {
45 | this.props.handleAccountChange(account);
46 | Scroll.animateScroll.scrollToBottom();
47 | },
48 |
49 | handleSearch(query) {
50 | const { activeAccount } = this.props;
51 |
52 | this.setState({
53 | search: query
54 | });
55 |
56 | this.fetchAssetList({
57 | account: activeAccount,
58 | search: query
59 | });
60 | },
61 |
62 | render() {
63 | const {
64 | activeAccount,
65 | assetList,
66 | assetMeta
67 | } = this.props;
68 |
69 | const assetListForAccount = (
70 | assetList && activeAccount && Array.isArray(assetList[activeAccount.vk])) ?
71 | assetList[activeAccount.vk] : null;
72 |
73 | return (
74 |
75 |
76 | "On the Record"
77 |
78 |
100 |
101 | );
102 | }
103 | });
104 |
105 |
106 | export default BigchainDBConnection(OnTheRecord);
107 |
--------------------------------------------------------------------------------
/client/on_the_record/scss/custom_style.scss:
--------------------------------------------------------------------------------
1 |
2 | .on-the-record {
3 |
4 | .asset-container {
5 | background: lighten($fg-color, 95%);
6 | border: 1px solid lighten($fg-color, 50%);
7 | border-radius: .7em;
8 | margin-bottom: .5em;
9 | }
10 |
11 | .asset-container-id {
12 | color: lighten($fg-color, 50%);
13 | }
14 |
15 | .page-content {
16 | padding-bottom: 5em;
17 | padding-top: 1em;
18 | }
19 | }
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bigchaindb-examples",
3 | "version": "0.0.3",
4 | "description": "BigchainDB Examples",
5 | "homepage": "https://www.bigchain.io/",
6 | "bugs": "https://github.com/bigchain/bigchaindb-examples/issues",
7 | "license": "Apache-2.0",
8 | "author": "Dimitri De Jonghe",
9 | "repository": {
10 | "type": "git",
11 | "url": "git@github.com:bigchaindb/bigchaindb-examples.git"
12 | },
13 | "keywords": [
14 | "bigchaindb",
15 | "blockchain",
16 | "examples",
17 | "react"
18 | ],
19 | "scripts": {
20 | "lint": "eslint ./",
21 | "build": "rimraf ./build && cross-env NODE_ENV=extract webpack",
22 | "build:dist": "rimraf ./dist && cross-env NODE_ENV=production webpack -p",
23 | "clean": "rimraf ./build ./dist",
24 | "start": "cross-env NODE_ENV=demo node server.demo.js",
25 | "test": "echo \"Error: no test specified\" && exit 1",
26 | "postinstall": "npm run build"
27 | },
28 | "dependencies": {
29 | "alt": "^0.18.4",
30 | "bootstrap-sass": "^3.3.6",
31 | "classnames": "^2.2.5",
32 | "core-js": "^2.4.0",
33 | "ilp-plugin-bigchaindb": "^0.0.7",
34 | "js-utility-belt": "^1.5.0",
35 | "moment": "^2.14.1",
36 | "react": "^15.2.1",
37 | "react-bootstrap": "^0.30.7",
38 | "react-dom": "^15.2.1",
39 | "react-matrix": "0.0.6",
40 | "react-scroll": "^1.0.24"
41 | },
42 | "devDependencies": {
43 | "autoprefixer": "^6.3.7",
44 | "babel-cli": "^6.10.1",
45 | "babel-eslint": "^6.1.2",
46 | "babel-loader": "^6.2.4",
47 | "babel-plugin-react-transform": "^2.0.2",
48 | "babel-plugin-transform-object-assign": "^6.8.0",
49 | "babel-plugin-transform-react-display-name": "^6.8.0",
50 | "babel-plugin-transform-runtime": "^6.9.0",
51 | "babel-preset-es2015": "^6.9.0",
52 | "babel-preset-react": "^6.11.1",
53 | "babel-runtime": "^6.9.2",
54 | "cross-env": "^2.0.0",
55 | "css-loader": "^0.23.1",
56 | "dotenv": "^2.0.0",
57 | "eslint": "^2.10.2",
58 | "eslint-config-ascribe-react": "^1.0.1",
59 | "eslint-plugin-import": "^1.10.3",
60 | "eslint-plugin-jsx-a11y": "^2.0.1",
61 | "eslint-plugin-react": "^5.2.2",
62 | "extract-text-webpack-plugin": "=2.0.0-beta.1",
63 | "file-loader": "^0.9.0",
64 | "html-webpack-plugin": "^2.22.0",
65 | "node-sass": "^3.8.0",
66 | "postcss-loader": "^0.9.1",
67 | "react-transform-hmr": "^1.0.4",
68 | "rimraf": "^2.5.3",
69 | "sass-loader": "^4.0.0",
70 | "style-loader": "^0.13.1",
71 | "url-loader": "^0.5.7",
72 | "webpack": "=2.1.0-beta.17",
73 | "webpack-combine-loaders": "^2.0.0",
74 | "webpack-dev-server": "=2.1.0-beta.0"
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/client/server.demo.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-console */
2 | /* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */
3 | 'use strict';
4 |
5 | const WebpackDevServer = require('webpack-dev-server');
6 | const webpack = require('webpack');
7 | const config = require('./webpack.config.js');
8 |
9 | require('dotenv').load({ silent: true });
10 |
11 | const HOST_NAME = process.env.CLIENT_HOST || 'localhost';
12 | const PORT = process.env.CLIENT_PORT || 3000;
13 |
14 | // Enable hot reloading if on demo mode
15 | if (process.env.NODE_ENV === 'demo') {
16 | // Each entry must have the hot dev server included
17 | Object.keys(config.entry).forEach((entryName) => {
18 | config.entry[entryName] = [
19 | config.entry[entryName],
20 | 'webpack/hot/dev-server',
21 | `webpack-dev-server/client?http://${HOST_NAME}:${PORT}/`
22 | ];
23 | });
24 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
25 | // React hot reloading is enabled through .babelrc and babel-react-transform
26 | }
27 |
28 | // Specify output location for bundled files
29 | config.output.publicPath = '/';
30 |
31 | // Configure server
32 | const compiler = webpack(config);
33 |
34 | const server = new WebpackDevServer(compiler, {
35 | publicPath: config.output.publicPath,
36 | contentBase: './demo',
37 | hot: true,
38 | noInfo: true,
39 | stats: { colors: true }
40 | });
41 |
42 | // Start server
43 | server.listen(PORT, HOST_NAME, (err) => {
44 | if (err) {
45 | console.error(`Demo server ran into ${err} while starting on ${HOST_NAME}:${PORT}.` +
46 | 'Shutting down...');
47 | server.close();
48 | }
49 | console.log(`Demo server running on ${HOST_NAME}:${PORT}`);
50 | });
51 |
--------------------------------------------------------------------------------
/client/share_trader/js/app.js:
--------------------------------------------------------------------------------
1 | // Install necessary polyfills (see supported browsers) into global
2 | import 'core-js/es6';
3 | import 'core-js/stage/4';
4 | import 'isomorphic-fetch';
5 |
6 | import React from 'react';
7 | import ReactDOM from 'react-dom';
8 |
9 | import ShareTrader from './components/share_trader';
10 |
11 | import '../../lib/css/scss/main.scss';
12 |
13 |
14 | const App = () => (
15 |
16 |
17 |
18 | );
19 |
20 | ReactDOM.render( , document.getElementById('bigchaindb-example-app'));
21 |
--------------------------------------------------------------------------------
/client/share_trader/js/components/asset_matrix.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Matrix from 'react-matrix';
3 | import { safeInvoke } from 'js-utility-belt/es6';
4 |
5 | const AssetMatrix = React.createClass({
6 |
7 | propTypes: {
8 | assetList: React.PropTypes.array,
9 | cols: React.PropTypes.number,
10 | handleAssetClick: React.PropTypes.func,
11 | rows: React.PropTypes.number,
12 | squareSize: React.PropTypes.number,
13 | states: React.PropTypes.object
14 | },
15 |
16 | getDefaultProps() {
17 | return {
18 | rows: 8,
19 | cols: 8,
20 | squareSize: 50,
21 | states: {
22 | '0': 'available',
23 | '1': 'state1',
24 | '2': 'state2',
25 | '3': 'state3',
26 | '4': 'state4',
27 | '5': 'state5',
28 | '6': 'state6',
29 | '7': 'state7',
30 | '8': 'state8'
31 | }
32 | };
33 | },
34 |
35 | initializeMatrix(rows, cols) {
36 | const matrix = new Array(cols);
37 |
38 | for (let i = 0; i < rows; i++) {
39 | matrix[i] = new Array(cols);
40 |
41 | for (let j = 0; j < cols; j++) {
42 | matrix[i][j] = 'default';
43 | }
44 | }
45 | return matrix;
46 | },
47 |
48 | mapAssetsOnMatrix() {
49 | const { rows, cols } = this.props;
50 | const matrix = this.initializeMatrix(cols, rows);
51 |
52 | for (const content of this.getAssetListContent()) {
53 | matrix[content.y][content.x] = content.vk;
54 | }
55 |
56 | return matrix;
57 | },
58 |
59 | getAssetListContent() {
60 | const { assetList } = this.props;
61 |
62 | if (assetList) {
63 | return assetList.map((asset) => ({
64 | vk: asset.transaction.conditions[0].new_owners[0],
65 | x: asset.transaction.data.payload.content.x,
66 | y: asset.transaction.data.payload.content.y
67 | }));
68 | }
69 | return [];
70 | },
71 |
72 | getAssetForCell(x, y) {
73 | const { assetList } = this.props;
74 |
75 | if (assetList) {
76 | for (const asset of assetList) {
77 | const content = asset.transaction.data.payload.content;
78 |
79 | if (content.x === x && content.y === y) {
80 | return asset;
81 | }
82 | }
83 | }
84 |
85 | return null;
86 | },
87 |
88 | handleCellClick(cellState) {
89 | const { handleAssetClick } = this.props;
90 |
91 | const x = parseInt(cellState.x, 10);
92 | const y = parseInt(cellState.y, 10);
93 |
94 | const activeAsset = this.getAssetForCell(x, y);
95 | safeInvoke(handleAssetClick, activeAsset);
96 | },
97 |
98 | render() {
99 | const {
100 | squareSize,
101 | states
102 | } = this.props;
103 |
104 | return (
105 |
106 |
111 |
112 | );
113 | }
114 | });
115 |
116 |
117 | export default AssetMatrix;
118 |
--------------------------------------------------------------------------------
/client/share_trader/js/components/asset_row.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import classnames from 'classnames';
3 | import { safeInvoke } from 'js-utility-belt/es6';
4 |
5 | import AssetActions from '../../../lib/js/react/actions/asset_actions';
6 |
7 | import AssetActionPanel from '../../../lib/js/react/components/asset_action_panel';
8 | import AssetDetail from '../../../lib/js/react/components/asset_detail';
9 |
10 |
11 | const AssetRow = React.createClass({
12 | propTypes: {
13 | accountList: React.PropTypes.array,
14 | activeAccount: React.PropTypes.object,
15 | asset: React.PropTypes.object,
16 | assetClass: React.PropTypes.string,
17 | handleAssetClick: React.PropTypes.func,
18 | isActive: React.PropTypes.bool
19 | },
20 |
21 | getInitialState() {
22 | return {
23 | inTransfer: false
24 | };
25 | },
26 |
27 | handleAssetClick() {
28 | const {
29 | asset,
30 | handleAssetClick
31 | } = this.props;
32 | safeInvoke(handleAssetClick, asset);
33 | },
34 |
35 | handleTransferClick(selectedAccount) {
36 | const {
37 | asset,
38 | activeAccount
39 | } = this.props;
40 |
41 | const idToTransfer = {
42 | txid: asset.id,
43 | cid: 0
44 | };
45 |
46 | const payloadToPost = {
47 | source: activeAccount,
48 | to: selectedAccount
49 | };
50 |
51 | AssetActions.transferAsset({
52 | idToTransfer,
53 | payloadToPost,
54 | account: activeAccount
55 | });
56 |
57 | this.setState({ inTransfer: true });
58 | },
59 |
60 | render() {
61 | const {
62 | asset,
63 | activeAccount,
64 | accountList,
65 | assetClass,
66 | isActive
67 | } = this.props;
68 |
69 | const {
70 | inTransfer
71 | } = this.state;
72 |
73 | const { data: { payload: { content } } = {} } = asset.transaction;
74 |
75 | let actionsPanel = null;
76 | if (isActive && activeAccount && accountList && !inTransfer) {
77 | actionsPanel = (
78 |
82 | );
83 | }
84 |
85 | return (
86 |
92 | {actionsPanel}
93 |
94 | );
95 | }
96 | });
97 |
98 | export default AssetRow;
99 |
--------------------------------------------------------------------------------
/client/share_trader/js/components/assets.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import AssetRow from './asset_row';
4 | import Spinner from '../../../lib/js/react/components/spinner';
5 |
6 |
7 | const Assets = ({
8 | accountList,
9 | activeAccount,
10 | activeAsset,
11 | assetClasses,
12 | assetList,
13 | handleAssetClick
14 | }) => {
15 | if (assetList && assetList.length) {
16 | // re-sorting assetList because assets of multiple accounts possible
17 | return (
18 |
19 | {assetList.sort((a, b) => {
20 | if (a.transaction.timestamp === b.transaction.timestamp) {
21 | if (a.id < b.id) {
22 | return -1;
23 | } else {
24 | return a.id > b.id ? 1 : 0;
25 | }
26 | }
27 | return a.transaction.timestamp - b.transaction.timestamp;
28 | })
29 | .map((asset) => {
30 | const isActive = !!activeAsset && activeAsset.id === asset.id;
31 | const assetClass = assetClasses[asset.transaction.conditions[0].new_owners[0]];
32 |
33 | return (
34 |
42 | );
43 | })}
44 |
45 | );
46 | } else {
47 | return (
48 |
49 |
50 |
51 | );
52 | }
53 | };
54 |
55 | Assets.propTypes = {
56 | accountList: React.PropTypes.array,
57 | activeAccount: React.PropTypes.object,
58 | activeAsset: React.PropTypes.object,
59 | assetClasses: React.PropTypes.object,
60 | assetList: React.PropTypes.array,
61 | handleAssetClick: React.PropTypes.func
62 | };
63 |
64 | export default Assets;
65 |
--------------------------------------------------------------------------------
/client/share_trader/js/components/share_trader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navbar, Row, Col, Button } from 'react-bootstrap/lib';
3 |
4 | import AccountList from '../../../lib/js/react/components/account_list';
5 | import AccountDetail from '../../../lib/js/react/components/account_detail';
6 |
7 | import Assets from './assets';
8 | import AssetMatrix from './asset_matrix';
9 |
10 | import AssetActions from '../../../lib/js/react/actions/asset_actions';
11 |
12 | import BigchainDBConnection from '../../../lib/js/react/components/bigchaindb_connection';
13 |
14 |
15 | const ShareTrader = React.createClass({
16 | propTypes: {
17 | // Injected through BigchainDBConnection
18 | accountList: React.PropTypes.array,
19 | activeAccount: React.PropTypes.object,
20 | activeAsset: React.PropTypes.object,
21 | assetList: React.PropTypes.object,
22 | handleAccountChange: React.PropTypes.func,
23 | handleAssetChange: React.PropTypes.func,
24 | resetActiveAccount: React.PropTypes.func
25 | },
26 |
27 | fetchAssetList({ account }) {
28 | AssetActions.fetchAssetList({
29 | account,
30 | blockWhenFetching: false
31 | });
32 | },
33 |
34 | mapAccountsOnStates(accountList) {
35 | const states = {
36 | 'default': 'available'
37 | };
38 |
39 | if (!accountList) {
40 | return states;
41 | }
42 |
43 | for (let i = 0; i < accountList.length; i++) {
44 | states[accountList[i].vk] = `state${i}`;
45 | }
46 |
47 | return states;
48 | },
49 |
50 | flattenAssetList(assetList) {
51 | return [].concat(...Object.values(assetList));
52 | },
53 |
54 | render() {
55 | const {
56 | activeAccount,
57 | accountList,
58 | activeAsset,
59 | assetList,
60 | handleAccountChange,
61 | handleAssetChange,
62 | resetActiveAccount
63 | } = this.props;
64 |
65 | const states = this.mapAccountsOnStates(accountList);
66 | const assetListForAccount =
67 | activeAccount && assetList.hasOwnProperty(activeAccount.vk) ?
68 | assetList[activeAccount.vk] : this.flattenAssetList(assetList);
69 |
70 | return (
71 |
72 |
73 | Share Trader
74 |
75 |
76 |
93 |
94 |
95 |
96 |
97 |
107 |
108 |
109 |
116 |
117 |
118 |
119 |
120 |
121 |
122 | );
123 | }
124 | });
125 |
126 | export default BigchainDBConnection(ShareTrader);
127 |
--------------------------------------------------------------------------------
/client/share_trader/scss/custom_style.scss:
--------------------------------------------------------------------------------
1 | .share-trader {
2 | $colorAvailable: #ECF0F1;
3 | $colorGrid: #BDC3C7;
4 | $colorState0: #3498DB;
5 | $colorState1: #39418b;
6 | $colorState2: #E74C3C;
7 | $colorState3: #34495E;
8 | $colorState4: #1ABC9C;
9 | $colorState5: #ff8600;
10 | $colorState6: #437c2b;
11 | $colorState7: #adaf35;
12 | $colorState8: #5bbc50;
13 |
14 | $colorList: (
15 | ("available", $colorAvailable),
16 | ("state0", $colorState0),
17 | ("state1", $colorState1),
18 | ("state2", $colorState2),
19 | ("state3", $colorState3),
20 | ("state4", $colorState4),
21 | ("state5", $colorState5),
22 | ("state6", $colorState6),
23 | ("state7", $colorState7),
24 | ("state8", $colorState8)
25 | );
26 |
27 | .Matrix {
28 | border: 1px solid black;
29 |
30 | .Grid {
31 | stroke: $colorGrid;
32 | }
33 |
34 | .Cell {
35 | @each $colorMap in $colorList {
36 | $state: nth($colorMap, 1);
37 | $color: nth($colorMap, 2);
38 |
39 | &.#{$state} {
40 | fill: $color;
41 | }
42 | }
43 | }
44 | .available {
45 | stroke: black;
46 | stroke-width: 0.1;
47 | }
48 | }
49 |
50 | @each $colorMap in $colorList {
51 | $state: nth($colorMap, 1);
52 | $color: nth($colorMap, 2);
53 |
54 | .asset-container.#{$state} {
55 | .asset-container-id,
56 | .asset-container-timestamp .glyphicon {
57 | color: $color
58 | }
59 | &.active {
60 | background: rgba($color, 0.1);
61 | border-left-color: $color;
62 | }
63 | &:hover {
64 | background: rgba($color, 0.05);
65 | border-left-color: rgba($color, 0.6);
66 | cursor: pointer;
67 | }
68 | &.inTransfer {
69 | background: rgba($color, 0.05);
70 | border-left-color: rgba($color, 0.4);
71 | cursor: wait;
72 | }
73 | }
74 | }
75 |
76 | .asset-container {
77 | width: 100%
78 | }
79 |
80 | .asset-history {
81 | height: calc(100vh - 70px);
82 | overflow-y: scroll;
83 | padding: 0;
84 | }
85 |
86 | .asset-matrix {
87 | border-right: 1px solid #888888;
88 | height: 100%;
89 | }
90 |
91 | .page-content {
92 | height: calc(100vh - 70px);
93 | padding-right: 0;
94 |
95 | .row {
96 | height: 100%;
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/client/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable strict, no-console, object-shorthand */
2 | /* eslint-disable import/no-extraneous-dependencies, import/newline-after-import */
3 | 'use strict';
4 |
5 | const path = require('path');
6 |
7 | const webpack = require('webpack');
8 | const autoPrefixer = require('autoprefixer');
9 | const combineLoaders = require('webpack-combine-loaders');
10 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
11 | const HtmlWebpackPlugin = require('html-webpack-plugin');
12 |
13 | require('dotenv').load({ path: '../.env', silent: true });
14 |
15 | const PRODUCTION = process.env.NODE_ENV === 'production';
16 | const EXTRACT = process.env.NODE_ENV === 'extract';
17 |
18 | const PATHS = {
19 | ON_THE_RECORD: path.resolve(__dirname, 'on_the_record/js/app.js'),
20 | SHARE_TRADER: path.resolve(__dirname, 'share_trader/js/app.js'),
21 | INTERLEDGER: path.resolve(__dirname, 'interledger/js/app.js'),
22 |
23 | BUILD: path.resolve(__dirname, 'build'),
24 | DIST: path.resolve(__dirname, 'dist'),
25 | NODE_MODULES: path.resolve(__dirname, 'node_modules'),
26 | };
27 |
28 |
29 | /** ENTRY POINTS **/
30 | const ENTRY = {
31 | // Use one entry per app
32 | ontherecord: PATHS.ON_THE_RECORD,
33 | sharetrader: PATHS.SHARE_TRADER,
34 | interledger: PATHS.INTERLEDGER,
35 | };
36 |
37 | const ENTRY_NAMES = {
38 | ontherecord: 'On the Record',
39 | sharetrader: 'Share Trader',
40 | interledger: 'Interledger',
41 | };
42 |
43 |
44 | /** EXTERNAL DEFINITIONS INJECTED INTO APP **/
45 | const DEFINITIONS = {
46 | 'process.env': {
47 | NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
48 | FLASK_BASE_URL: JSON.stringify(`http://${process.env.FLASK_HOST || 'localhost'}:` +
49 | `${process.env.FLASK_PORT || '8000'}`),
50 | },
51 | };
52 |
53 |
54 | /** PLUGINS **/
55 | const PLUGINS = [
56 | new webpack.DefinePlugin(DEFINITIONS),
57 | new webpack.NoErrorsPlugin(),
58 | ];
59 |
60 | const PROD_PLUGINS = [
61 | new webpack.optimize.UglifyJsPlugin({
62 | compress: {
63 | warnings: false
64 | },
65 | output: {
66 | comments: false
67 | },
68 | sourceMap: true,
69 | }),
70 | new webpack.LoaderOptionsPlugin({
71 | debug: false,
72 | minimize: true
73 | }),
74 | ];
75 |
76 | const EXTRACT_CSS_PLUGIN = new ExtractTextPlugin(
77 | PRODUCTION ? '[name]/styles.min.css' : '[name]/styles.css', {
78 | allChunks: true
79 | }
80 | );
81 |
82 | // Generate html files for each of the example apps specified in ENTRY
83 | const HTML_PLUGINS = Object.keys(ENTRY).map((entryName) => (
84 | new HtmlWebpackPlugin({
85 | filename: `${entryName}/index.html`,
86 | title: `${ENTRY_NAMES[entryName]} - powered by BigchainDB`,
87 | chunks: [entryName],
88 | minify: PRODUCTION ? {
89 | collapseWhitespace: true,
90 | minifyJS: true,
91 | removeComments: true,
92 | removeRedundantAttributes: true
93 | } : false,
94 | template: path.resolve(__dirname, 'app_index_template.html'),
95 |
96 | // Our own options
97 | PRODUCTION: PRODUCTION
98 | })
99 | ));
100 |
101 | PLUGINS.push(...HTML_PLUGINS);
102 |
103 | if (EXTRACT || PRODUCTION) {
104 | PLUGINS.push(EXTRACT_CSS_PLUGIN);
105 | }
106 |
107 | if (PRODUCTION) {
108 | PLUGINS.push(...PROD_PLUGINS);
109 | }
110 |
111 |
112 | /** LOADERS **/
113 | const JS_LOADER = combineLoaders([
114 | {
115 | loader: 'babel',
116 | query: {
117 | cacheDirectory: true,
118 | },
119 | },
120 | ]);
121 |
122 | const CSS_LOADER = combineLoaders([
123 | {
124 | loader: 'css',
125 | query: {
126 | sourceMap: true
127 | }
128 | },
129 | { loader: 'postcss' },
130 | {
131 | loader: 'sass',
132 | query: {
133 | precision: '8', // See https://github.com/twbs/bootstrap-sass#sass-number-precision
134 | outputStyle: 'expanded',
135 | sourceMap: true
136 | }
137 | },
138 | ]);
139 |
140 | const LOADERS = [
141 | {
142 | test: /\.jsx?$/,
143 | exclude: [PATHS.NODE_MODULES],
144 | loader: JS_LOADER,
145 | },
146 | {
147 | test: /\.json$/,
148 | loader: 'json'
149 | },
150 | {
151 | test: /\.s[ac]ss$/,
152 | exclude: [PATHS.NODE_MODULES],
153 | loader: PRODUCTION || EXTRACT ? ExtractTextPlugin.extract('style', CSS_LOADER)
154 | : `style!${CSS_LOADER}`,
155 | },
156 | {
157 | test: /.(png|woff(2)?|eot|ttf|svg)(\?[a-z0-9=\.]+)?$/,
158 | loader: 'url-loader?limit=100000'
159 | },
160 | ];
161 |
162 |
163 | /** EXPORTED WEBPACK CONFIG **/
164 | module.exports = {
165 | entry: ENTRY,
166 |
167 | output: {
168 | filename: PRODUCTION ? '[name]/bundle.min.js' : '[name]/bundle.js',
169 | path: PRODUCTION ? PATHS.DIST : PATHS.BUILD,
170 | },
171 |
172 | debug: !PRODUCTION,
173 |
174 | devtool: PRODUCTION ? '#source-map' : '#inline-source-map',
175 |
176 | resolve: {
177 | alias: {
178 | 'babel-runtime': path.resolve(PATHS.NODE_MODULES, 'babel-runtime'),
179 | 'core-js': path.resolve(PATHS.NODE_MODULES, 'core-js'),
180 | },
181 | extensions: ['', '.js', '.jsx'],
182 | modules: ['node_modules'], // Don't use absolute path here to allow recursive matching
183 | },
184 |
185 | plugins: PLUGINS,
186 |
187 | module: {
188 | loaders: LOADERS,
189 | },
190 |
191 | postcss: [autoPrefixer()],
192 | };
193 |
--------------------------------------------------------------------------------
/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/commands/__init__.py
--------------------------------------------------------------------------------
/commands/bigchaindb_examples.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import argparse
4 | import logging
5 | import subprocess
6 |
7 | import rethinkdb as r
8 | from bigchaindb import Bigchain
9 |
10 | from init_accounts import main as init_accounts_main
11 | from init_assets import main as init_assets_main
12 | from apps_config import APPS
13 |
14 | logging.basicConfig(level=logging.INFO)
15 | logger = logging.getLogger(__name__)
16 |
17 |
18 | def start(parser, scope):
19 | """Utility function to execute a subcommand.
20 | The function will look up in the ``scope``
21 | if there is a function called ``run_``
22 | and will run it using ``parser.args`` as first positional argument.
23 | Args:
24 | parser: an ArgumentParser instance.
25 | scope (dict): map containing (eventually) the functions to be called.
26 | Raises:
27 | NotImplementedError: if ``scope`` doesn't contain a function called
28 | ``run_``.
29 | """
30 | args = parser.parse_args()
31 |
32 | if not args.command:
33 | parser.print_help()
34 | return
35 |
36 | # look up in the current scope for a function called 'run_'
37 | # replacing all the dashes '-' with the lowercase character '_'
38 | func = scope.get('run_' + args.command.replace('-', '_'))
39 |
40 | # if no command has been found, raise a `NotImplementedError`
41 | if not func:
42 | raise NotImplementedError('Command `{}` not yet implemented'.
43 | format(args.command))
44 |
45 | func(args)
46 |
47 |
48 | def delete_databases(dbnames=[]):
49 | b = Bigchain()
50 |
51 | for dbname in dbnames:
52 | logger.info('Dropping database: {}'.format(dbname))
53 | try:
54 | r.db_drop(dbname).run(b.conn)
55 | except r.ReqlOpFailedError as e:
56 | logger.info(e.message)
57 |
58 |
59 | def init_ledgers(ledger_ids=[]):
60 | for ledger_id in ledger_ids:
61 | my_env = os.environ.copy()
62 | bigchaindb_db_name = 'bigchaindb_examples_{}'.format(ledger_id)
63 | logger.info('Initializing ledger {}'.format(bigchaindb_db_name))
64 | my_env['BIGCHAINDB_DATABASE_NAME'] = bigchaindb_db_name
65 | subprocess.Popen(['bigchaindb', '-yc', '.bigchaindb_examples', 'configure'], env=my_env).wait()
66 | subprocess.Popen(['bigchaindb', '-c', '.bigchaindb_examples', 'init'], env=my_env).wait()
67 |
68 |
69 | def start_services(ledger_num):
70 | procs = []
71 |
72 | # setup env
73 | frontend_port = 3000 + ledger_num
74 | flask_port = 8000 + ledger_num
75 | tornado_port = 8888 + ledger_num
76 | bigchaindb_db_name = 'bigchaindb_examples_{}'.format(ledger_num)
77 | bigchaindb_server_bind = 'localhost:{}'.format(9984 + ledger_num)
78 |
79 | my_env = os.environ.copy()
80 | my_env['CLIENT_PORT'] = str(frontend_port)
81 | my_env['FLASK_PORT'] = str(flask_port)
82 | my_env['TORNADO_PORT'] = str(tornado_port)
83 | my_env['BIGCHAINDB_DATABASE_NAME'] = bigchaindb_db_name
84 | my_env['BIGCHAINDB_SERVER_BIND'] = bigchaindb_server_bind
85 | my_env['BIGCHAINDB_LEDGER_NUMBER'] = str(ledger_num)
86 |
87 | # start flask
88 | p_flask = subprocess.Popen(['python', '-m', 'server.app'], env=my_env)
89 | procs.append(p_flask)
90 |
91 | # start tornado
92 | p_tornado = subprocess.Popen(['python', '-m', 'server.tornado_app'], env=my_env)
93 | procs.append(p_tornado)
94 |
95 | # start bigchaindb
96 | p_bigchaindb = subprocess.Popen(['bigchaindb', '-c', '.bigchaindb_examples', 'start'], env=my_env)
97 | procs.append(p_bigchaindb)
98 |
99 | return procs
100 |
101 |
102 | def start_connectors():
103 | # start connectors
104 | return [subprocess.Popen(['python', '-m', 'server.lib.models.connector'])]
105 |
106 |
107 | def get_ledger_ids_from_config(config):
108 | # read the config file and return all ledger ids
109 | ledger_ids = []
110 | for app in config:
111 | if app['name'] != 'interledger':
112 | ledger_ids.append(app['ledger'])
113 | else:
114 | for account in app['accounts']:
115 | for ledger in account['ledgers']:
116 | ledger_ids.append(ledger['id'])
117 |
118 | return list(set(ledger_ids))
119 |
120 |
121 | def run_init_bigchaindb(args):
122 | # initialize the databases for ledger args.ledger
123 | ledger_ids = []
124 | if args.ledger:
125 | ledger_ids = [args.ledger]
126 | elif args.all:
127 | ledger_ids = get_ledger_ids_from_config(APPS)
128 |
129 | init_ledgers(ledger_ids)
130 |
131 |
132 | def run_reset_bigchaindb(args):
133 | # delete databases for ledger args.ledger or all
134 | b = Bigchain()
135 |
136 | # dbs do delete
137 | dbnames = []
138 | if args.ledger:
139 | dbnames = ['bigchaindb_examples_{}'.format(args.ledger)]
140 | elif args.all:
141 | regex_db = re.compile(r'^(bigchaindb_examples_\d*$)')
142 | for dbname in r.db_list().run(b.conn):
143 | if regex_db.match(dbname):
144 | dbnames.append(dbname)
145 |
146 | delete_databases(dbnames)
147 |
148 |
149 | def run_init_accounts(args):
150 | init_accounts_main()
151 |
152 |
153 | def run_reset_accounts(args):
154 | delete_databases(['interledger', 'ontherecord', 'sharetrader'])
155 |
156 |
157 | def run_init_assets(args):
158 | init_assets_main()
159 |
160 |
161 | def run_start(args):
162 | # check if we need to initialize
163 | if args.init:
164 | init_args = argparse.Namespace()
165 | run_init_all(init_args)
166 |
167 | ledger_ids = []
168 | if args.ledger:
169 | ledger_ids = [args.ledger]
170 | elif args.all:
171 | ledger_ids = get_ledger_ids_from_config(APPS)
172 |
173 | procs = []
174 | for ledger in ledger_ids:
175 | procs += start_services(ledger)
176 |
177 | # start npm
178 | p_npm = subprocess.Popen(['npm', 'start'], cwd='./client/')
179 | procs.append(p_npm)
180 |
181 | procs += start_connectors()
182 |
183 | # wait for processes to finish
184 | for proc in procs:
185 | proc.wait()
186 |
187 |
188 | def run_reset_all(args):
189 | # reset bigchaindb
190 | args = argparse.Namespace(all=True, command='reset-bigchaindb', ledger=None)
191 | run_reset_bigchaindb(args)
192 |
193 | # reset accounts
194 | args = argparse.Namespace()
195 | run_reset_accounts(args)
196 |
197 |
198 | def run_init_all(args):
199 | # init bigchaindb
200 | args = argparse.Namespace(all=True, command='init-bigchaindb', ledger=None)
201 | run_init_bigchaindb(args)
202 |
203 | # init accounts
204 | args = argparse.Namespace()
205 | run_init_accounts(args)
206 |
207 | # init assets
208 | args = argparse.Namespace()
209 | run_init_assets(args)
210 |
211 |
212 | def main():
213 | parser = argparse.ArgumentParser(prog='bigchaindb-examples',
214 | description='Run bigchaindb examples')
215 |
216 | subparser = parser.add_subparsers(title='Commands',
217 | dest='command')
218 |
219 | # Start services
220 | start_parser = subparser.add_parser('start',
221 | help='start a new ledger')
222 | start_parser.add_argument('-l', '--ledger',
223 | type=int,
224 | help='Start the services for the provided ledger')
225 | start_parser.add_argument('-a', '--all',
226 | default=False,
227 | action='store_true',
228 | help='Start the services for all ledgers')
229 | start_parser.add_argument('-i', '--init',
230 | default=False,
231 | action='store_true',
232 | help='First initialize and then start the services for all ledgers')
233 |
234 | # Initialize bigchaindb
235 | init_bigchaindb_parser = subparser.add_parser('init-bigchaindb',
236 | help='Initialize a new bigchaindb ledger')
237 | init_bigchaindb_parser.add_argument('-l', '--ledger',
238 | type=int,
239 | help='Initialize the databases for a ledger')
240 | init_bigchaindb_parser.add_argument('-a', '--all',
241 | default=False,
242 | action='store_true',
243 | help='Initialize all databases for the ledgers')
244 |
245 | # Reset bigchaindb
246 | reset_bigchaindb_parser = subparser.add_parser('reset-bigchaindb',
247 | help='Delete the bigchaindb ledger')
248 | reset_bigchaindb_parser.add_argument('-l', '--ledger',
249 | type=int,
250 | help='Delete the bigchaindb ledger with the number provided')
251 | reset_bigchaindb_parser.add_argument('-a', '--all',
252 | default=False,
253 | action='store_true',
254 | help='Delete all the bigchaindb ledgers')
255 |
256 | # Initialize accounts
257 | subparser.add_parser('init-accounts',
258 | help='Initialize accounts for all the apps')
259 |
260 | # Reset accounts
261 | subparser.add_parser('reset-accounts',
262 | help='Delete the accounts databases')
263 |
264 | # Initialize assets
265 | subparser.add_parser('init-assets',
266 | help='Initialize assets for all the apps in all the ledgers')
267 |
268 | # Initialize everything
269 | subparser.add_parser('init-all',
270 | help='Initializes all the databases for apps, ledgers and assets')
271 |
272 | # Reset everything
273 | subparser.add_parser('reset-all',
274 | help='Deletes all databases created by apps and all the ledgers')
275 |
276 | start(parser, globals())
277 |
278 |
279 | if __name__ == '__main__':
280 | main()
281 |
--------------------------------------------------------------------------------
/compose/frontend/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:6
2 |
3 | RUN mkdir -p /usr/src/app
4 | WORKDIR /usr/src/app/client
5 |
6 | COPY client /usr/src/app/client
7 |
8 | # Adds fs-extra to npm and replaces the fs.rename method with the fs.extra
9 | # move method that now automatic chooses what to do (rename/move).
10 | # See https://github.com/npm/npm/issues/9863.
11 | RUN cd $(npm root -g)/npm \
12 | && npm install fs-extra \
13 | && sed -i -e s/graceful-fs/fs-extra/ -e s/fs\.rename/fs\.move/ ./lib/utils/rename.js
14 |
15 | # On some platforms, the .dockerignore file is being ignored in some versions of docker-compose
16 | # See https://github.com/docker/compose/issues/1607.
17 | RUN rm -rf node_modules
18 |
19 | RUN npm install
20 |
--------------------------------------------------------------------------------
/compose/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3
2 |
3 | RUN mkdir -p /usr/src/app
4 | WORKDIR /usr/src/app
5 |
6 | COPY setup.py /usr/src/app/
7 | COPY server /usr/src/app/server
8 |
9 | RUN pip install --upgrade pip
10 | RUN pip install --no-cache-dir -e .[dev]
11 |
--------------------------------------------------------------------------------
/docs.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 |
5 | bdocs:
6 | build:
7 | context: .
8 | dockerfile: Dockerfile-bdb
9 | volumes:
10 | - .:/usr/src/app/
11 | working_dir: /usr/src/app/docs
12 | command: make html
13 |
14 | vdocs:
15 | image: nginx
16 | ports:
17 | - '41234:80'
18 | volumes:
19 | - ./docs/build/html:/usr/share/nginx/html
20 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don\'t have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
21 |
22 | .PHONY: help
23 | help:
24 | @echo "Please use \`make ' where is one of"
25 | @echo " html to make standalone HTML files"
26 | @echo " dirhtml to make HTML files named index.html in directories"
27 | @echo " singlehtml to make a single large HTML file"
28 | @echo " pickle to make pickle files"
29 | @echo " json to make JSON files"
30 | @echo " htmlhelp to make HTML files and a HTML help project"
31 | @echo " qthelp to make HTML files and a qthelp project"
32 | @echo " applehelp to make an Apple Help Book"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " epub3 to make an epub3"
36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
37 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
39 | @echo " text to make text files"
40 | @echo " man to make manual pages"
41 | @echo " texinfo to make Texinfo files"
42 | @echo " info to make Texinfo files and run them through makeinfo"
43 | @echo " gettext to make PO message catalogs"
44 | @echo " changes to make an overview of all changed/added/deprecated items"
45 | @echo " xml to make Docutils-native XML files"
46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
47 | @echo " linkcheck to check all external links for integrity"
48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
49 | @echo " coverage to run coverage check of the documentation (if enabled)"
50 | @echo " dummy to check syntax errors of document sources"
51 |
52 | .PHONY: clean
53 | clean:
54 | rm -rf $(BUILDDIR)/*
55 |
56 | .PHONY: html
57 | html:
58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
61 |
62 | .PHONY: dirhtml
63 | dirhtml:
64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
65 | @echo
66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
67 |
68 | .PHONY: singlehtml
69 | singlehtml:
70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
71 | @echo
72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
73 |
74 | .PHONY: pickle
75 | pickle:
76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
77 | @echo
78 | @echo "Build finished; now you can process the pickle files."
79 |
80 | .PHONY: json
81 | json:
82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
83 | @echo
84 | @echo "Build finished; now you can process the JSON files."
85 |
86 | .PHONY: htmlhelp
87 | htmlhelp:
88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
89 | @echo
90 | @echo "Build finished; now you can run HTML Help Workshop with the" \
91 | ".hhp project file in $(BUILDDIR)/htmlhelp."
92 |
93 | .PHONY: qthelp
94 | qthelp:
95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
96 | @echo
97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/BigchainDBExamples.qhcp"
100 | @echo "To view the help file:"
101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/BigchainDBExamples.qhc"
102 |
103 | .PHONY: applehelp
104 | applehelp:
105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
106 | @echo
107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
108 | @echo "N.B. You won't be able to view it unless you put it in" \
109 | "~/Library/Documentation/Help or install it in your application" \
110 | "bundle."
111 |
112 | .PHONY: devhelp
113 | devhelp:
114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
115 | @echo
116 | @echo "Build finished."
117 | @echo "To view the help file:"
118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/BigchainDBExamples"
119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/BigchainDBExamples"
120 | @echo "# devhelp"
121 |
122 | .PHONY: epub
123 | epub:
124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
125 | @echo
126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
127 |
128 | .PHONY: epub3
129 | epub3:
130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
131 | @echo
132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
133 |
134 | .PHONY: latex
135 | latex:
136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
137 | @echo
138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
140 | "(use \`make latexpdf' here to do that automatically)."
141 |
142 | .PHONY: latexpdf
143 | latexpdf:
144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
145 | @echo "Running LaTeX files through pdflatex..."
146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
148 |
149 | .PHONY: latexpdfja
150 | latexpdfja:
151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
152 | @echo "Running LaTeX files through platex and dvipdfmx..."
153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
155 |
156 | .PHONY: text
157 | text:
158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
159 | @echo
160 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
161 |
162 | .PHONY: man
163 | man:
164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
165 | @echo
166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
167 |
168 | .PHONY: texinfo
169 | texinfo:
170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
171 | @echo
172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
173 | @echo "Run \`make' in that directory to run these through makeinfo" \
174 | "(use \`make info' here to do that automatically)."
175 |
176 | .PHONY: info
177 | info:
178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
179 | @echo "Running Texinfo files through makeinfo..."
180 | make -C $(BUILDDIR)/texinfo info
181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
182 |
183 | .PHONY: gettext
184 | gettext:
185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
186 | @echo
187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
188 |
189 | .PHONY: changes
190 | changes:
191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
192 | @echo
193 | @echo "The overview file is in $(BUILDDIR)/changes."
194 |
195 | .PHONY: linkcheck
196 | linkcheck:
197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
198 | @echo
199 | @echo "Link check complete; look for any errors in the above output " \
200 | "or in $(BUILDDIR)/linkcheck/output.txt."
201 |
202 | .PHONY: doctest
203 | doctest:
204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
205 | @echo "Testing of doctests in the sources finished, look at the " \
206 | "results in $(BUILDDIR)/doctest/output.txt."
207 |
208 | .PHONY: coverage
209 | coverage:
210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
211 | @echo "Testing of coverage in the sources finished, look at the " \
212 | "results in $(BUILDDIR)/coverage/python.txt."
213 |
214 | .PHONY: xml
215 | xml:
216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
217 | @echo
218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
219 |
220 | .PHONY: pseudoxml
221 | pseudoxml:
222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
223 | @echo
224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
225 |
226 | .PHONY: dummy
227 | dummy:
228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
229 | @echo
230 | @echo "Build finished. Dummy builder generates no files."
231 |
--------------------------------------------------------------------------------
/docs/img/on_the_record_v0.0.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/img/on_the_record_v0.0.1.png
--------------------------------------------------------------------------------
/docs/img/share_trader_v0.0.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/img/share_trader_v0.0.1.png
--------------------------------------------------------------------------------
/docs/on_the_record.md:
--------------------------------------------------------------------------------
1 | # On The Record
--------------------------------------------------------------------------------
/docs/share_trade.md:
--------------------------------------------------------------------------------
1 | # Share Trade
--------------------------------------------------------------------------------
/docs/source/_static/ontherecord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/source/_static/ontherecord.png
--------------------------------------------------------------------------------
/docs/source/_static/sharetrader.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/docs/source/_static/sharetrader.png
--------------------------------------------------------------------------------
/docs/source/ack.rst:
--------------------------------------------------------------------------------
1 | Acknowledgements
2 | ================
3 | Special thanks to the BigchainDB/ascribe.io team for their insights and code
4 | contributions:
5 |
6 | `@r-marques`_, `@vrde`_, `@ttmc`_, `@rhsimplex`_, `@SohKai`_, `@sbellem`_, `@TimDaub`_, `@diminator`_
7 |
8 |
9 | .. _@r-marques: https://github.com/r-marques
10 | .. _@vrde: https://github.com/vrde
11 | .. _@ttmc: https://github.com/ttmc
12 | .. _@rhsimplex: https://github.com/rhsimplex
13 | .. _@SohKai: https://github.com/SohKai
14 | .. _@sbellem: https://github.com/sbellem
15 | .. _@TimDaub: https://github.com/TimDaub
16 | .. _@diminator: https://github.com/diminator
17 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 |
4 | import sphinx_rtd_theme
5 |
6 |
7 | extensions = [
8 | 'sphinx.ext.autodoc',
9 | 'sphinx.ext.doctest',
10 | 'sphinx.ext.intersphinx',
11 | 'sphinx.ext.todo',
12 | 'sphinx.ext.coverage',
13 | 'sphinx.ext.viewcode',
14 | 'sphinx.ext.napoleon',
15 | ]
16 |
17 | templates_path = ['_templates']
18 | source_suffix = '.rst'
19 | master_doc = 'index'
20 | project = 'BigchainDB Examples'
21 | copyright = '2016, BigchainDB Contributors'
22 | author = 'BigchainDB Contributors'
23 | version = '0.0.2'
24 | release = '0.0.2'
25 | language = None
26 | exclude_patterns = []
27 | pygments_style = 'sphinx'
28 | todo_include_todos = True
29 | html_theme = 'sphinx_rtd_theme'
30 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
31 | html_static_path = ['_static']
32 | htmlhelp_basename = 'BigchainDBExamplesdoc'
33 |
34 | latex_elements = {}
35 | latex_documents = [
36 | (master_doc, 'BigchainDBExamples.tex', 'BigchainDB Examples Documentation',
37 | 'BigchainDB Contributors', 'manual'),
38 | ]
39 |
40 | man_pages = [
41 | (master_doc, 'bigchaindbexamples', 'BigchainDB Examples Documentation',
42 | [author], 1)
43 | ]
44 |
45 | texinfo_documents = [
46 | (master_doc, 'BigchainDBExamples', 'BigchainDB Examples Documentation',
47 | author, 'BigchainDBExamples', 'One line description of project.',
48 | 'Miscellaneous'),
49 | ]
50 |
51 | intersphinx_mapping = {'https://docs.python.org/': None}
52 |
--------------------------------------------------------------------------------
/docs/source/docs.rst:
--------------------------------------------------------------------------------
1 | About this Documentation
2 | ========================
3 |
4 | This section contains instructions to build and view the documentation locally,
5 | using the ``docs.yml`` file of the `bigchaindb-examples`_ repository.
6 |
7 | If you do not have a clone of the repo, you need to get one.
8 |
9 |
10 | Building the documentation
11 | --------------------------
12 | To build the docs, simply run
13 |
14 | .. code-block:: bash
15 |
16 | $ docker-compose -f docs.yml up bdocs
17 |
18 | Or if you prefer, start a ``bash`` session,
19 |
20 | .. code-block:: bash
21 |
22 | $ docker-compose -f docs.yml run --rm bdocs bash
23 |
24 | and build the docs:
25 |
26 | .. code-block:: bash
27 |
28 | root@a651959a1f2d:/usr/src/app/docs# make html
29 |
30 |
31 | Viewing the documentation
32 | -------------------------
33 | You can start a little web server to view the docs at http://localhost:41234/
34 |
35 | .. code-block:: bash
36 |
37 | $ docker-compose -f docs.yml up -d vdocs
38 |
39 | .. note:: If you are using ``docker-machine`` you need to replace ``localhost``
40 | with the ``ip`` of the machine (e.g.: ``docker-machine ip tm`` if your
41 | machine is named ``tm``).
42 |
43 |
44 | Making changes
45 | --------------
46 | The necessary source code is mounted, which allows you to make modifications,
47 | and view the changes by simply re-building the docs, and refreshing the
48 | browser.
49 |
50 |
51 | .. _bigchaindb-examples: https://github.com/bigchaindb/bigchaindb-examples
52 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | *******************
2 | BigchainDB Examples
3 | *******************
4 |
5 | Documentation for the BigchainDB examples and tutorials found under
6 | https://github.com/bigchaindb/bigchaindb-examples
7 |
8 | .. warning:: These examples are for demonstration purpose and should not be
9 | used for production
10 |
11 |
12 |
13 | Contents
14 | ========
15 |
16 | .. toctree::
17 | :maxdepth: 2
18 |
19 | structure
20 | install
21 | run
22 | ontherecord
23 | sharetrader
24 | interledger
25 | troubleshooting
26 | docs
27 | ack
28 |
29 |
30 | Indices and tables
31 | ==================
32 |
33 | * :ref:`genindex`
34 | * :ref:`modindex`
35 | * :ref:`search`
36 |
--------------------------------------------------------------------------------
/docs/source/install.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | Clone the repository:
5 |
6 | .. code-block:: bash
7 |
8 | $ git clone git@github.com:bigchaindb/bigchaindb-examples.git
9 |
10 | Go into it!
11 |
12 | .. code-block:: bash
13 |
14 | $ cd bigchaindb-examples
15 |
16 | We now document three options:
17 |
18 | * Installing via Docker (**recommended**); supports OSX
19 | * Installing from source via the CLI
20 | * Installing from source Manually
21 |
22 |
23 | The Docker Way
24 | --------------
25 | Just make sure you have recent versions of `docker engine`_ and
26 | `docker-compose`_, e.g.:
27 |
28 | .. code-block:: bash
29 |
30 | $ docker --version
31 | Docker version 1.11.1, build 5604cbe
32 |
33 | $ docker-compose --version
34 | docker-compose version 1.7.0, build 0d7bf73
35 |
36 |
37 | We've provided a `Makefile` to make starting the examples through Docker easy, so once you've set
38 | up your docker environment (e.g. starting docker-machine if necessary), simply:
39 |
40 | .. code-block:: bash
41 |
42 | # Make all the things! Build, inits, configures, and runs everything.
43 | $ make
44 |
45 |
46 | If you're using docker-machine instances (ie. on OSX / Windows), you should run `make` with your
47 | docker-machine ip:
48 |
49 | .. code-block:: bash
50 |
51 | $ DOCKER_MACHINE_IP=$(docker-machine ip) make
52 |
53 |
54 | The `Makefile` will automatically start the examples so just sit back and wait :)
55 |
56 | Note: we've renamed the `docker-compose.yml` to `ledgers.yml`, so if you want to do Docker builds manually, use `-f ledgers.yml`.
57 |
58 | Install from Source
59 | -------------------
60 |
61 | .. _dependencies:
62 |
63 | Dependencies
64 | ^^^^^^^^^^^^
65 |
66 | * OS dependencies: see `setup BigchainDB & RethinkDB `_
67 | * ``python>=3.4``
68 | * node>=5.3 using `nvm `_ (**recommended**), or
69 | `manually `_
70 | * `npm>=3.3 `_ (should be installed with node)
71 |
72 |
73 | Using the CLI
74 | ^^^^^^^^^^^^^
75 |
76 | This examples project includes a CLI tool to configure and start the project. If you'll be running
77 | things locally, it's **recommended** to use the CLI.
78 |
79 | .. code-block:: bash
80 |
81 | # (optional) Run a virtualenv (make sure you have a recent version)
82 | $ virtualenv venv -p python3
83 | $ source venv/bin/activate
84 |
85 | # Install server
86 | $ pip install -e .[dev]
87 |
88 | # (optional) Check out the CLI
89 | $ bigchaindb-examples --help
90 |
91 | # Initialize BigchainDB and load initial data
92 | $ bigchaindb-examples init --all
93 |
94 | # Install client dependencies
95 | $ cd client && npm install && cd -
96 |
97 |
98 | The CLI will handle any initialization that's necessary for the client and servers so you can skip
99 | to :ref:`run` to begin running the examples.
100 |
101 | .. _manual-setup:
102 |
103 | Manual Setup
104 | ^^^^^^^^^^^^
105 |
106 | Make sure you have all the :ref:`dependencies`.
107 |
108 | .. code-block:: bash
109 |
110 | # (optional) Run a virtualenv (make sure you have a recent version)
111 | $ virtualenv venv -p python3
112 | $ source venv/bin/activate
113 |
114 | # Install server
115 | $ pip install -e .[dev]
116 |
117 | # Make sure RethinkDB is running!
118 | # Configure and initialize BigchainDB with a different BIGCHAINDB_DATABASE_NAME for each ledger
119 | $ BIGCHAINDB_DATABASE_NAME=bigchaindb_examples_0 \
120 | bigchaindb -yc .bigchaindb_examples configure
121 | $ bigchaindb -c .bigchaindb_examples init
122 |
123 | $ BIGCHAINDB_DATABASE_NAME=bigchaindb_examples_1 \
124 | bigchaindb -yc .bigchaindb_examples configure
125 | $ bigchaindb -c .bigchaindb_examples init
126 |
127 | # Load initial data
128 | $ python3 init_accounts.py
129 | $ python3 init_assets.py
130 |
131 | # Install client dependencies
132 | $ cd client && npm install && cd -
133 |
134 |
135 | You should now be ready to run the examples. See :ref:`run` for instructions.
136 |
137 |
138 |
139 | .. _docker engine: https://www.docker.com/products/docker-engine
140 | .. _docker-compose: https://www.docker.com/products/docker-compose
141 |
--------------------------------------------------------------------------------
/docs/source/interledger.rst:
--------------------------------------------------------------------------------
1 | Interledger Lab
2 | ===============
3 |
4 | .. note:: **Work in progress**.
5 |
6 |
7 | Quickstart with Docker
8 | ----------------------
9 |
10 | .. code-block:: bash
11 |
12 | $ make
13 |
14 | If you are using ``docker-machine``, then:
15 |
16 |
17 | .. code-block:: bash
18 |
19 | $ DOCKER_MACHINE_IP=$(docker-machine ip ) make
20 |
21 | Where ```` is the name of the machine you created, e.g.:
22 |
23 | .. code-block:: bash
24 |
25 | $ docker-machine create --driver virtualbox
26 |
27 |
28 | Step by step with Docker
29 | ------------------------
30 |
31 | Build the services:
32 |
33 | .. code-block:: bash
34 |
35 | $ docker-compose -f ledgers.yml build
36 |
37 | Run RethinkDB in the background:
38 |
39 | .. code-block:: bash
40 |
41 | $ docker-compose -f ledgers.yml up -d rdb
42 |
43 |
44 | Configure each ledger:
45 |
46 | .. code-block:: bash
47 |
48 | $ docker-compose -f ledgers.yml run bdb-0 bigchaindb -y configure
49 | $ docker-compose -f ledgers.yml run bdb-1 bigchaindb -y configure
50 |
51 | Initialize each ledger:
52 |
53 | .. code-block:: bash
54 |
55 | $ docker-compose -f ledgers.yml run bdb-0 bigchaindb init
56 | $ docker-compose -f ledgers.yml run bdb-1 bigchaindb init
57 |
58 | Initialize the accounts and assets:
59 |
60 | .. code-block:: bash
61 |
62 | $ docker-compose -f ledgers.yml run bdb-0 python init_accounts.py
63 | $ docker-compose -f ledgers.yml run bdb-0 python init_assets.py
64 |
65 | .. note:: Since each ledger/service (``bdb-0``, ``bdb-1``) is connected to the
66 | same RethinkDB instance, the initialization commands can be run with either
67 | service (``bdb-0``, or ``bdb-1``).
68 |
69 | Start everything:
70 |
71 | .. code-block:: bash
72 |
73 | $ docker-compose -f ledgers.yml up
74 |
75 |
76 | To view each ledger in browser, visit:
77 |
78 | * ``bdb-0``: http://localhost:32800
79 | * ``bdb-1``: http://localhost:32810
80 |
81 | .. note:: Replace ``localhost`` with your docker-machine ip as necessary.
82 |
--------------------------------------------------------------------------------
/docs/source/ontherecord.rst:
--------------------------------------------------------------------------------
1 | .. _ontherecord:
2 |
3 | On the Record
4 | =============
5 |
6 | "On the Record" is a simple logging app, wrapped as a messaging board.
7 |
8 | .. image:: /_static/ontherecord.png
9 |
10 |
11 | Use cases
12 | ---------
13 |
14 | - Immutable logging of data
15 | - Notarization of data, text, emails
16 |
17 | Functionality
18 | -------------
19 |
20 | Create assets
21 | ^^^^^^^^^^^^^
22 |
23 | - With arbitrary payload
24 | - And an unlimited amount
25 |
26 | Retrieve assets
27 | ***************
28 |
29 | - That you currently own (like UTXO's)
30 | - By searching the asset data/payload
31 | - State indicator (in backlog vs. on bigchain)
32 |
33 | What this app doesn't provide
34 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 | - Proper user and key management
37 | - Transfer of assets
38 |
--------------------------------------------------------------------------------
/docs/source/run.rst:
--------------------------------------------------------------------------------
1 | .. _run:
2 |
3 | Running the Examples
4 | ====================
5 | Details about each app is documented under:
6 |
7 | * :ref:`ontherecord`
8 | * :ref:`sharetrader`
9 | * :ref:`interledger`
10 |
11 |
12 | Docker
13 | ------
14 |
15 | Use the provided `Makefile` to configure, initialize, and start running on Docker all in one go:
16 |
17 | .. code-block:: bash
18 |
19 | $ make
20 |
21 | Or, if you're using docker-machine instances (ie. on OSX / Windows),
22 |
23 | .. code-block:: bash
24 |
25 | $ DOCKER_MACHINE_IP=$(docker-machine ip) make
26 |
27 | You should be able to view the app at ` `_ (replace ``localhost`` with your
28 | docker-machine ip as necessary).
29 |
30 |
31 | Locally
32 | -------
33 |
34 | Using the CLI
35 | ^^^^^^^^^^^^^
36 |
37 | .. code-block:: bash
38 |
39 | $ bigchaindb-examples start --init --all
40 |
41 | Starting Everything Manually
42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
43 |
44 | Not for the faint of heart; use the CLI instead!
45 |
46 | You'll need to run at least two instances of BigchainDB along with a Flask and a Tornado server for
47 | each instance (Flask should be run under ports 8000 and 8001; Tornado should be run under 8888 and
48 | 8889).
49 |
50 | Running the javascript client
51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 | In one terminal, run ``npm start`` in ``client/`` to serve the client apps
53 |
54 | .. code-block:: bash
55 |
56 | $ cd client
57 | $ npm start
58 |
59 |
60 | Running BigchainDB
61 | ^^^^^^^^^^^^^^^^^^
62 | Launch ``BigchainDB`` with ``RethinkDB`` in a separate terminal
63 |
64 | .. code-block:: bash
65 |
66 | $ rethinkdb & # skip this if RethinkDB is already running
67 | $ bigchaindb -c .bigchaindb_examples start
68 |
69 |
70 | Running the App servers
71 | ^^^^^^^^^^^^^^^^^^^^^^^
72 | In another terminal, launch the ``flask`` server
73 |
74 | .. code-block:: bash
75 |
76 | $ python3 -m server.app
77 |
78 | In (yet) another terminal, launch the ``tornado`` server
79 |
80 | .. code-block:: bash
81 |
82 | $ python3 -m server.tornado_app
83 |
84 | You should be able to view the app at ` `_.
85 |
--------------------------------------------------------------------------------
/docs/source/sharetrader.rst:
--------------------------------------------------------------------------------
1 | .. _sharetrader:
2 |
3 | Share Trader
4 | ============
5 |
6 | Share Trader is a simple share allocation and trade app. Each square represents
7 | an asset that can be traded amongst accounts.
8 |
9 | .. image:: /_static/sharetrader.png
10 |
11 |
12 | Use cases
13 | ---------
14 |
15 | - Reservation of tickets, seats in a concert/transport, ...
16 | - Trade of limited issued assets
17 |
18 | Functionality
19 | -------------
20 |
21 | Create assets
22 | ^^^^^^^^^^^^^
23 |
24 | - Assets are created following a structured payload
25 | - The amount is limited
26 |
27 | Transfer assets
28 | ^^^^^^^^^^^^^^^
29 |
30 | - Easy transfer of assets between accounts by:
31 | - Clicking on an account first. This will give the assets for that account
32 | - Clicking on an asset of that account. Transfer actions will appear on the
33 | right side.
34 |
35 | Retrieve assets
36 | ^^^^^^^^^^^^^^^
37 |
38 | - That you currently own (like UTXO's)
39 | - All assets on bigchain
40 | - State indicator (blinks if asset has various owners)
41 |
42 | What this app doesn't provide
43 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
44 | - Proper user and key management
45 | - Proper signing of transfers
46 | - Proper search by payload
47 |
--------------------------------------------------------------------------------
/docs/source/structure.rst:
--------------------------------------------------------------------------------
1 | Structure
2 | =========
3 |
4 | The apps are structured as follows:
5 |
6 | * Client: ReactJS
7 | * Server: Python Flask REST API server
8 | * DB: BigchainDB
9 |
10 | All messages are JSON based.
11 |
--------------------------------------------------------------------------------
/docs/source/troubleshooting.rst:
--------------------------------------------------------------------------------
1 | Troubleshooting
2 | ===============
3 |
4 | Oops ¯\\\_(ツ)\_/¯
5 | ------------------
6 |
7 | My installation fails with:
8 |
9 | .. code-block:: bash
10 |
11 | error: Setup script exited with error in BigchainDB setup command: 'install_requires' must be a string or list of strings containing valid project/version requirement specifiers
12 |
13 | * **Solution**: update the ``setuptools``, see `PR fix `_
14 |
15 |
16 | OMG: I've messed up my database
17 | -------------------------------
18 |
19 | * **Solution**: reset your bigchaindb_examples database
20 | * **Warning**: the following resets your bigchaindb database to its default initialized state!
21 |
22 | Via Docker
23 | ^^^^^^^^^^
24 |
25 | .. code-block:: bash
26 |
27 | $ make init
28 |
29 | Or, to reinitialize and restart:
30 |
31 | .. code-block:: bash
32 |
33 | $ make restart
34 |
35 |
36 | Via the CLI
37 | ^^^^^^^^^^^
38 |
39 | .. code-block:: bash
40 |
41 | $ bigchaindb-examples init --all
42 |
43 | Or, to reinitialize and restart:
44 |
45 | .. code-block:: bash
46 |
47 | $ bigchaindb-examples start --init --all
48 |
49 |
50 | Manually
51 | ^^^^^^^^
52 |
53 | Restart your RethinkDB instance and follow the initialization steps in
54 | :ref:`manual-setup`.
55 |
--------------------------------------------------------------------------------
/init_accounts.py:
--------------------------------------------------------------------------------
1 | import logging
2 | import os.path
3 |
4 | import bigchaindb.config_utils
5 |
6 | import apps_config
7 | from server.config_bigchaindb import get_bigchain
8 | from server.lib.models.accounts import Account
9 |
10 | logging.basicConfig(level=logging.INFO)
11 | logger = logging.getLogger(__name__)
12 |
13 | APPS = apps_config.APPS
14 |
15 | LEDGER_API_BASE_HOST = os.environ.get('DOCKER_MACHINE_IP') or 'localhost'
16 | LEDGER_API_BASE_PORT = int(os.environ.get('LEDGER_API_BASE_PORT', '8000'))
17 | LEDGER_WS_BASE_HOST = os.environ.get('DOCKER_MACHINE_IP') or 'localhost'
18 | LEDGER_WS_BASE_PORT = int(os.environ.get('LEDGER_WS_BASE_PORT', '8888'))
19 |
20 | bigchain = get_bigchain()
21 | logging.info('INIT: bigchain initialized with database: {}'.format(bigchaindb.config['database']['name']))
22 |
23 |
24 | def creat_uri(host, port, offset):
25 | return '{}:{}'.format(host, port+offset)
26 |
27 |
28 | def main():
29 |
30 | for app in APPS:
31 | accounts = []
32 | app_name = '{}'.format(app['name'])
33 | if 'num_accounts' in app:
34 | for i in range(app['num_accounts']):
35 | account = Account(bigchain=bigchain,
36 | name='account_{}'.format(i),
37 | ledger={
38 | 'id': app['ledger'],
39 | 'api': creat_uri(LEDGER_API_BASE_HOST,
40 | LEDGER_API_BASE_PORT,
41 | app['ledger']),
42 | 'ws': creat_uri(LEDGER_WS_BASE_HOST,
43 | LEDGER_WS_BASE_PORT,
44 | app['ledger'])
45 | },
46 | db=app_name)
47 | accounts.append(account)
48 | elif 'accounts' in app:
49 | for account_config in app['accounts']:
50 | for ledger in account_config['ledgers']:
51 | account = Account(bigchain=bigchain,
52 | name=account_config['name'],
53 | ledger={
54 | 'id': ledger['id'],
55 | 'api': creat_uri(
56 | LEDGER_API_BASE_HOST,
57 | LEDGER_API_BASE_PORT,
58 | ledger['id']
59 | ),
60 | 'ws': creat_uri(
61 | LEDGER_WS_BASE_HOST,
62 | LEDGER_WS_BASE_PORT,
63 | ledger['id']
64 | )
65 | },
66 | db=app_name)
67 | accounts.append(account)
68 | logging.info('INIT: {} accounts initialized for app: {}'.format(len(accounts), app_name))
69 |
70 |
71 | if __name__ == '__main__':
72 | main()
73 |
--------------------------------------------------------------------------------
/init_assets.py:
--------------------------------------------------------------------------------
1 | import random
2 | import logging
3 |
4 | import bigchaindb
5 | import bigchaindb.config_utils
6 |
7 | import apps_config
8 | from server.lib.models.accounts import retrieve_accounts
9 | from server.lib.models.assets import create_asset
10 | from server.config_bigchaindb import get_bigchain
11 |
12 | logging.basicConfig(level=logging.INFO)
13 | logger = logging.getLogger(__name__)
14 |
15 | APPS = apps_config.APPS
16 |
17 |
18 | def get_accounts_by_name(accounts):
19 | # returns a dict with key = 'name-' value = account
20 | return {'{}-{}'.format(account['name'], account['ledger']['id']): account for account in accounts}
21 |
22 |
23 | def main():
24 | for app in APPS:
25 | app_name = '{}'.format(app['name'])
26 | if 'num_accounts' in app:
27 | ledger_name = 'bigchaindb_examples_{}'.format(app['ledger'])
28 | bigchain = get_bigchain(ledger_id=app['ledger'])
29 | accounts = retrieve_accounts(bigchain, app_name)
30 | assets = []
31 | for i in range(app['num_assets']):
32 | asset = create_asset(bigchain=bigchain,
33 | to=accounts[random.randint(0, app['num_accounts'] - 1)]['vk'],
34 | payload=app['payload_func'](i))
35 | assets.append(asset)
36 | logging.info('{} assets initialized for app {} on ledger {}'.format(len(assets),
37 | app_name,
38 | ledger_name))
39 | elif 'accounts' in app:
40 | bigchain = bigchaindb.Bigchain()
41 | accounts_by_name = get_accounts_by_name(retrieve_accounts(bigchain, app['name']))
42 | for account in app['accounts']:
43 | for ledger in account['ledgers']:
44 | ledger_name = 'bigchaindb_examples_{}'.format(ledger['id'])
45 | account_name = '{}-{}'.format(account['name'], ledger['id'])
46 | bigchain = bigchaindb.Bigchain(dbname=ledger_name)
47 | assets = []
48 | for i in range(ledger['num_assets']):
49 | asset = create_asset(bigchain=bigchain,
50 | to=accounts_by_name[account_name]['vk'],
51 | payload=app['payload_func'](i))
52 | assets.append(asset)
53 | logging.info('{} assets initialized for account {} in app {} on ledger {}'
54 | .format(len(assets), account['name'], app_name, ledger_name))
55 |
56 |
57 | if __name__ == '__main__':
58 | main()
59 |
--------------------------------------------------------------------------------
/ledgers.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 |
4 | services:
5 |
6 | rdb:
7 | image: rethinkdb
8 | ports:
9 | - "58087:8080"
10 | - "28015"
11 |
12 | frontend:
13 | build:
14 | context: .
15 | dockerfile: ./compose/frontend/Dockerfile
16 | volumes:
17 | - ./client/demo:/usr/src/app/client/demo
18 | - ./client/lib:/usr/src/app/client/lib
19 | - ./client/on_the_record:/usr/src/app/client/on_the_record
20 | - ./client/share_trader:/usr/src/app/client/share_trader
21 | - ./client/app_index_template.html:/usr/src/app/client/app_index_template.html
22 | - ./client/browserlist:/usr/src/app/client/browserlist
23 | - ./client/package.json:/usr/src/app/client/package.json
24 | - ./client/server.demo.js/:/usr/src/app/client/server.demo.js
25 | - ./client/webpack.config.js:/usr/src/app/client/webpack.config.js
26 | environment:
27 | CLIENT_HOST: 0.0.0.0
28 | FLASK_HOST: "${DOCKER_MACHINE_IP}"
29 | FLASK_PORT: 48000
30 | ports:
31 | - "33000:3000"
32 | command: node server.demo.js
33 |
34 | connector:
35 | build:
36 | context: .
37 | dockerfile: ./compose/server/Dockerfile
38 | volumes:
39 | - ./server:/usr/src/app/server
40 | - ./.bigchaindb_examples_docker_connector:/usr/src/app/.bigchaindb_examples
41 | environment:
42 | BIGCHAINDB_CONFIG: .bigchaindb_examples
43 | BIGCHAINDB_DATABASE_HOST: rdb
44 | command: python -m server.lib.models.connector
45 |
46 | bdb-0:
47 | build:
48 | context: .
49 | dockerfile: ./compose/server/Dockerfile
50 | volumes:
51 | - ./commands:/usr/src/app/commands
52 | - ./setup.py:/usr/src/app/setup.py
53 | - ./docs:/usr/src/app/docs
54 | - ./init_accounts.py:/usr/src/app/init_accounts.py
55 | - ./init_assets.py:/usr/src/app/init_assets.py
56 | - ./apps_config.py:/usr/src/app/apps_config.py
57 | - ./.bigchaindb_examples_docker:/usr/src/app/.bigchaindb_examples
58 | environment:
59 | BIGCHAINDB_CONFIG: .bigchaindb_examples
60 | BIGCHAINDB_DATABASE_HOST: rdb
61 | BIGCHAINDB_DATABASE_NAME: bigchaindb_examples_0
62 | LEDGER_API_BASE_PORT: 48000
63 | LEDGER_WS_BASE_PORT: 48888
64 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}"
65 | command: bigchaindb -c .bigchaindb_examples start
66 |
67 | app-0:
68 | build:
69 | context: .
70 | dockerfile: ./compose/server/Dockerfile
71 | volumes:
72 | - ./server:/usr/src/app/server
73 | volumes_from:
74 | - bdb-0
75 | environment:
76 | BIGCHAINDB_CONFIG: .bigchaindb_examples
77 | BIGCHAINDB_DATABASE_HOST: rdb
78 | BIGCHAINDB_LEDGER_NUMBER: 0
79 | FLASK_HOST: 0.0.0.0
80 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}"
81 | ports:
82 | - "48000:8000"
83 | command: python -m server.app
84 |
85 | ws-0:
86 | build:
87 | context: .
88 | dockerfile: ./compose/server/Dockerfile
89 | volumes_from:
90 | - app-0
91 | environment:
92 | BIGCHAINDB_CONFIG: .bigchaindb_examples
93 | BIGCHAINDB_DATABASE_HOST: rdb
94 | BIGCHAINDB_LEDGER_NUMBER: 0
95 | TORNADO_HOST: 0.0.0.0
96 | ports:
97 | - "48888:8888"
98 | command: python -m server.tornado_app
99 |
100 | bdb-1:
101 | build:
102 | context: .
103 | dockerfile: ./compose/server/Dockerfile
104 | volumes:
105 | - ./setup.py:/usr/src/app/setup.py
106 | - ./docs:/usr/src/app/docs
107 | - ./init_accounts.py:/usr/src/app/init_accounts.py
108 | - ./init_assets.py:/usr/src/app/init_assets.py
109 | - ./apps_config.py:/usr/src/app/apps_config.py
110 | - ./.bigchaindb_examples_docker:/usr/src/app/.bigchaindb_examples
111 | environment:
112 | BIGCHAINDB_CONFIG: .bigchaindb_examples
113 | BIGCHAINDB_DATABASE_HOST: rdb
114 | BIGCHAINDB_DATABASE_NAME: bigchaindb_examples_1
115 | command: bigchaindb -c .bigchaindb_examples start
116 |
117 | app-1:
118 | build:
119 | context: .
120 | dockerfile: ./compose/server/Dockerfile
121 | volumes:
122 | - ./server:/usr/src/app/server
123 | volumes_from:
124 | - bdb-1
125 | environment:
126 | BIGCHAINDB_CONFIG: .bigchaindb_examples
127 | BIGCHAINDB_DATABASE_HOST: rdb
128 | BIGCHAINDB_LEDGER_NUMBER: 1
129 | FLASK_HOST: 0.0.0.0
130 | DOCKER_MACHINE_IP: "${DOCKER_MACHINE_IP}"
131 | ports:
132 | - "48001:8000"
133 | command: python -m server.app
134 |
135 | ws-1:
136 | build:
137 | context: .
138 | dockerfile: ./compose/server/Dockerfile
139 | volumes_from:
140 | - app-1
141 | environment:
142 | BIGCHAINDB_CONFIG: .bigchaindb_examples
143 | BIGCHAINDB_DATABASE_HOST: rdb
144 | BIGCHAINDB_LEDGER_NUMBER: 1
145 | TORNADO_HOST: 0.0.0.0
146 | ports:
147 | - "48889:8888"
148 | command: python -m server.tornado_app
149 |
--------------------------------------------------------------------------------
/server/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/__init__.py
--------------------------------------------------------------------------------
/server/app.py:
--------------------------------------------------------------------------------
1 | """This module contains basic functions to instantiate the BigchainDB API.
2 |
3 | The application is implemented in Flask and runs using Gunicorn.
4 | """
5 | import os
6 |
7 | from flask import Flask
8 | from flask.ext.cors import CORS
9 |
10 | from server.lib.api.views import api_views
11 |
12 |
13 | def create_app(debug):
14 | """Return an instance of the Flask application.
15 |
16 | Args:
17 | debug (bool): a flag to activate the debug mode for the app
18 | (default: False).
19 | """
20 |
21 | app = Flask(__name__)
22 | hostname = os.environ.get('DOCKER_MACHINE_IP', 'localhost')
23 | if not hostname:
24 | hostname = 'localhost'
25 | origins = ('^(https?://)?(www\.)?({}|0|0.0.0.0|dimi-bat.local|'
26 | 'localhost|127.0.0.1)(\.com)?:\d{{1,5}}$').format(hostname),
27 | CORS(app,
28 | origins=origins,
29 | headers=(
30 | 'x-requested-with',
31 | 'content-type',
32 | 'accept',
33 | 'origin',
34 | 'authorization',
35 | 'x-csrftoken',
36 | 'withcredentials',
37 | 'cache-control',
38 | 'cookie',
39 | 'session-id',
40 | ),
41 | supports_credentials=True,
42 | )
43 |
44 | app.debug = debug
45 |
46 | app.register_blueprint(api_views, url_prefix='/api')
47 | return app
48 |
49 |
50 | def run_flask_server():
51 | app = create_app(debug=True)
52 | app.run(host=os.environ.get('FLASK_HOST', '127.0.0.1'), port=int(os.environ.get('FLASK_PORT', 8000)))
53 | app.run(use_reloader=False)
54 |
55 | if __name__ == '__main__':
56 | run_flask_server()
57 |
--------------------------------------------------------------------------------
/server/config_bigchaindb.py:
--------------------------------------------------------------------------------
1 | import os
2 | import os.path
3 |
4 | import bigchaindb
5 | import bigchaindb.config_utils
6 |
7 | try:
8 | CONFIG_FILE = os.environ['BIGCHAINDB_CONFIG']
9 | except KeyError:
10 | CONFIG_FILE = '.bigchaindb_examples'
11 |
12 |
13 | def get_bigchain(conf=CONFIG_FILE, ledger_id=None):
14 | if os.path.isfile(conf):
15 | bigchaindb.config_utils.autoconfigure(filename=conf, force=True)
16 |
17 | if ledger_id is not None:
18 | return bigchaindb.Bigchain(dbname='bigchaindb_examples_{}'.format(ledger_id))
19 | else:
20 | return bigchaindb.Bigchain()
21 |
--------------------------------------------------------------------------------
/server/lib/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/__init__.py
--------------------------------------------------------------------------------
/server/lib/api/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/api/__init__.py
--------------------------------------------------------------------------------
/server/lib/api/views.py:
--------------------------------------------------------------------------------
1 | """This module provides the blueprint for some basic API endpoints.
2 |
3 | For more information please refer to the documentation in Apiary:
4 | - http://docs.bigchaindb.apiary.io/
5 | """
6 | import os
7 |
8 | import flask
9 | from flask import request, Blueprint
10 |
11 | from server.config_bigchaindb import get_bigchain
12 | from server.lib.models import accounts
13 | from server.lib.models import assets
14 |
15 | api_views = Blueprint('api_views', __name__)
16 |
17 | bigchain = get_bigchain(ledger_id=os.environ.get('BIGCHAINDB_LEDGER_NUMBER'))
18 |
19 |
20 | @api_views.route('/accounts/')
21 | def get_accounts():
22 | app = '{}'.format(request.args.get('app'))
23 | result = accounts.retrieve_accounts(bigchain, app)
24 | return flask.jsonify({'accounts': result})
25 |
26 |
27 | @api_views.route('/accounts/', methods=['POST'])
28 | def post_account():
29 | json_payload = request.get_json(force=True)
30 | tx = assets.create_asset(bigchain=bigchain,
31 | to=json_payload['to'],
32 | payload={'content': json_payload['content']})
33 | return flask.jsonify(**tx)
34 |
35 |
36 | @api_views.route('/accounts//assets/')
37 | def get_assets_for_account(account_vk):
38 | query = request.args.get('search')
39 |
40 | result = {
41 | 'bigchain': assets.get_owned_assets(bigchain, vk=account_vk, query=query),
42 | 'backlog': assets.get_owned_assets(bigchain, vk=account_vk, query=query, table='backlog')
43 | }
44 | return flask.jsonify({'assets': result, 'account': account_vk})
45 |
46 |
47 | @api_views.route('/ledgers//connectors/')
48 | def get_connectors_for_account(ledger_id):
49 | app = '{}'.format(request.args.get('app'))
50 | result = accounts.get_connectors(bigchain, ledger_id, app)
51 | return flask.jsonify({'connectors': result})
52 |
53 |
54 | @api_views.route('/assets/')
55 | def get_assets():
56 | search = request.args.get('search')
57 | result = assets.get_assets(bigchain, search)
58 | return flask.jsonify({'assets': result})
59 |
60 |
61 | @api_views.route('/assets/', methods=['POST'])
62 | def post_asset():
63 | json_payload = request.get_json(force=True)
64 | to = json_payload.pop('to')
65 | tx = assets.create_asset(bigchain=bigchain,
66 | to=to,
67 | payload=json_payload)
68 |
69 | return flask.jsonify(**tx)
70 |
71 |
72 | @api_views.route('/assets///transfer/', methods=['POST'])
73 | def transfer_asset(asset_id, cid):
74 | json_payload = request.get_json(force=True)
75 | source = json_payload.pop('source')
76 | to = json_payload.pop('to')
77 |
78 | tx = assets.transfer_asset(bigchain=bigchain,
79 | source=source['vk'],
80 | to=to['vk'],
81 | asset_id={
82 | 'txid': asset_id,
83 | 'cid': int(cid)
84 | },
85 | sk=source['sk'])
86 |
87 | return flask.jsonify(**tx)
88 |
89 |
90 | @api_views.route('/assets///escrow/', methods=['POST'])
91 | def escrow_asset(asset_id, cid):
92 | json_payload = request.get_json(force=True)
93 | source = json_payload.pop('source')
94 | expires_at = json_payload.pop('expiresAt')
95 | ilp_header = json_payload.pop('ilpHeader', None)
96 | execution_condition = json_payload.pop('executionCondition')
97 | to = json_payload.pop('to')
98 |
99 | tx = assets.escrow_asset(bigchain=bigchain,
100 | source=source['vk'],
101 | to=to['vk'],
102 | asset_id={
103 | 'txid': asset_id,
104 | 'cid': int(cid)
105 | },
106 | sk=source['sk'],
107 | expires_at=expires_at,
108 | ilp_header=ilp_header,
109 | execution_condition=execution_condition)
110 |
111 | return flask.jsonify(**tx)
112 |
113 |
114 | @api_views.route('/assets///escrow/fulfill/', methods=['POST'])
115 | def fulfill_escrow_asset(asset_id, cid):
116 | json_payload = request.get_json(force=True)
117 | source = json_payload.pop('source')
118 | to = json_payload.pop('to')
119 |
120 | execution_fulfillment = json_payload.pop('conditionFulfillment', None)
121 |
122 | tx = assets.fulfill_escrow_asset(bigchain=bigchain,
123 | source=source['vk'],
124 | to=to['vk'],
125 | asset_id={
126 | 'txid': asset_id,
127 | 'cid': int(cid)
128 | },
129 | sk=source['sk'],
130 | execution_fulfillment=execution_fulfillment)
131 |
132 | return flask.jsonify(**tx)
133 |
--------------------------------------------------------------------------------
/server/lib/models/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bigchaindb/bigchaindb-examples/656183a378047f6481ac0e767ab0f6d7f64e3ef1/server/lib/models/__init__.py
--------------------------------------------------------------------------------
/server/lib/models/accounts.py:
--------------------------------------------------------------------------------
1 | import rethinkdb as r
2 |
3 | import bigchaindb.crypto
4 | from .assets import transfer_asset
5 |
6 |
7 | class Account:
8 | def __init__(self, bigchain, name, ledger, db):
9 | self.bigchain = bigchain
10 | self.db = db
11 | self.name = name
12 | self.sk, self.vk = bigchaindb.crypto.generate_key_pair()
13 | self.ledger = ledger
14 | self.save()
15 |
16 | @property
17 | def assets(self):
18 | return self.bigchain.get_owned_ids(self.vk)
19 |
20 | def transfer(self, to, asset_id):
21 | return transfer_asset(bigchain=self.bigchain,
22 | source=self.vk,
23 | to=to,
24 | asset_id=asset_id,
25 | sk=self.sk)
26 |
27 | def save(self):
28 | try:
29 | r.db_create(self.db).run(self.bigchain.conn)
30 | except r.ReqlOpFailedError:
31 | pass
32 |
33 | try:
34 | r.db(self.db).table_create('accounts').run(self.bigchain.conn)
35 | except r.ReqlOpFailedError:
36 | pass
37 |
38 | user_exists = list(r.db(self.db)
39 | .table('accounts')
40 | .filter(lambda user: (user['name'] == self.name)
41 | & (user['ledger']['id'] == self.ledger['id']))
42 | .run(self.bigchain.conn))
43 | if not len(user_exists):
44 | r.db(self.db)\
45 | .table('accounts')\
46 | .insert(self.as_dict(), durability='hard')\
47 | .run(self.bigchain.conn)
48 | else:
49 | user_persistent = user_exists[0]
50 | self.vk = user_persistent['vk']
51 | self.sk = user_persistent['sk']
52 |
53 | def as_dict(self):
54 | return {
55 | 'name': self.name,
56 | 'sk': self.sk,
57 | 'vk': self.vk,
58 | 'ledger': self.ledger
59 | }
60 |
61 |
62 | def retrieve_accounts(bigchain, db):
63 | return list(r.db(db)
64 | .table('accounts')
65 | .run(bigchain.conn))
66 |
67 |
68 | def get_connectors(bigchain, ledger_id, db):
69 | account_on_ledgers = \
70 | list(r.db(db)
71 | .table('accounts')
72 | .filter(lambda user: user['ledger']['id'] == int(ledger_id))
73 | .run(bigchain.conn))
74 | result = []
75 | for account_on_ledger in account_on_ledgers:
76 | account_on_multiple_ledgers = \
77 | list(r.db(db)
78 | .table('accounts')
79 | .filter(lambda user: user['name'] == account_on_ledger['name'])
80 | .run(bigchain.conn))
81 | if len(account_on_multiple_ledgers) > 1:
82 | result += [account for account in account_on_multiple_ledgers if account['ledger']['id'] == int(ledger_id)]
83 | return result
84 |
--------------------------------------------------------------------------------
/server/lib/models/connector.py:
--------------------------------------------------------------------------------
1 | import threading
2 | import multiprocessing
3 | from itertools import groupby
4 |
5 | import rethinkdb as r
6 |
7 | from bigchaindb import Bigchain
8 | import cryptoconditions as cc
9 |
10 | from server.lib.models.accounts import retrieve_accounts
11 | from server.lib.models.assets import escrow_asset, get_subcondition_indices_from_type, fulfill_escrow_asset
12 | from server.config_bigchaindb import get_bigchain
13 |
14 |
15 | class Connector(object):
16 |
17 | def __init__(self, account1, account2):
18 | self.accounts = {}
19 | self.add_accounts(account1)
20 | self.add_accounts(account2)
21 |
22 | def add_accounts(self, account):
23 | self.accounts[account['ledger']['id']] = account
24 |
25 | def listen_events(self):
26 | listeners = []
27 | for ledger_id in self.accounts.keys():
28 | listen = threading.Thread(target=self._listen_events, args=(ledger_id,))
29 | listen.start()
30 | listeners.append(listen)
31 |
32 | for listen in listeners:
33 | listen.join()
34 |
35 | def handle_escrow(self, tx, current_ledger_id):
36 | print('called handle_escrow {}'.format(tx['id']))
37 |
38 | ilp_header = tx['transaction']['data']['payload']['ilp_header']
39 | if 'hops' not in ilp_header:
40 | ilp_header['hops'] = []
41 | ilp_header['hops'].append({
42 | 'ledger': current_ledger_id,
43 | 'txid': tx['id']
44 | })
45 |
46 | destination_ledger_id = ilp_header['ledger']
47 |
48 | ledger = get_bigchain(ledger_id=destination_ledger_id)
49 | source = self.accounts[destination_ledger_id]['vk']
50 | to = ilp_header['account']
51 | asset_id = ledger.get_owned_ids(source).pop()
52 | sk = self.accounts[destination_ledger_id]['sk']
53 |
54 | condition = cc.Fulfillment.from_dict(tx['transaction']['conditions'][0]['condition']['details'])
55 |
56 | timelocks, _ = get_subcondition_indices_from_type(condition, cc.TimeoutFulfillment.TYPE_ID)
57 | expires_at = timelocks[0].expire_time.decode()
58 |
59 | hashlocks, _ = get_subcondition_indices_from_type(condition, cc.PreimageSha256Fulfillment.TYPE_ID)
60 | execution_condition = hashlocks[0].serialize_uri()
61 |
62 | escrow_asset(bigchain=ledger,
63 | source=source,
64 | to=to,
65 | asset_id=asset_id,
66 | sk=sk,
67 | expires_at=expires_at,
68 | ilp_header=ilp_header,
69 | execution_condition=execution_condition)
70 |
71 | def handle_execute(self, tx):
72 | print('called handle_execute {}'.format(tx['id']))
73 |
74 | ilp_header = tx['transaction']['data']['payload']['ilp_header']
75 |
76 | hop = ilp_header['hops'][0]
77 |
78 | ledger = get_bigchain(ledger_id=hop['ledger'])
79 | tx_escrow = ledger.get_transaction(hop['txid'])
80 |
81 | source = self.accounts[hop['ledger']]['vk']
82 | to = source
83 | asset_id = {
84 | 'txid': hop['txid'],
85 | 'cid': 0
86 | }
87 | sk = self.accounts[hop['ledger']]['sk']
88 |
89 | fulfillment = cc.Fulfillment.from_uri(tx['transaction']['fulfillments'][0]['fulfillment'])
90 |
91 | hashlocks, _ = get_subcondition_indices_from_type(fulfillment, cc.PreimageSha256Fulfillment.TYPE_ID)
92 | execution_fulfillment = hashlocks[0].serialize_uri()
93 |
94 | fulfill_escrow_asset(bigchain=ledger,
95 | source=source,
96 | to=to,
97 | asset_id=asset_id,
98 | sk=sk,
99 | execution_fulfillment=execution_fulfillment)
100 |
101 | def _listen_events(self, ledger_id):
102 | ledger = get_bigchain(ledger_id=ledger_id)
103 | for change in r.table('bigchain').changes().run(ledger.conn):
104 | if change['old_val'] is None:
105 | self._handle_block(change['new_val'], ledger_id)
106 |
107 | def _handle_block(self, block, ledger_id):
108 | """
109 | 1. Alice ---> [Alice, Chloe] ledger_a
110 | 2. Chloe ---> [Chloe, Bob] ledger_b
111 | 3. [Chloe, Bob] ---> Bob ledger_b
112 | 4. [Alice, Chloe] ---> Chloe ledger_a
113 |
114 |
115 | 1. If chloe not in current owners and if new_owners = [current_owner, chloe] ---> escrow
116 | 2. If current_owners == [chloe] do nothing
117 | 3. If current_owners = [chloe, new_owner] and new_owners = [bob] ---> bob fulfilled hashlock
118 | 4. If new_owner == [chloe] do nothing
119 | """
120 | vk = self.accounts[ledger_id]['vk']
121 |
122 | for transaction in block['block']['transactions']:
123 | current_owners = transaction['transaction']['fulfillments'][0]['current_owners']
124 | new_owners = transaction['transaction']['conditions'][0]['new_owners']
125 |
126 | # 1.
127 | if vk not in current_owners and sorted(new_owners) == sorted([vk] + current_owners):
128 | print('chloe received escrow {}'.format(transaction['id']))
129 | self.handle_escrow(transaction, ledger_id)
130 | # 2.
131 | elif current_owners == [vk]:
132 | print('skip {}'.format(transaction['id']))
133 | # 3.
134 | elif vk in current_owners and vk not in new_owners:
135 | print('hashlock fulfilled {}'.format(transaction['id']))
136 | self.handle_execute(transaction)
137 | # 4.
138 | elif new_owners == [vk]:
139 | print('skip {}'.format(transaction['id']))
140 |
141 |
142 | def get_connector_accounts(db='interledger'):
143 | b = get_bigchain()
144 | connector_accounts = []
145 | accounts_db = retrieve_accounts(b, db)
146 |
147 | for name, accounts in groupby(sorted(accounts_db, key=lambda d: d['name']), key=lambda d: d['name']):
148 | accounts = list(accounts)
149 | if len(accounts) == 2:
150 | connector_accounts.append(tuple(accounts))
151 |
152 | return connector_accounts
153 |
154 |
155 | def run_connector(account1, account2):
156 | c = Connector(account1=account1, account2=account2)
157 | c.listen_events()
158 |
159 |
160 | if __name__ == '__main__':
161 | connector_accounts = get_connector_accounts()
162 | connector_procs = []
163 |
164 | for connector_account in connector_accounts:
165 | print('Starting connector: {} <--- {} ---> {}'.format(connector_account[0]['ledger']['id'],
166 | connector_account[0]['name'],
167 | connector_account[1]['ledger']['id']))
168 |
169 | connector_proc = multiprocessing.Process(target=run_connector, args=connector_account)
170 | connector_proc.start()
171 | connector_procs.append(connector_proc)
172 |
173 | for connector_proc in connector_procs:
174 | connector_proc.join()
175 |
176 |
--------------------------------------------------------------------------------
/server/tornado_app.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import os
3 | import logging
4 |
5 | from tornado import websocket, web, ioloop
6 | from tornado.gen import coroutine
7 |
8 | import rethinkdb as r
9 |
10 | from server.config_bigchaindb import get_bigchain
11 |
12 | clients = []
13 | bigchain = get_bigchain(ledger_id=os.environ.get('BIGCHAINDB_LEDGER_NUMBER'))
14 |
15 | # from http://blog.hiphipjorge.com/django-and-realtime-using-django-with-tornado-and-rethinkdb/
16 | r.set_loop_type('tornado')
17 |
18 |
19 | logger = logging.getLogger('tornado')
20 |
21 |
22 | @coroutine
23 | def print_changes(db_table):
24 | conn = yield bigchain.conn
25 | feed = yield r.table(db_table).changes().run(conn)
26 | while (yield feed.fetch_next()):
27 | change = yield feed.next()
28 | block = get_block_from_change(change, db_table)
29 | for client in clients:
30 | for tx in block:
31 | # TODO: use REQL for filtering
32 | if tx_contains_vk(tx['transaction'], client.username):
33 | msg = {'change': change,
34 | 'client': client.username}
35 | client.write_message(msg)
36 | break
37 |
38 |
39 | def get_block_from_change(change, db_table):
40 | block = []
41 | if db_table in ['backlog', 'bigchain'] and (change['old_val'] or change['new_val']):
42 | block_data = change['old_val'] if change['old_val'] else change['new_val']
43 | if db_table == 'bigchain':
44 | block = block_data['block']['transactions']
45 | else:
46 | block.append(block_data)
47 | return block
48 |
49 |
50 | def tx_contains_vk(tx, vk):
51 | for condition in tx['conditions']:
52 | if vk in condition['new_owners']:
53 | return True
54 | for fullfillment in tx['fulfillments']:
55 | if vk in fullfillment['current_owners']:
56 | return True
57 |
58 |
59 | class ChangeFeedWebSocket(websocket.WebSocketHandler):
60 | username = None
61 |
62 | def check_origin(self, origin):
63 | return True
64 |
65 | def open(self, username):
66 | if self not in clients:
67 | self.username = username
68 | clients.append(self)
69 | print('ws: open (Pool: {} connections)'.format(len(clients)))
70 |
71 | def on_message(self, message):
72 | pass
73 |
74 | def on_close(self):
75 | for i, client in enumerate(clients):
76 | if client is self:
77 | clients.remove(self)
78 | print('ws: close (Pool: {} connections)'.format(len(clients)))
79 | return
80 |
81 | # TODO: use split changefeed for backlog and bigchain
82 | app = web.Application([
83 | (r'/users/(.*)/changes', ChangeFeedWebSocket)
84 | ])
85 |
86 |
87 | def run_tornado_server():
88 | tornado_port = int(os.environ.get('TORNADO_PORT', 8888))
89 | tornado_address = os.environ.get('TORNADO_HOST', '127.0.0.1')
90 | app.listen(tornado_port, address=tornado_address)
91 | # TODO: use split changefeed for backlog and bigchain
92 | ioloop.IOLoop.current().add_callback(functools.partial(print_changes, 'backlog'))
93 | ioloop.IOLoop.current().add_callback(functools.partial(print_changes, 'bigchain'))
94 |
95 | logger.info('Running on http://{}:{}'.format(tornado_address, tornado_port))
96 | ioloop.IOLoop.instance().start()
97 |
98 | if __name__ == '__main__':
99 | run_tornado_server()
100 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """
2 | BigchainDB: A Scalable Blockchain Database
3 |
4 | For full docs visit https://bigchaindb.readthedocs.org
5 |
6 | """
7 | from setuptools import setup, find_packages
8 |
9 | tests_require = [
10 | 'pytest',
11 | 'coverage',
12 | 'pep8',
13 | 'pyflakes',
14 | 'pylint',
15 | 'pytest',
16 | 'pytest-cov',
17 | 'pytest-xdist',
18 | 'pytest-flask',
19 | ]
20 |
21 | dev_require = [
22 | 'ipdb',
23 | 'ipython',
24 | ]
25 |
26 | docs_require = [
27 | 'recommonmark>=0.4.0',
28 | 'Sphinx>=1.3.5',
29 | 'sphinxcontrib-napoleon>=0.4.4',
30 | 'sphinx-rtd-theme>=0.1.9',
31 | ]
32 |
33 | setup(
34 | name='BigchainDB-Examples',
35 | version='0.1.0',
36 | description='Example usages for BigchainDB',
37 | long_description=__doc__,
38 | url='https://github.com/BigchainDB/bigchaindb-examples/',
39 | author='BigchainDB Contributors',
40 | author_email='dev@bigchaindb.com',
41 | license='AGPLv3',
42 | zip_safe=False,
43 |
44 | classifiers=[
45 | 'Development Status :: 3 - Alpha',
46 | 'Intended Audience :: Developers',
47 | 'Topic :: Database',
48 | 'Topic :: Database :: Database Engines/Servers',
49 | 'Topic :: Software Development',
50 | 'Natural Language :: English',
51 | 'License :: OSI Approved :: GNU Affero General Public License v3',
52 | 'Programming Language :: Python :: 3',
53 | 'Programming Language :: Python :: 3.4',
54 | 'Programming Language :: Python :: 3.5',
55 | 'Operating System :: MacOS :: MacOS X',
56 | 'Operating System :: POSIX :: Linux',
57 | ],
58 |
59 | packages=find_packages(exclude=['tests*']),
60 |
61 | entry_points={
62 | 'console_scripts': [
63 | 'bigchaindb-examples=commands.bigchaindb_examples:main'
64 | ]
65 | },
66 |
67 | install_requires=[
68 | "rethinkdb==2.3.0",
69 | "BigchainDB==0.5.0",
70 | "decorator==4.0.9",
71 | "flask==0.10.1",
72 | "flask-cors==2.1.2",
73 | "tornado"
74 | ],
75 |
76 | setup_requires=['pytest-runner'],
77 | tests_require=tests_require,
78 | extras_require={
79 | 'test': tests_require,
80 | 'dev': dev_require + tests_require + docs_require,
81 | 'docs': docs_require,
82 | },
83 | )
84 |
--------------------------------------------------------------------------------