42 | )
43 | expect(component.find('tbody').contains(expected)).toBe(true)
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/app/test/app/components/utils/CommentList.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import CommentList from '../../../../components/utils/CommentList'
4 | import Ratings from '../../../../components/utils/Ratings'
5 |
6 | describe('CommentList', () => {
7 | let component, props;
8 |
9 | beforeEach(() => {
10 | props = {
11 | members: {
12 | '0x1': {
13 | name: 'User'
14 | }
15 | },
16 | comments: ['comment'],
17 | reviewers: ['0x1'],
18 | ratings: [4]
19 | }
20 | component = shallow()
21 | })
22 | it('should has a class "comments"',() => {
23 | expect(component.find('.comments').exists()).toBe(true)
24 | })
25 | it('should has a title "Reviews"',() => {
26 | expect(component.find('.lead').text()).toBe('Reviews')
27 | })
28 | it('should has a class "comment"',() => {
29 | expect(component.find('.comment').exists()).toBe(true)
30 | })
31 | it('should have a Ratings component',() => {
32 | expect(component.find(Ratings).exists()).toBe(true)
33 | })
34 | it('should have a name & comment',() => {
35 | expect(component.find('.comment p').text()).toBe('Usercomment')
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/app/test/app/components/utils/Image.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render } from 'react-dom'
3 | import { mount, shallow } from 'enzyme'
4 | import Image from '../../../../components/utils/Image'
5 | import BookUnavailabelImg from '../../../../img/book-unavailable.png'
6 |
7 | describe('Image', () => {
8 |
9 | it('renders without crashing', () => {
10 | mount()
11 | })
12 | describe('render', () => {
13 | let component;
14 | beforeEach(() => {
15 | component = shallow()
16 | })
17 | it('should have a class imgContainer', () => {
18 | expect(component.find('.imgContainer').exists()).toEqual(true)
19 | })
20 | it('should have a class loader', () => {
21 | expect(component.find('.loader').exists()).toEqual(true)
22 | })
23 | it('should have a img element', () => {
24 | expect(component.find('img').exists()).toEqual(true)
25 | })
26 | it('should have a img element with imgLarge class', () => {
27 | expect(component.find('img.imgLarge').exists()).toEqual(true)
28 | })
29 | it('should set new src on error', () => {
30 | component.find('img').simulate('error');
31 | expect(component.find('img').props().src).toEqual(BookUnavailabelImg)
32 | })
33 | it('should hide the loaded on image load', () => {
34 | component.find('img').simulate('load');
35 | expect(component.find('.loader').exists()).toEqual(false)
36 | })
37 | it('should change the source of image if image not available', () => {
38 | const nextProps = { src: 'Image' }
39 | component.instance().componentWillReceiveProps(nextProps)
40 | expect(component.state().src).toEqual(nextProps.src)
41 | })
42 | it('should not change the source of image if image is available', () => {
43 | const nextProps = {}
44 | component.instance().componentWillReceiveProps(nextProps)
45 | expect(component.state().src).toEqual(undefined)
46 | })
47 | })
48 |
49 | describe('render when type is false',() => {
50 | let component;
51 | beforeEach(() => {
52 | component = shallow()
53 | })
54 | it('show have a img element with imgMedium class', () => {
55 | expect(component.find('img.imgMedium').exists()).toEqual(true)
56 | })
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/app/test/app/components/utils/LoginButton.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import LoginButton from '../../../../components/utils/LoginButton'
4 |
5 | describe('LoginButton', () => {
6 | let component, props;
7 | beforeEach(() => {
8 | props = {
9 | authenticated: true,
10 | loginSuccess: jest.fn(),
11 | loginFailure: jest.fn(),
12 | success: jest.fn(),
13 | className: 'btn',
14 | disabled: false,
15 | buttonText: 'Login',
16 | logo: 'glyphicon'
17 | }
18 | component = shallow()
19 | })
20 |
21 | describe('User is authenticated',() => {
22 | it('Should have a button',() => {
23 | expect(component.find('button').exists()).toEqual(true)
24 | })
25 | it('Should have a "btn" class',() => {
26 | expect(component.find('.btn').exists()).toEqual(true)
27 | })
28 | it('Should have a "Login" text',() => {
29 | expect(component.find('.btn').text()).toEqual('Login')
30 | })
31 | it('Should not be disabled',() => {
32 | expect(component.find('.btn').props().disabled).toEqual(false)
33 | })
34 | it('Should execute success() on click',() => {
35 | component.find('.btn').simulate('click')
36 | expect(props.success.mock.calls.length).toBe(1)
37 | })
38 | })
39 | describe('User is not authenticated',() => {
40 | beforeEach(() => {
41 | props.authenticated = false
42 | component = shallow()
43 | })
44 | it('Should have a "btn" class',() => {
45 | expect(component.find('.btn').exists()).toEqual(true)
46 | })
47 | it('Should have a "Login" text',() => {
48 | expect(component.find('strong').text()).toEqual('Login')
49 | })
50 | it('Should not be disabled',() => {
51 | expect(component.find('.btn').props().disabled).toEqual(false)
52 | })
53 | it('Should trigger "loginSuccess" on success',() => {
54 | component.find('.btn').simulate('success')
55 | expect(props.loginSuccess.mock.calls.length).toBe(1)
56 | })
57 | it('Should trigger "loginFailure" on failure',() => {
58 | component.find('.btn').simulate('failure')
59 | expect(props.loginFailure.mock.calls.length).toBe(1)
60 | })
61 | })
62 |
63 |
64 | })
65 |
--------------------------------------------------------------------------------
/app/test/app/components/utils/Ratings.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import Ratings from '../../../../components/utils/Ratings'
4 |
5 | describe('Ratings',() => {
6 | let component, props;
7 | beforeEach(() => {
8 | props = {
9 | ratings: 4
10 | }
11 | component = shallow()
12 | })
13 | it('should display 4 active stars',() => {
14 | expect(component.find('.active').length).toBe(4)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/app/web3.js:
--------------------------------------------------------------------------------
1 | // Import libraries we need.
2 | import { default as Web3 } from 'web3'
3 | import { default as contract } from 'truffle-contract'
4 |
5 | // Import our contract artifacts and turn them into usable abstractions.
6 | import lmsArtifacts from '../build/contracts/LMS.json'
7 |
8 | // Import contract address
9 | import contractConfig from './config'
10 |
11 | const LMS = contract(lmsArtifacts)
12 |
13 | // Checking if Web3 has been injected by the browser (Mist/MetaMask)
14 | let web3
15 | if (typeof web3 !== 'undefined') {
16 | // Use Mist/MetaMask's provider
17 | web3 = new Web3(web3.currentProvider)
18 | } else {
19 | // fallback - use your fallback strategy (local node / hosted node + in-dapp id mgmt / fail)
20 | web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545'))
21 | }
22 |
23 | LMS.setProvider(web3.currentProvider)
24 |
25 | let lms = LMS.at(contractConfig.id)
26 |
27 | export { lms, web3 }
28 |
--------------------------------------------------------------------------------
/build/contracts/ConvertLib.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "ConvertLib",
3 | "abi": [
4 | {
5 | "constant": false,
6 | "inputs": [
7 | {
8 | "name": "amount",
9 | "type": "uint256"
10 | },
11 | {
12 | "name": "conversionRate",
13 | "type": "uint256"
14 | }
15 | ],
16 | "name": "convert",
17 | "outputs": [
18 | {
19 | "name": "convertedAmount",
20 | "type": "uint256"
21 | }
22 | ],
23 | "payable": false,
24 | "type": "function"
25 | }
26 | ],
27 | "unlinked_binary": "0x6060604052346000575b6076806100176000396000f300606060405263ffffffff60e060020a60003504166396e4ee3d81146022575b6000565b602e6004356024356040565b60408051918252519081900360200190f35b8181025b929150505600a165627a7a72305820bca8825c6cd0f256866e82870a00fedbcce34297c41973c51bd01e44fdb76adb0029",
28 | "networks": {
29 | "1492703837605": {
30 | "events": {},
31 | "links": {},
32 | "address": "0x2918dd577ff5d2df78ba2119821316d5db34e7e8",
33 | "updated_at": 1492769788085
34 | },
35 | "1492947502546": {
36 | "events": {},
37 | "links": {},
38 | "address": "0x4da8037cf17f26e4ff9146046ea8657a483a95f1",
39 | "updated_at": 1492950521447
40 | },
41 | "1493110506448": {
42 | "events": {},
43 | "links": {},
44 | "address": "0x9c377c2af32e6cae47902714b2fef9ce63218941",
45 | "updated_at": 1493143738107
46 | }
47 | },
48 | "schema_version": "0.0.5",
49 | "updated_at": 1493143738107
50 | }
--------------------------------------------------------------------------------
/build/contracts/Killable.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "Killable",
3 | "abi": [
4 | {
5 | "constant": false,
6 | "inputs": [],
7 | "name": "kill",
8 | "outputs": [],
9 | "payable": false,
10 | "type": "function"
11 | },
12 | {
13 | "constant": true,
14 | "inputs": [],
15 | "name": "owner",
16 | "outputs": [
17 | {
18 | "name": "",
19 | "type": "address"
20 | }
21 | ],
22 | "payable": false,
23 | "type": "function"
24 | },
25 | {
26 | "constant": false,
27 | "inputs": [
28 | {
29 | "name": "newOwner",
30 | "type": "address"
31 | }
32 | ],
33 | "name": "transferOwnership",
34 | "outputs": [],
35 | "payable": false,
36 | "type": "function"
37 | }
38 | ],
39 | "unlinked_binary": "0x60606040525b60008054600160a060020a03191633600160a060020a03161790555b5b61014c806100316000396000f300606060405263ffffffff60e060020a60003504166341c0e1b5811461003a5780638da5cb5b14610049578063f2fde38b14610072575b610000565b346100005761004761008d565b005b34610000576100566100b9565b60408051600160a060020a039092168252519081900360200190f35b3461000057610047600160a060020a03600435166100c8565b005b60005433600160a060020a039081169116146100a857610000565b600054600160a060020a0316ff5b5b565b600054600160a060020a031681565b60005433600160a060020a039081169116146100e357610000565b600160a060020a0381161561011b576000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b5b505600a165627a7a723058208cf9da1627ed1ddc82560907ee912db9df9c1592311c861412448dc8831bdf7f0029",
40 | "networks": {},
41 | "schema_version": "0.0.5",
42 | "updated_at": 1497540759961
43 | }
--------------------------------------------------------------------------------
/build/contracts/MetaCoin.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "MetaCoin",
3 | "abi": [
4 | {
5 | "constant": false,
6 | "inputs": [
7 | {
8 | "name": "addr",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "getBalanceInEth",
13 | "outputs": [
14 | {
15 | "name": "",
16 | "type": "uint256"
17 | }
18 | ],
19 | "payable": false,
20 | "type": "function"
21 | },
22 | {
23 | "constant": false,
24 | "inputs": [
25 | {
26 | "name": "receiver",
27 | "type": "address"
28 | },
29 | {
30 | "name": "amount",
31 | "type": "uint256"
32 | }
33 | ],
34 | "name": "sendCoin",
35 | "outputs": [
36 | {
37 | "name": "sufficient",
38 | "type": "bool"
39 | }
40 | ],
41 | "payable": false,
42 | "type": "function"
43 | },
44 | {
45 | "constant": false,
46 | "inputs": [
47 | {
48 | "name": "addr",
49 | "type": "address"
50 | }
51 | ],
52 | "name": "getBalance",
53 | "outputs": [
54 | {
55 | "name": "",
56 | "type": "uint256"
57 | }
58 | ],
59 | "payable": false,
60 | "type": "function"
61 | },
62 | {
63 | "inputs": [],
64 | "payable": false,
65 | "type": "constructor"
66 | },
67 | {
68 | "anonymous": false,
69 | "inputs": [
70 | {
71 | "indexed": true,
72 | "name": "_from",
73 | "type": "address"
74 | },
75 | {
76 | "indexed": true,
77 | "name": "_to",
78 | "type": "address"
79 | },
80 | {
81 | "indexed": false,
82 | "name": "_value",
83 | "type": "uint256"
84 | }
85 | ],
86 | "name": "Transfer",
87 | "type": "event"
88 | }
89 | ],
90 | "unlinked_binary": "0x606060405234610000575b600160a060020a033216600090815260208190526040902061271090555b5b610223806100386000396000f300606060405263ffffffff60e060020a6000350416637bd703e8811461003a57806390b98a1114610065578063f8b2cb4f14610095575b610000565b3461000057610053600160a060020a03600435166100c0565b60408051918252519081900360200190f35b3461000057610081600160a060020a0360043516602435610140565b604080519115158252519081900360200190f35b3461000057610053600160a060020a03600435166101d8565b60408051918252519081900360200190f35b600073__ConvertLib____________________________6396e4ee3d6100e5846101d8565b60026000604051602001526040518363ffffffff1660e060020a028152600401808381526020018281526020019250505060206040518083038186803b156100005760325a03f415610000575050604051519150505b919050565b600160a060020a03331660009081526020819052604081205482901015610169575060006101d2565b600160a060020a0333811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a35060015b92915050565b600160a060020a0381166000908152602081905260409020545b9190505600a165627a7a723058200573de10a8f50dea4730f93cc43ee9c476cb8377655017efc174f257244ef3830029",
91 | "networks": {
92 | "1492703837605": {
93 | "events": {
94 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
95 | "anonymous": false,
96 | "inputs": [
97 | {
98 | "indexed": true,
99 | "name": "_from",
100 | "type": "address"
101 | },
102 | {
103 | "indexed": true,
104 | "name": "_to",
105 | "type": "address"
106 | },
107 | {
108 | "indexed": false,
109 | "name": "_value",
110 | "type": "uint256"
111 | }
112 | ],
113 | "name": "Transfer",
114 | "type": "event"
115 | }
116 | },
117 | "links": {
118 | "ConvertLib": "0x2918dd577ff5d2df78ba2119821316d5db34e7e8"
119 | },
120 | "address": "0xe0068382686de561198621df38ed016a62669bb3",
121 | "updated_at": 1492769788085
122 | },
123 | "1492947502546": {
124 | "events": {
125 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
126 | "anonymous": false,
127 | "inputs": [
128 | {
129 | "indexed": true,
130 | "name": "_from",
131 | "type": "address"
132 | },
133 | {
134 | "indexed": true,
135 | "name": "_to",
136 | "type": "address"
137 | },
138 | {
139 | "indexed": false,
140 | "name": "_value",
141 | "type": "uint256"
142 | }
143 | ],
144 | "name": "Transfer",
145 | "type": "event"
146 | }
147 | },
148 | "links": {
149 | "ConvertLib": "0x4da8037cf17f26e4ff9146046ea8657a483a95f1"
150 | },
151 | "address": "0x3ff146b3a724a8b6a7d703b6138ce4d96719014a",
152 | "updated_at": 1492950521447
153 | },
154 | "1493110506448": {
155 | "events": {
156 | "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
157 | "anonymous": false,
158 | "inputs": [
159 | {
160 | "indexed": true,
161 | "name": "_from",
162 | "type": "address"
163 | },
164 | {
165 | "indexed": true,
166 | "name": "_to",
167 | "type": "address"
168 | },
169 | {
170 | "indexed": false,
171 | "name": "_value",
172 | "type": "uint256"
173 | }
174 | ],
175 | "name": "Transfer",
176 | "type": "event"
177 | }
178 | },
179 | "links": {
180 | "ConvertLib": "0x9c377c2af32e6cae47902714b2fef9ce63218941"
181 | },
182 | "address": "0x1d50196f2a8de2f13a45af6860759295d61496a0",
183 | "updated_at": 1493143738107
184 | }
185 | },
186 | "schema_version": "0.0.5",
187 | "updated_at": 1493143738107
188 | }
--------------------------------------------------------------------------------
/build/contracts/Migrations.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "Migrations",
3 | "abi": [
4 | {
5 | "constant": false,
6 | "inputs": [
7 | {
8 | "name": "new_address",
9 | "type": "address"
10 | }
11 | ],
12 | "name": "upgrade",
13 | "outputs": [],
14 | "payable": false,
15 | "type": "function"
16 | },
17 | {
18 | "constant": true,
19 | "inputs": [],
20 | "name": "last_completed_migration",
21 | "outputs": [
22 | {
23 | "name": "",
24 | "type": "uint256"
25 | }
26 | ],
27 | "payable": false,
28 | "type": "function"
29 | },
30 | {
31 | "constant": true,
32 | "inputs": [],
33 | "name": "owner",
34 | "outputs": [
35 | {
36 | "name": "",
37 | "type": "address"
38 | }
39 | ],
40 | "payable": false,
41 | "type": "function"
42 | },
43 | {
44 | "constant": false,
45 | "inputs": [
46 | {
47 | "name": "completed",
48 | "type": "uint256"
49 | }
50 | ],
51 | "name": "setCompleted",
52 | "outputs": [],
53 | "payable": false,
54 | "type": "function"
55 | },
56 | {
57 | "inputs": [],
58 | "payable": false,
59 | "type": "constructor"
60 | }
61 | ],
62 | "unlinked_binary": "0x606060405234610000575b60008054600160a060020a03191633600160a060020a03161790555b5b610190806100366000396000f300606060405263ffffffff60e060020a6000350416630900f0108114610045578063445df0ac146100605780638da5cb5b1461007f578063fdacd576146100a8575b610000565b346100005761005e600160a060020a03600435166100ba565b005b346100005761006d61012d565b60408051918252519081900360200190f35b346100005761008c610133565b60408051600160a060020a039092168252519081900360200190f35b346100005761005e600435610142565b005b6000805433600160a060020a03908116911614156101275781905080600160a060020a031663fdacd5766001546040518263ffffffff1660e060020a02815260040180828152602001915050600060405180830381600087803b156100005760325a03f115610000575050505b5b5b5050565b60015481565b600054600160a060020a031681565b60005433600160a060020a039081169116141561015f5760018190555b5b5b505600a165627a7a723058205ef82550f8f0e448401d4d9982e46d9dccbf715f769d97629a640c7a623be0800029",
63 | "networks": {
64 | "1497522959887": {
65 | "events": {},
66 | "links": {},
67 | "address": "0xc677dea7ff2da2ed650a8a3af1827c22d85d3348",
68 | "updated_at": 1497536141776
69 | },
70 | "1497538780131": {
71 | "events": {},
72 | "links": {},
73 | "address": "0x77e9d05decf809fd2763767fc84b4159c0234a55",
74 | "updated_at": 1497540760524
75 | }
76 | },
77 | "schema_version": "0.0.5",
78 | "updated_at": 1497540760524
79 | }
--------------------------------------------------------------------------------
/build/contracts/Ownable.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "Ownable",
3 | "abi": [
4 | {
5 | "constant": true,
6 | "inputs": [],
7 | "name": "owner",
8 | "outputs": [
9 | {
10 | "name": "",
11 | "type": "address"
12 | }
13 | ],
14 | "payable": false,
15 | "type": "function"
16 | },
17 | {
18 | "constant": false,
19 | "inputs": [
20 | {
21 | "name": "newOwner",
22 | "type": "address"
23 | }
24 | ],
25 | "name": "transferOwnership",
26 | "outputs": [],
27 | "payable": false,
28 | "type": "function"
29 | },
30 | {
31 | "inputs": [],
32 | "payable": false,
33 | "type": "constructor"
34 | }
35 | ],
36 | "unlinked_binary": "0x606060405234610000575b60008054600160a060020a03191633600160a060020a03161790555b5b60fa806100356000396000f300606060405263ffffffff60e060020a6000350416638da5cb5b8114602c578063f2fde38b146052575b6000565b346000576036606a565b60408051600160a060020a039092168252519081900360200190f35b346000576068600160a060020a03600435166079565b005b600054600160a060020a031681565b60005433600160a060020a039081169116146092576000565b600160a060020a0381161560c9576000805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b5b5b505600a165627a7a72305820934d650add4d653e3b4ab387026781c16ed54b40186c8c58f3260adbb81836d90029",
37 | "networks": {},
38 | "schema_version": "0.0.5",
39 | "updated_at": 1497540759961
40 | }
--------------------------------------------------------------------------------
/build/contracts/StringLib.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "StringLib",
3 | "abi": [
4 | {
5 | "constant": false,
6 | "inputs": [
7 | {
8 | "name": "b",
9 | "type": "bytes1"
10 | }
11 | ],
12 | "name": "char",
13 | "outputs": [
14 | {
15 | "name": "c",
16 | "type": "bytes1"
17 | }
18 | ],
19 | "payable": false,
20 | "type": "function"
21 | },
22 | {
23 | "constant": true,
24 | "inputs": [
25 | {
26 | "name": "v",
27 | "type": "bytes32"
28 | }
29 | ],
30 | "name": "bytesToUInt",
31 | "outputs": [
32 | {
33 | "name": "ret",
34 | "type": "uint256"
35 | }
36 | ],
37 | "payable": false,
38 | "type": "function"
39 | }
40 | ],
41 | "unlinked_binary": "0x606060405234610000575b61018b806100196000396000f300606060405263ffffffff60e060020a60003504166369f9ad2f811461002f57806381a33a6f14610061575b610000565b610044600160f860020a03196004351661007e565b60408051600160f860020a03199092168252519081900360200190f35b61006c6004356100e4565b60408051918252519081900360200190f35b60007f0a00000000000000000000000000000000000000000000000000000000000000600160f860020a0319831610156100ca578160f860020a900460300160f860020a0290506100de565b8160f860020a900460570160f860020a0290505b5b919050565b600080808315156100f457610000565b5060005b6020811015610157576008601f8290030260020a848115610000570460ff169150816000141561012757610157565b60308210806101365750603982115b1561014057610000565b5b600a929092028101602f1901915b6001016100f8565b5b50509190505600a165627a7a72305820e81878cf0e28eab0b733dd3e42d8d3e1e2dede9db5637e3048e92a20445875c00029",
42 | "networks": {
43 | "1497522959887": {
44 | "events": {},
45 | "links": {},
46 | "address": "0x8e7340831cc40ba9385747e7a88a6474b8e94e1d",
47 | "updated_at": 1497536141423
48 | },
49 | "1497538780131": {
50 | "events": {},
51 | "links": {},
52 | "address": "0xa04dffc15c1d6da3d014310a28016ad79efb3d97",
53 | "updated_at": 1497540760221
54 | }
55 | },
56 | "schema_version": "0.0.5",
57 | "updated_at": 1497540760221
58 | }
--------------------------------------------------------------------------------
/build/contracts/strings.json:
--------------------------------------------------------------------------------
1 | {
2 | "contract_name": "strings",
3 | "abi": [],
4 | "unlinked_binary": "0x6060604052346000575b60358060166000396000f30060606040525b60005600a165627a7a723058203b1e6e4b80a7909e06a84ffd1ba4dd24948c88afc3b6ecc94380bf5c05c2ee280029",
5 | "networks": {},
6 | "schema_version": "0.0.5",
7 | "updated_at": 1497535235687
8 | }
--------------------------------------------------------------------------------
/contracts/BooksLibrary.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 |
3 | import "./DataStore.sol";
4 |
5 |
6 | library BooksLibrary {
7 |
8 | // Status of transaction. Used for error handling.
9 | event Status(uint indexed statusCode);
10 |
11 | // Book has following states: 0 (Available), 1 (Borrowed)
12 |
13 | function bookCount(address bookStoreAddress) constant returns (uint count) {
14 | return DataStore(bookStoreAddress).count();
15 | }
16 |
17 | function addBook(address bookStoreAddress, uint isbn13) public {
18 | // TODO Only members should be able to add books
19 | if (this.balance < 10**12) {
20 | Status(120);
21 | return;
22 | }
23 | if (!msg.sender.send(10**12)) {
24 | Status(121);
25 | return;
26 | }
27 | var bookStore = DataStore(bookStoreAddress);
28 | bookStore.addNew();
29 | // TODO Find if addNew can be called simultaneously. If yes, the below index will not point to correct entry.
30 | var index = bookStore.count();
31 |
32 | bookStore.setIntValue(index, sha3('isbn'), isbn13);
33 | bookStore.setIntValue(index, sha3('dateAdded'), now);
34 | bookStore.setAddressValue(index, sha3('owner'), msg.sender);
35 | }
36 |
37 | function getBook(address bookStoreAddress, uint id) constant returns (uint index, uint isbn, uint state, address owner, address borrower, uint dateAdded, uint dateIssued, uint totalRating, uint reviewersCount) {
38 | var bookStore = DataStore(bookStoreAddress);
39 | if (id < 1 || id > bookStore.count()) {
40 | return;
41 | }
42 | index = id;
43 | isbn = bookStore.getIntValue(id, sha3('isbn'));
44 | state = bookStore.getIntValue(id, sha3('state'));
45 | owner = bookStore.getAddressValue(id, sha3('owner'));
46 | borrower = bookStore.getAddressValue(id, sha3('borrower'));
47 | dateAdded = bookStore.getIntValue(id, sha3('dateAdded'));
48 | dateIssued = bookStore.getIntValue(id, sha3('dateIssued'));
49 | totalRating = bookStore.getIntValue(id, sha3('totalRating'));
50 | reviewersCount = bookStore.getIntValue(id, sha3('reviewersCount'));
51 | }
52 |
53 | function borrowBook(address bookStoreAddress, uint id) {
54 | var bookStore = DataStore(bookStoreAddress);
55 | // Can't borrow book if passed value is not sufficient
56 | if (msg.value < 10**12) {
57 | Status(123);
58 | return;
59 | }
60 | // Can't borrow a non-existent book
61 | if (id > bookStore.count() || bookStore.getIntValue(id, sha3('state')) != 0) {
62 | Status(124);
63 | return;
64 | }
65 | // 50% value is shared with the owner
66 | var owner_share = msg.value/2;
67 | if (!bookStore.getAddressValue(id, sha3('owner')).send(owner_share)) {
68 | Status(125);
69 | return;
70 | }
71 |
72 | bookStore.setAddressValue(id, sha3('borrower'), msg.sender);
73 | bookStore.setIntValue(id, sha3('dateIssued'), now);
74 | bookStore.setIntValue(id, sha3('state'), 1);
75 | // Borrow(id, msg.sender, catalog[id].dateIssued);
76 | }
77 |
78 | function returnBook(address bookStoreAddress, uint id) {
79 | // address borrower;
80 | var bookStore = DataStore(bookStoreAddress);
81 | if (id > bookStore.count()
82 | || bookStore.getIntValue(id, sha3('state')) == 0
83 | || bookStore.getAddressValue(id, sha3('owner')) != msg.sender)
84 | {
85 | Status(126);
86 | return;
87 | }
88 | // borrower = catalog[id].borrower;
89 | bookStore.setAddressValue(id, sha3('borrower'), 0x0);
90 | bookStore.setIntValue(id, sha3('dateIssued'), 0);
91 | bookStore.setIntValue(id, sha3('state'), 0);
92 | // Return(id, borrower, now);
93 | }
94 |
95 | function rateBook(address bookStoreAddress, uint id, uint rating, uint oldRating, string comments) {
96 | var bookStore = DataStore(bookStoreAddress);
97 | if (id > bookStore.count() || rating < 1 || rating > 5) {
98 | Status(127);
99 | return;
100 | }
101 | if (oldRating == 0) {
102 | bookStore.setIntValue(id, sha3('reviewersCount'), bookStore.getIntValue(id, sha3('reviewersCount')) + 1);
103 | bookStore.setIntValue(id, sha3('totalRating'), bookStore.getIntValue(id, sha3('totalRating')) + rating);
104 | } else {
105 | bookStore.setIntValue(
106 | id, sha3('totalRating'),
107 | bookStore.getIntValue(id, sha3('totalRating')) + rating - oldRating
108 | );
109 | }
110 |
111 | // All reviews are logged. Applications are responsible for eliminating duplicate ratings
112 | // and computing average rating.
113 | // Rate(id, msg.sender, rating, comments, now);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/contracts/DataStore.sol:
--------------------------------------------------------------------------------
1 | // An iterable data stored designed to be eternal.
2 | // A more appropriate name could be IterableDataStore, but that's a mouthful.
3 | // Create new instances for each logical entity e.g. one for books, one for members and so on.
4 | // As is true for all Ethereum contracts, keep the contract addresses very safe, else you will lose all data.
5 |
6 | pragma solidity ^0.4.0;
7 |
8 | import "./helper_contracts/zeppelin/lifecycle/Killable.sol";
9 |
10 | contract DataStore is Killable {
11 | // The data in this contract can be changed only by the owner, which should be the calling contract.
12 | uint public count;
13 |
14 | function addNew() {
15 | // Invoke this function before adding a new record.
16 | // TODO Find if addNew can be called simultaneously. If yes, the below index will not point to correct entry.
17 | count++;
18 | }
19 |
20 | mapping (uint => mapping (bytes32 => address)) public AddressStorage;
21 | mapping (uint => mapping (bytes32 => uint)) public IntStorage;
22 | mapping (uint => mapping (bytes32 => string)) public StringStorage;
23 | // An example Member Data Store:
24 | // {1: {'name': 'John Doe', 'email': 'john.doe@example.com'}}
25 | // {2: {'name': 'Johnny Appleseed', 'email': 'johnny.appleseed@icloud.com', 'address': '1, Infinite Loop'}}
26 | // Book Data Store: {1: {'title': '1984', 'author': '', 'publisher': '', 'imgUrl': ''}}
27 |
28 | function getAddressValue(uint index, bytes32 key) constant returns (address) {
29 | return AddressStorage[index][key];
30 | }
31 |
32 | function setAddressValue(uint index, bytes32 key, address value) onlyOwner {
33 | AddressStorage[index][key] = value;
34 | }
35 |
36 | function getIntValue(uint index, bytes32 key) constant returns (uint) {
37 | return IntStorage[index][key];
38 | }
39 |
40 | function setIntValue(uint index, bytes32 key, uint value) onlyOwner {
41 | IntStorage[index][key] = value;
42 | }
43 |
44 | function getStringValue(uint index, bytes32 key) constant returns (string) {
45 | // This function cannot be used by other contracts or libraries due to an EVM restriction
46 | // on contracts reading variable-sized data from other contracts.
47 | return StringStorage[index][key];
48 | }
49 |
50 | function setStringValue(uint index, bytes32 key, string value) onlyOwner {
51 | StringStorage[index][key] = value;
52 | }
53 |
54 | mapping(bytes32 => mapping (address => uint)) AddressIndex;
55 | mapping(bytes32 => mapping (bytes32 => uint)) Bytes32Index;
56 | mapping(bytes32 => mapping (int => uint)) IntIndex;
57 |
58 | function getAddressIndex(bytes32 indexName, address key) constant returns (uint) {
59 | return AddressIndex[indexName][key];
60 | }
61 |
62 | function setAddressIndex(bytes32 indexName, address key, uint index) onlyOwner {
63 | AddressIndex[indexName][key] = index;
64 | }
65 |
66 | function getBytes32Index(bytes32 indexName, bytes32 key) constant returns (uint) {
67 | return Bytes32Index[indexName][key];
68 | }
69 |
70 | function setBytes32Index(bytes32 indexName, bytes32 key, uint index) onlyOwner {
71 | Bytes32Index[indexName][key] = index;
72 | }
73 |
74 | function getIntIndex(bytes32 indexName, int key) constant returns (uint) {
75 | return IntIndex[indexName][key];
76 | }
77 |
78 | function setIntIndex(bytes32 indexName, int key, uint index) onlyOwner {
79 | IntIndex[indexName][key] = index;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/contracts/MembersLibrary.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 |
3 | import "./helper_contracts/strings.sol";
4 | import "./helper_contracts/StringLib.sol";
5 |
6 | import "./DataStore.sol";
7 |
8 |
9 | library MembersLibrary {
10 |
11 | using strings for *;
12 |
13 | // Status of transaction. Used for error handling.
14 | event Status(uint indexed statusCode);
15 |
16 | // Member has following states: 0 (Active), 1 (Inactive)
17 |
18 | function memberCount(address memberStoreAddress) constant returns (uint count) {
19 | return DataStore(memberStoreAddress).count();
20 | }
21 |
22 | function addMember(address memberStoreAddress, string name, string email, address account) public {
23 | var memberStore = DataStore(memberStoreAddress);
24 | var emailIndex = memberStore.getBytes32Index('email', sha3(email));
25 | var accountIndex = memberStore.getAddressIndex('account', account);
26 | if (accountIndex == emailIndex && accountIndex != 0) {
27 | // if member is already registered with given info
28 | memberStore.setIntValue(accountIndex, 'state', 0);
29 | Status(102);
30 | return;
31 | }
32 | if (accountIndex != 0 && emailIndex != 0 && emailIndex != accountIndex) {
33 | // provided account and email already registered but with different users
34 | Status(103);
35 | return;
36 | }
37 | if (accountIndex == 0 && emailIndex != 0) {
38 | // email is already registered
39 | Status(104);
40 | return;
41 | }
42 | if (accountIndex != 0 && emailIndex == 0) {
43 | // account is already registered
44 | Status(105);
45 | return;
46 | }
47 | memberStore.addNew();
48 | var index = memberStore.count();
49 |
50 | memberStore.setStringValue(index, 'name', name);
51 | memberStore.setStringValue(index, 'email', email);
52 | memberStore.setIntValue(index, 'dateAdded', now);
53 | memberStore.setAddressValue(index, 'account', account);
54 |
55 | memberStore.setBytes32Index('email', sha3(email), index);
56 | memberStore.setAddressIndex('account', account, index);
57 | }
58 |
59 | function removeMember(address memberStoreAddress, address account) {
60 | var memberStore = DataStore(memberStoreAddress);
61 | // Deactivate member
62 | var accountIndex = memberStore.getAddressIndex('account', account);
63 | if (accountIndex != 0) {
64 | memberStore.setIntValue(accountIndex, 'state', 1);
65 | }
66 | }
67 |
68 | function getMember(address memberStoreAddress, uint index) constant returns (address account, uint state, uint dateAdded) {
69 | var memberStore = DataStore(memberStoreAddress);
70 | if (index < 1 || index > memberStore.count()) {
71 | return;
72 | }
73 | account = memberStore.getAddressValue(index, 'account');
74 | state = memberStore.getIntValue(index, 'state');
75 | dateAdded = memberStore.getIntValue(index, 'dateAdded');
76 | }
77 |
78 | // Functions below are not used since Organisation contract cannot read string from library.
79 | function getMemberDetailsByAccount(address memberStoreAddress, address account) constant returns (string member) {
80 | var memberStore = DataStore(memberStoreAddress);
81 | var accountIndex = memberStore.getAddressIndex('account', account);
82 | if (accountIndex < 1 || accountIndex > memberStore.count()) {
83 | return;
84 | }
85 | var parts = new strings.slice[](4);
86 | parts[0] = StringLib.uintToString(accountIndex).toSlice();
87 | parts[1] = StringLib.addressToString(memberStore.getAddressValue(accountIndex, 'account')).toSlice();
88 | parts[2] = StringLib.uintToString(memberStore.getIntValue(accountIndex, 'state')).toSlice();
89 | parts[3] = StringLib.uintToString(memberStore.getIntValue(accountIndex, 'dateAdded')).toSlice();
90 | member = ";".toSlice().join(parts);
91 | }
92 |
93 | function getMemberDetailsByEmail(address memberStoreAddress, string email) constant returns (string member) {
94 | var memberStore = DataStore(memberStoreAddress);
95 | var emailIndex = memberStore.getBytes32Index('email', sha3(email));
96 | if (emailIndex < 1 || emailIndex > memberStore.count()) {
97 | return;
98 | }
99 | var parts = new strings.slice[](4);
100 | parts[0] = StringLib.uintToString(emailIndex).toSlice();
101 | parts[1] = StringLib.addressToString(memberStore.getAddressValue(emailIndex, 'account')).toSlice();
102 | parts[2] = StringLib.uintToString(memberStore.getIntValue(emailIndex, 'state')).toSlice();
103 | parts[3] = StringLib.uintToString(memberStore.getIntValue(emailIndex, 'dateAdded')).toSlice();
104 | member = ";".toSlice().join(parts);
105 | }
106 |
107 | function getMemberDetailsByIndex(address memberStoreAddress, uint index) constant returns (string member) {
108 | var memberStore = DataStore(memberStoreAddress);
109 | if (index < 1 || index > memberStore.count()) {
110 | return;
111 | }
112 | var parts = new strings.slice[](4);
113 | parts[0] = StringLib.uintToString(index).toSlice();
114 | parts[1] = StringLib.addressToString(memberStore.getAddressValue(index, 'account')).toSlice();
115 | parts[2] = StringLib.uintToString(memberStore.getIntValue(index, 'state')).toSlice();
116 | parts[3] = StringLib.uintToString(memberStore.getIntValue(index, 'dateAdded')).toSlice();
117 | member = ";".toSlice().join(parts);
118 | }
119 |
120 | function getAllMembers(address memberStoreAddress) constant returns (string memberString, uint8 count) {
121 | string memory member;
122 | var memberStore = DataStore(memberStoreAddress);
123 | for (uint i = 1; i <= memberStore.count(); i++) {
124 | member = getMemberDetailsByIndex(memberStoreAddress, i);
125 | if (!member.toSlice().equals("".toSlice())) {
126 | count++;
127 | if (memberString.toSlice().equals("".toSlice())) {
128 | memberString = member;
129 | } else {
130 | memberString = memberString.toSlice().concat('|'.toSlice()).toSlice().concat(member.toSlice());
131 | }
132 | }
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/contracts/Migrations.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.2;
2 |
3 | contract Migrations {
4 | address public owner;
5 | uint public last_completed_migration;
6 |
7 | modifier restricted() {
8 | if (msg.sender == owner) _;
9 | }
10 |
11 | function Migrations() {
12 | owner = msg.sender;
13 | }
14 |
15 | function setCompleted(uint completed) restricted {
16 | last_completed_migration = completed;
17 | }
18 |
19 | function upgrade(address new_address) restricted {
20 | Migrations upgraded = Migrations(new_address);
21 | upgraded.setCompleted(last_completed_migration);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/OrgLibrary.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 |
3 | import "./DataStore.sol";
4 |
5 |
6 | library OrgLibrary {
7 | function createOrganisation(address orgAddress, bytes32 key, address newAddress) public {
8 | var orgStore = DataStore(orgAddress);
9 | orgStore.addNew();
10 | var index = orgStore.count();
11 |
12 | orgStore.setAddressValue(index, key, newAddress);
13 | orgStore.setBytes32Index('org', key, index);
14 | }
15 |
16 | function getOrganisation(address orgAddress, bytes32 key) constant returns (address) {
17 | var orgStore = DataStore(orgAddress);
18 | var index = orgStore.getBytes32Index('org', key);
19 | return orgStore.getAddressValue(index, key);
20 | }
21 |
22 | function setOrganisation(address orgAddress, bytes32 key, address newAddress) {
23 | var orgStore = DataStore(orgAddress);
24 | var index = orgStore.getBytes32Index('org', key);
25 | return orgStore.setAddressValue(index, key, newAddress);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/Organisation.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 | import "./helper_contracts/strings.sol";
4 | import "./helper_contracts/StringLib.sol";
5 | import "./helper_contracts/zeppelin/ownership/Ownable.sol";
6 |
7 | import "./DataStore.sol";
8 | import "./BooksLibrary.sol";
9 | import "./MembersLibrary.sol";
10 |
11 |
12 | contract Organisation is Ownable {
13 | using strings for *;
14 | using BooksLibrary for address;
15 | using MembersLibrary for address;
16 |
17 | address public bookStore;
18 | address public memberStore;
19 |
20 | modifier onlyMember {
21 | bool member = false;
22 | for (uint i=1; i <= memberStore.memberCount(); i++) {
23 | var (account, state, dateAdded) = memberStore.getMember(i);
24 | if (account == msg.sender && state == 0) {
25 | member = true;
26 | break;
27 | }
28 | }
29 | if (!member) {
30 | throw;
31 | } else {
32 | _;
33 | }
34 | }
35 |
36 | function Organisation() payable {
37 | // TODO Check for funds being transferred
38 | // The contract could also be funded after instantiation through sendTransaction.
39 | }
40 |
41 | function setDataStore(address _bookStore, address _memberStore) onlyOwner {
42 | if (_bookStore == 0x0) {
43 | bookStore = new DataStore();
44 | } else {
45 | bookStore = _bookStore;
46 | }
47 | if (_memberStore == 0x0) {
48 | memberStore = new DataStore();
49 | } else {
50 | memberStore = _memberStore;
51 | }
52 | }
53 |
54 | function getDataStore() constant returns (address, address) {
55 | return (bookStore, memberStore);
56 | }
57 |
58 | //////////////////////
59 | // Member Functions //
60 | //////////////////////
61 |
62 | function memberCount() constant returns (uint) {
63 | return memberStore.memberCount();
64 | }
65 |
66 | function addMember(string name, string email, address account) onlyOwner {
67 | memberStore.addMember(name, email, account);
68 | }
69 |
70 | function removeMember(address account) onlyOwner {
71 | memberStore.removeMember(account);
72 | }
73 |
74 | function getMember(uint id) constant onlyOwner returns (string memberString) {
75 | if (id < 1 || id > memberStore.memberCount()) {
76 | return;
77 | }
78 | var (account, state, dateAdded) = memberStore.getMember(id);
79 | var parts = new strings.slice[](4);
80 | parts[0] = StringLib.uintToString(id).toSlice();
81 | parts[1] = StringLib.addressToString(account).toSlice();
82 | parts[2] = StringLib.uintToString(state).toSlice();
83 | parts[3] = StringLib.uintToString(dateAdded).toSlice();
84 | memberString = ";".toSlice().join(parts);
85 | return memberString;
86 | }
87 |
88 | function getAllMembers() constant onlyOwner returns (string memberString, uint8 count) {
89 | string memory member;
90 | for (uint i = 1; i <= memberStore.memberCount(); i++) {
91 | member = getMember(i);
92 | count++;
93 | if (memberString.toSlice().equals("".toSlice())) {
94 | memberString = member;
95 | } else {
96 | memberString = memberString.toSlice().concat('|'.toSlice()).toSlice().concat(member.toSlice());
97 | }
98 | }
99 | }
100 |
101 | ////////////////////
102 | // Book Functions //
103 | ////////////////////
104 |
105 | function bookCount() constant returns (uint) {
106 | return bookStore.bookCount();
107 | }
108 |
109 | function addBook(uint isbn13) public onlyMember {
110 | bookStore.addBook(isbn13);
111 | }
112 |
113 | function getBook(uint id) constant returns (string bookString) {
114 | if (id < 1 || id > bookStore.bookCount()) {
115 | return;
116 | }
117 | var (i, isbn, state, owner, borrower, dateAdded, dateIssued, totalRating, reviewersCount) = bookStore.getBook(id);
118 | var parts = new strings.slice[](9);
119 | parts[0] = StringLib.uintToString(i).toSlice();
120 | parts[1] = StringLib.uintToString(isbn).toSlice();
121 | parts[2] = StringLib.uintToString(state).toSlice();
122 | parts[3] = StringLib.addressToString(owner).toSlice();
123 | parts[4] = StringLib.addressToString(borrower).toSlice();
124 | parts[5] = StringLib.uintToString(dateAdded).toSlice();
125 | parts[6] = StringLib.uintToString(dateIssued).toSlice();
126 | parts[7] = StringLib.uintToString(totalRating).toSlice();
127 | parts[8] = StringLib.uintToString(reviewersCount).toSlice();
128 | bookString = ";".toSlice().join(parts);
129 | return bookString;
130 | }
131 |
132 | function getAllBooks() constant returns (string bookString, uint8 count) {
133 | string memory book;
134 | for (uint i = 1; i <= bookStore.bookCount(); i++) {
135 | book = getBook(i);
136 | count++;
137 | if (bookString.toSlice().equals("".toSlice())) {
138 | bookString = book;
139 | } else {
140 | bookString = bookString.toSlice().concat('|'.toSlice()).toSlice().concat(book.toSlice());
141 | }
142 | }
143 | }
144 |
145 | function borrowBook(uint id) payable onlyMember {
146 | bookStore.borrowBook(id);
147 | }
148 |
149 | function returnBook(uint id) onlyMember {
150 | bookStore.returnBook(id);
151 | }
152 |
153 | function rateBook(uint id, uint rating, uint oldRating, string comments) onlyMember {
154 | bookStore.rateBook(id, rating, oldRating, comments);
155 | }
156 |
157 | function kill(address upgradedOrganisation) onlyOwner {
158 | DataStore(bookStore).transferOwnership(upgradedOrganisation);
159 | DataStore(memberStore).transferOwnership(upgradedOrganisation);
160 | selfdestruct(upgradedOrganisation);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/contracts/OrganisationInterface.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 | contract OrganisationInterface {
4 | function OrganisationInterface(address, address);
5 |
6 | function setDataStore(address, address);
7 |
8 | function getDataStore() constant returns (address, address);
9 |
10 | function addBook(uint);
11 |
12 | function bookCount() constant returns (uint);
13 |
14 | function getBook(uint) constant returns (string);
15 |
16 | function getAllBooks() constant returns (string, uint8);
17 |
18 | function borrowBook(uint);
19 |
20 | function returnBook(uint);
21 |
22 | function rateBook(uint, uint, uint);
23 |
24 | function kill(address);
25 |
26 | function () {
27 | // This function gets executed if a
28 | // transaction with invalid data is sent to
29 | // the contract or just ether without data.
30 | // We revert the send so that no-one
31 | // accidentally loses money when using the
32 | // contract.
33 | throw;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/contracts/Parent.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 |
3 | import "./DataStore.sol";
4 | import "./OrgLibrary.sol";
5 | import "./OrganisationInterface.sol";
6 |
7 | import "./helper_contracts/zeppelin/ownership/Ownable.sol";
8 |
9 | contract Parent is Ownable {
10 | using OrgLibrary for address;
11 | address public orgStore;
12 |
13 | function Parent() payable {
14 | // Call setDataStore before using this contract.
15 | setDataStore(0x0);
16 | }
17 |
18 | function setDataStore(address _orgStore) onlyOwner {
19 | if (_orgStore == 0x0) {
20 | orgStore = new DataStore();
21 | } else {
22 | orgStore = _orgStore;
23 | }
24 | }
25 |
26 | function registerOrganisation(bytes32 key, address org) onlyOwner {
27 | // Important: Pass an organisation without a set data store
28 | orgStore.createOrganisation(key, org);
29 | // Create new book and member data stores
30 | OrganisationInterface(org).setDataStore(0x0, 0x0);
31 | }
32 |
33 | function getOrganisation(bytes32 key) constant returns (address) {
34 | return orgStore.getOrganisation(key);
35 | }
36 |
37 | function upgradeOrganisation(bytes32 key, address newOrg) onlyOwner {
38 | var org = orgStore.getOrganisation(key);
39 | var (bookStore, memberStore) = OrganisationInterface(org).getDataStore();
40 | OrganisationInterface(newOrg).setDataStore(bookStore, memberStore);
41 | orgStore.setOrganisation(key, newOrg);
42 | OrganisationInterface(org).kill(newOrg);
43 | }
44 |
45 | function kill(address upgradedParent) onlyOwner {
46 | // Provide the address of upgraded parent in order to transfer all data and ownership to the new parent.
47 | if (upgradedParent == 0x0) {
48 | throw;
49 | }
50 | Parent(upgradedParent).setDataStore(orgStore);
51 | DataStore(orgStore).transferOwnership(upgradedParent);
52 | selfdestruct(upgradedParent);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/contracts/StringLib.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 | // String Utils v0.1
3 |
4 | /// @title String Utils - String utility functions
5 | /// @author Piper Merriam -
6 | library StringLib {
7 | /// @dev Converts an unsigned integert to its string representation.
8 | /// @param v The number to be converted.
9 | function uintToBytes(uint v) constant internal returns (bytes32 ret) {
10 | if (v == 0) {
11 | ret = '0';
12 | }
13 | else {
14 | while (v > 0) {
15 | ret = bytes32(uint(ret) / (2 ** 8));
16 | ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
17 | v /= 10;
18 | }
19 | }
20 | return ret;
21 | }
22 |
23 | function bytes32ToString(bytes32 x) constant internal returns (string) {
24 | bytes memory bytesString = new bytes(32);
25 | uint charCount = 0;
26 | for (uint j = 0; j < 32; j++) {
27 | byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
28 | if (char != 0) {
29 | bytesString[charCount] = char;
30 | charCount++;
31 | }
32 | }
33 | bytes memory bytesStringTrimmed = new bytes(charCount);
34 | for (j = 0; j < charCount; j++) {
35 | bytesStringTrimmed[j] = bytesString[j];
36 | }
37 | return string(bytesStringTrimmed);
38 | }
39 |
40 | function uintToString(uint v) constant internal returns (string) {
41 | return bytes32ToString(uintToBytes(v));
42 | }
43 |
44 | function addressToString(address x) constant internal returns (string) {
45 | bytes memory s = new bytes(40);
46 | for (uint i = 0; i < 20; i++) {
47 | byte b = byte(uint8(uint(x) / (2**(8*(19 - i)))));
48 | byte hi = byte(uint8(b) / 16);
49 | byte lo = byte(uint8(b) - 16 * uint8(hi));
50 | s[2*i] = char(hi);
51 | s[2*i+1] = char(lo);
52 | }
53 | return string(s);
54 | }
55 |
56 | function char(byte b) returns (byte c) {
57 | if (b < 10) return byte(uint8(b) + 0x30);
58 | else return byte(uint8(b) + 0x57);
59 | }
60 |
61 | /// @dev Converts a numeric string to it's unsigned integer representation.
62 | /// @param v The string to be converted.
63 | function bytesToUInt(bytes32 v) constant returns (uint ret) {
64 | if (v == 0x0) {
65 | throw;
66 | }
67 |
68 | uint digit;
69 |
70 | for (uint i = 0; i < 32; i++) {
71 | digit = uint((uint(v) / (2 ** (8 * (31 - i)))) & 0xff);
72 | if (digit == 0) {
73 | break;
74 | }
75 | else if (digit < 48 || digit > 57) {
76 | throw;
77 | }
78 | ret *= 10;
79 | ret += (digit - 48);
80 | }
81 | return ret;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/contracts/helper_contracts/StringLib.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.0;
2 | // String Utils v0.1
3 |
4 | /// @title String Utils - String utility functions
5 | /// @author Piper Merriam -
6 | library StringLib {
7 | /// @dev Converts an unsigned integert to its string representation.
8 | /// @param v The number to be converted.
9 | function uintToBytes(uint v) constant internal returns (bytes32 ret) {
10 | if (v == 0) {
11 | ret = '0';
12 | }
13 | else {
14 | while (v > 0) {
15 | ret = bytes32(uint(ret) / (2 ** 8));
16 | ret |= bytes32(((v % 10) + 48) * 2 ** (8 * 31));
17 | v /= 10;
18 | }
19 | }
20 | return ret;
21 | }
22 |
23 | function bytes32ToString(bytes32 x) constant internal returns (string) {
24 | bytes memory bytesString = new bytes(32);
25 | uint charCount = 0;
26 | for (uint j = 0; j < 32; j++) {
27 | byte char = byte(bytes32(uint(x) * 2 ** (8 * j)));
28 | if (char != 0) {
29 | bytesString[charCount] = char;
30 | charCount++;
31 | }
32 | }
33 | bytes memory bytesStringTrimmed = new bytes(charCount);
34 | for (j = 0; j < charCount; j++) {
35 | bytesStringTrimmed[j] = bytesString[j];
36 | }
37 | return string(bytesStringTrimmed);
38 | }
39 |
40 | function uintToString(uint v) constant internal returns (string) {
41 | return bytes32ToString(uintToBytes(v));
42 | }
43 |
44 | function addressToString(address x) constant internal returns (string) {
45 | bytes memory s = new bytes(40);
46 | for (uint i = 0; i < 20; i++) {
47 | byte b = byte(uint8(uint(x) / (2**(8*(19 - i)))));
48 | byte hi = byte(uint8(b) / 16);
49 | byte lo = byte(uint8(b) - 16 * uint8(hi));
50 | s[2*i] = char(hi);
51 | s[2*i+1] = char(lo);
52 | }
53 | return string(s);
54 | }
55 |
56 | function char(byte b) returns (byte c) {
57 | if (b < 10) return byte(uint8(b) + 0x30);
58 | else return byte(uint8(b) + 0x57);
59 | }
60 |
61 | /// @dev Converts a numeric string to it's unsigned integer representation.
62 | /// @param v The string to be converted.
63 | function bytesToUInt(bytes32 v) constant returns (uint ret) {
64 | if (v == 0x0) {
65 | throw;
66 | }
67 |
68 | uint digit;
69 |
70 | for (uint i = 0; i < 32; i++) {
71 | digit = uint((uint(v) / (2 ** (8 * (31 - i)))) & 0xff);
72 | if (digit == 0) {
73 | break;
74 | }
75 | else if (digit < 48 || digit > 57) {
76 | throw;
77 | }
78 | ret *= 10;
79 | ret += (digit - 48);
80 | }
81 | return ret;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/contracts/helper_contracts/zeppelin/lifecycle/Killable.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 |
4 | import "../ownership/Ownable.sol";
5 |
6 |
7 | /*
8 | * Killable
9 | * Base contract that can be killed by owner. All funds in contract will be sent to the owner.
10 | */
11 | contract Killable is Ownable {
12 | function kill() onlyOwner {
13 | selfdestruct(owner);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/contracts/helper_contracts/zeppelin/ownership/Ownable.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 |
4 | /*
5 | * Ownable
6 | *
7 | * Base contract with an owner.
8 | * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
9 | */
10 | contract Ownable {
11 | address public owner;
12 |
13 | function Ownable() {
14 | owner = msg.sender;
15 | }
16 |
17 | modifier onlyOwner() {
18 | if (msg.sender != owner) {
19 | throw;
20 | }
21 | _;
22 | }
23 |
24 | function transferOwnership(address newOwner) onlyOwner {
25 | if (newOwner != address(0)) {
26 | owner = newOwner;
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const app = express();
4 | const config = require('./server/config');
5 | const routes = require('./server/routes');
6 | const bodyParser = require('body-parser');
7 |
8 |
9 | // Middlewares
10 | app.use(express.static(__dirname + '/dist'));
11 | app.use(bodyParser.urlencoded({ extended: true }));
12 | app.use(bodyParser.json());
13 |
14 | // Routing
15 | app.get('*', function(req, res) {
16 | res.sendFile(path.join(__dirname,'dist','index.html'));
17 | });
18 |
19 | const api = require('./server/routes')(app, express);
20 | app.use('/api',api);
21 |
22 | // Start Server
23 | app.listen(config.port, 'localhost', function(err) {
24 | if (err) {
25 | console.log(err);
26 | return;
27 | }
28 | console.log(`Listening on ${config.base_url}`);
29 | });
--------------------------------------------------------------------------------
/migrations/1_initial_migration.js:
--------------------------------------------------------------------------------
1 | var Migrations = artifacts.require("./Migrations.sol");
2 |
3 | module.exports = function(deployer) {
4 | deployer.deploy(Migrations);
5 | };
6 |
--------------------------------------------------------------------------------
/migrations/2_deploy_contracts.js:
--------------------------------------------------------------------------------
1 | var StringLib = artifacts.require("./StringLib.sol");
2 | var LMS = artifacts.require("./LMS.sol");
3 |
4 | module.exports = function(deployer) {
5 | deployer.deploy(StringLib);
6 | deployer.link(StringLib, LMS);
7 | deployer.deploy(LMS);
8 | };
9 |
--------------------------------------------------------------------------------
/migrations/3_deploy_parent.js:
--------------------------------------------------------------------------------
1 | var DataStore = artifacts.require("./DataStore.sol");
2 | var OrgLibrary = artifacts.require("./OrgLibrary.sol");
3 | var Parent = artifacts.require("./Parent.sol");
4 |
5 | module.exports = function(deployer) {
6 | deployer.deploy(DataStore);
7 | deployer.deploy(OrgLibrary);
8 | deployer.link(OrgLibrary, Parent);
9 | deployer.deploy(Parent);
10 | };
11 |
--------------------------------------------------------------------------------
/migrations/4_deploy_organisation.js:
--------------------------------------------------------------------------------
1 | var BooksLibrary = artifacts.require("./BooksLibrary.sol");
2 | var MembersLibrary = artifacts.require("./MembersLibrary.sol");
3 | var Organisation = artifacts.require("./Organisation.sol");
4 |
5 | module.exports = function(deployer) {
6 | deployer.deploy(BooksLibrary);
7 | deployer.deploy(MembersLibrary);
8 | deployer.link(BooksLibrary, Organisation);
9 | deployer.link(MembersLibrary, Organisation);
10 | deployer.deploy(Organisation);
11 | };
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "truffle-init-webpack",
3 | "version": "0.0.1",
4 | "description": "Frontend example using truffle v3",
5 | "scripts": {
6 | "lint": "eslint ./",
7 | "build": "webpack",
8 | "dev": "webpack-dev-server",
9 | "test": "jest --coverage",
10 | "test:watch": "jest --watch --coverage",
11 | "start": "npm run build && npm run server",
12 | "server": "node index.js"
13 | },
14 | "author": "Douglas von Kohorn",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "babel-cli": "^6.22.2",
18 | "babel-core": "^6.24.1",
19 | "babel-eslint": "^6.1.2",
20 | "babel-jest": "^19.0.0",
21 | "babel-loader": "^6.4.1",
22 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
23 | "babel-plugin-transform-runtime": "^6.22.0",
24 | "babel-polyfill": "^6.23.0",
25 | "babel-preset-env": "^1.1.8",
26 | "babel-preset-es2015": "^6.24.1",
27 | "babel-preset-react": "^6.24.1",
28 | "babel-preset-stage-0": "^6.24.1",
29 | "babel-register": "^6.22.0",
30 | "copy-webpack-plugin": "^4.0.1",
31 | "css-loader": "^0.26.1",
32 | "enzyme": "^2.8.2",
33 | "eslint": "^3.14.0",
34 | "eslint-config-standard": "^6.0.0",
35 | "eslint-plugin-babel": "^4.0.0",
36 | "eslint-plugin-mocha": "^4.8.0",
37 | "eslint-plugin-promise": "^3.0.0",
38 | "eslint-plugin-react": "^7.0.0",
39 | "eslint-plugin-standard": "^2.0.0",
40 | "expect": "^1.20.2",
41 | "expect-jsx": "^3.0.0",
42 | "extract-text-webpack-plugin": "^2.1.0",
43 | "file-loader": "^0.11.1",
44 | "html-webpack-plugin": "^2.28.0",
45 | "jest": "^19.0.2",
46 | "json-loader": "^0.5.4",
47 | "mocha": "^3.2.0",
48 | "nock": "^9.0.13",
49 | "react-test-renderer": "^15.5.4",
50 | "redux-mock-store": "^1.2.3",
51 | "style-loader": "^0.13.1",
52 | "truffle-contract": "^1.1.6",
53 | "url-loader": "^0.5.8",
54 | "web3": "^0.18.2",
55 | "webpack": "^2.2.1",
56 | "webpack-dev-server": "^2.3.0"
57 | },
58 | "dependencies": {
59 | "axios": "^0.16.1",
60 | "body-parser": "^1.17.1",
61 | "classnames": "^2.2.5",
62 | "express": "^4.15.2",
63 | "mailin-api-node-js": "^1.0.0",
64 | "react": "^15.5.4",
65 | "react-dom": "^15.5.4",
66 | "react-google-login": "^2.8.9",
67 | "react-modal": "^1.7.7",
68 | "react-notification-system": "^0.2.14",
69 | "react-notification-system-redux": "^1.1.2",
70 | "react-redux": "^5.0.4",
71 | "react-router-dom": "^4.1.1",
72 | "react-stars": "^2.1.0",
73 | "redux": "^3.6.0",
74 | "redux-react-session": "^1.2.0",
75 | "redux-thunk": "^2.2.0",
76 | "request": "^2.81.0"
77 | },
78 | "jest": {
79 | "moduleNameMapper": {
80 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/app/test/app/__mocks__/fileMock.js"
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/scripts/add_bulk_books.js:
--------------------------------------------------------------------------------
1 | // USAGE: "truffle exec add_bulk_books.js".
2 | // Add the bulk books using the addBook function.
3 | // TODO Write unit test for the bulk upload script.
4 |
5 | var Contract = require("../app/config.js");
6 | var data = require("./mock_data/books.json");
7 | const LMS = artifacts.require("../contracts/LMS.sol");
8 |
9 | module.exports = function(handleError) {
10 | //Calculating the nonce count to avoid the ambiguity of setting the same nonce to more than one transaction.
11 | var nonceCount = web3.eth.getTransactionCount(web3.eth.accounts[0])
12 |
13 | LMS.at(Contract.id).then( function(result) {
14 | for (var i = 0; i < data.books.length; i++) {
15 | //Iterating the mock data to call the addBook function
16 | result.addBook(data.books[i].title, data.books[i].author, data.books[i].publisher,
17 | data.books[i].img, data.books[i].description, data.books[i].genre,
18 | {"nonce": nonceCount+i }
19 | ).then(
20 | function(res) {
21 | console.log(res)
22 | }
23 | ).catch(
24 | function(err) {
25 | console.log(err)
26 | }
27 | )
28 | }
29 | })
30 | }
31 |
32 | var handleError = function(error) {
33 | console.log(error)
34 | }
35 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Used by Jenkins to deploy code to EC2 instance.
4 | # After deploying a new contract, update the contract address below.
5 |
6 | cd lms
7 | sed -i s/0x[0-9a-z]*/0xa1f1405b1aae0bf18d4515e140a62186b8ab0f3c/ app/config.js
8 | sed -i s/localhost/ec2-35-164-104-24.us-west-2.compute.amazonaws.com/ app/web3.js
9 | sed -i s/localhost/ec2-35-164-104-24.us-west-2.compute.amazonaws.com/ server/routes.js
10 | sed -i s/localhost/ec2-35-164-104-24.us-west-2.compute.amazonaws.com/ index.js
11 | sed -i s/localhost/ec2-35-164-104-24.us-west-2.compute.amazonaws.com/ truffle.js
12 | sed -i s/localhost/ec2-35-164-104-24.us-west-2.compute.amazonaws.com/ scripts/overdue_books_reminder.js
13 | npm install && npm run build
14 |
--------------------------------------------------------------------------------
/scripts/cron.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # this shell script's purpose is to generate LMS.json and run overdue_books_reminder.js script
3 |
4 | cd lms/scripts
5 | solc ../contracts/LMS.sol --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,devdoc,interface,opcodes,srcmap,srcmap-runtime,userdoc > ../build/LMS.json
6 | node overdue_books_reminder.js
--------------------------------------------------------------------------------
/scripts/mine_on_tx.js:
--------------------------------------------------------------------------------
1 | //Start the minning only when the transactions happens on blockchain
2 | var mining_threads = 1
3 |
4 | function checkWork() {
5 | if (eth.getBlock("pending").transactions.length > 0) {
6 | if (eth.mining) return;
7 | console.log("== Pending transactions! Mining...");
8 | miner.start(mining_threads);
9 | } else {
10 | miner.stop();
11 | console.log("== No transactions! Mining stopped.");
12 | }
13 | }
14 | eth.filter("latest", function(err, block) { checkWork(); });
15 | eth.filter("pending", function(err, block) { checkWork(); });
16 | checkWork();
--------------------------------------------------------------------------------
/scripts/mock_data/books.json:
--------------------------------------------------------------------------------
1 | {
2 | "books": [{
3 | "title": "The Design of Everyday Things",
4 | "author": "Don Norman",
5 | "publisher": "Tantor Media Inc",
6 | "img": "https://images-eu.ssl-images-amazon.com/images/I/416Hql52NCL.jpg",
7 | "description": "Even the smartest among us can feel inept as we fail to figure out which light switch or oven burner to turn on, or whether to push, pull, or slide a door. The fault, argues this ingenious—even liberating—book, lies not in ourselves, but in product design that ignores the needs of users and the principles of cognitive psychology.",
8 | "genre": "Design"
9 | }, {
10 | "title": "Damn Good Advice (For People with Talent!): How To Unleash Your Creative Potential by America's Master Communicator",
11 | "author": "George Lois",
12 | "publisher": "Phaidon Press",
13 | "img": "http://ecx.images-amazon.com/images/I/41x6UDYqILL._SX335_BO1,204,203,200_.jpg",
14 | "description": "Damn Good Advice (For People With Talent!) is a look into the mind of one of America's most legendary creative thinkers, George Lois. Offering indispensle lessons, practical advice, facts, anecdotes and inspiration, this book is a timeless creative bible for all those looking to succeed in life, business and creativity.",
15 | "genre": "Design"
16 | }, {
17 | "title": "Steal Like an Artist: 10 Things Nobody Told You About Being Creative",
18 | "author": "Austin Kleon ",
19 | "publisher": "Adams Media",
20 | "img": "https://images-na.ssl-images-amazon.com/images/I/71yg3JyYa9L.jpg",
21 | "description": "You don’t need to be a genius, you just need to be yourself. That’s the message from Austin Kleon, a young writer and artist who knows that creativity is everywhere, creativity is for everyone. A manifesto for the digital age, Steal Like an Artist is a guide whose positive message, graphic look and illustrations, exercises, and examples will put readers directly in touch with their artistic side",
22 | "genre": "Design"
23 | }, {
24 | "title": "Made to Stick: Why some ideas take hold and others come unstuck",
25 | "author": "Chip Heath, Dan Heath",
26 | "publisher": "Random House",
27 | "img": "https://images-na.ssl-images-amazon.com/images/I/41hMTwhl6IL.jpg",
28 | "description": "What is that makes urban myths so persistent but many everyday truths so eminently forgettable? How do newspapers set about ensuring that their headlines make you want to read on? And why do we remember complicated stories but not complicated facts?",
29 | "genre": "Literature"
30 | }, {
31 | "title": "Design Patterns - Elements of Reusable Object-Oriented Software",
32 | "author": "Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides",
33 | "publisher": "Pearson Education",
34 | "img": "https://images-na.ssl-images-amazon.com/images/I/81gtKoapHFL.jpg",
35 | "description": "The authors begin by describing what patterns are and how they can help you design object-oriented software. They then go on to systematically name, explain, evaluate, and catalog recurring designs in object-oriented systems. With Design Patterns as your guide, you will learn how these important patterns fit into the software development process, and how you can leverage them to solve your own design problems most efficiently",
36 | "genre": "Programming"
37 | }, {
38 | "title": "Beautiful Code",
39 | "author": "Andy Oram, Greg Wilson",
40 | "publisher": "O'Reilly Media",
41 | "img": "http://akamaicovers.oreilly.com/images/9780596510046/lrg.jpg",
42 | "description": "How do the experts solve difficult problems in software development? In this unique and insightful book, leading computer scientists offer case studies that reveal how they found unusual, carefully designed solutions to high-profile projects. You will be able to look over the shoulder of major coding and design experts to see problems through their eyes. This is not simply another design patterns book, or another software engineering treatise on the right and wrong way to do things. The authors think aloud as they work through their project's architecture, the tradeoffs made in its construction, and when it was important to break rules.",
43 | "genre": "Programming"
44 | }, {
45 | "title": "Joel on Software: And on Diverse and Occasionally Related Matters That Will Prove of Interest to Software Developers, Designers, and Managers, and to Those Who, Whether by Good Fortune or Ill Luck, Work with Them in Some Capacity",
46 | "author": "Joel Spolsky",
47 | "publisher": "Apress",
48 | "img": "https://images-na.ssl-images-amazon.com/images/I/51WaIrBLYtL._SX258_BO1,204,203,200_.jpg",
49 | "description": "First published in 2004, Joel on Software contains 45 of the best articles from the site, from the early rants about hit-and-run management to the 2004 classic “How Microsoft Lost the API War,” and everything in between. It has gone through ten printings and remains a bestseller today.",
50 | "genre": "Programming"
51 | }, {
52 | "title": "Team Geek - A Software Developer's Guide to Working Well with Others",
53 | "author": "Brian W. Fitzpatrick and Ben Collins-Sussman",
54 | "publisher": "O'Reilly Media",
55 | "img": "https://images-na.ssl-images-amazon.com/images/I/51M7AVrXBLL._SX322_BO1,204,203,200_.jpg",
56 | "description": "In a perfect world, software engineers who produce the best code are the most successful. But in our perfectly messy world, success also depends on how you work with people to get your job done. In this highly entertaining book, Brian Fitzpatrick and Ben Collins-Sussman cover basic patterns and anti-patterns for working with other people, teams, and users while trying to develop software. This is valuable information from two respected software engineers whose popular series of talks—including 'Working with Poisonous People'—has attracted hundreds of thousands of followers.",
57 | "genre": ""
58 | }, {
59 | "title": "Learning Python, 5th Edition - Powerful Object-Oriented Programming",
60 | "author": "Mark Lutz",
61 | "publisher": "O'Reilly Media",
62 | "img": "http://akamaicovers.oreilly.com/images/0636920028154/lrg.jpg",
63 | "description": "Get a comprehensive, in-depth introduction to the core Python language with this hands-on book. Based on author Mark Lutz’s popular training course, this updated fifth edition will help you quickly write efficient, high-quality code with Python. It’s an ideal way to begin, whether you’re new to programming or a professional developer versed in other languages.",
64 | "genre": "Programming"
65 | }, {
66 | "title": "You've Got 8 Seconds - Communication Secrets for a Distracted World",
67 | "author": "Paul HELLMAN",
68 | "publisher": "AMACOM",
69 | "img": "http://akamaicovers.oreilly.com/images/9780814438305/lrg.jpg",
70 | "description": "Every day at work, people do three things: talk, listen, and pretend to listen. That’s not surprising—the average attention span has dropped to 8 seconds. To get heard, says high-stakes communications expert Paul Hellman, you need to focus your message, be slightly different, and deliver with finesse.",
71 | "genre": ""
72 | }]
73 | }
74 |
--------------------------------------------------------------------------------
/scripts/overdue_books_reminder.js:
--------------------------------------------------------------------------------
1 | /**
2 | This script will run with a cron jon &
3 | will iterate through all books and find out
4 | overdue book borrowers and send them email reminders
5 |
6 | Note: The LMS contract is not being deployed here,
7 | Script is using contract address copied from truffle
8 | in app/config.js file
9 | */
10 | // import dependencies
11 | var fs = require("fs");
12 | var Web3 = require('web3');
13 | var web3 = new Web3();
14 | // set http provider for web3, pointing to geth node location
15 | web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545'));
16 |
17 | // import mailinjs npm package for sending email
18 | // & instantiate constructor by passing API URL and API key
19 | require('mailin-api-node-js');
20 | var client = new Mailin("https://api.sendinblue.com/v2.0","apikey");
21 |
22 | // import contract id from app/config.js
23 | var contract_id = require("../app/config.js");
24 |
25 | // use following command to generate LMS.json
26 | // solc LMS.sol --combined-json abi,asm,ast,bin,bin-runtime,clone-bin,devdoc,interface,opcodes,srcmap,srcmap-runtime,userdoc > LMS.json
27 | var source = fs.readFileSync("../build/LMS.json");
28 | var contracts = JSON.parse(source).contracts;
29 |
30 | // generate the contract ABI definition
31 | var abi = JSON.parse(contracts["../contracts/LMS.sol:LMS"].abi);
32 | // following two line of code is not being used,
33 | // as we are not deploying the contract here
34 | var code = '0x'+contracts['../contracts/LMS.sol:LMS'].bin;
35 | var gasEstimate = web3.eth.estimateGas({data: code});
36 | var LMS = web3.eth.contract(abi);
37 | var lms = LMS.at(contract_id.id);
38 |
39 | // get all existing book from blockchain
40 | var allBooks = lms.getAllBooks()[0].split("|");
41 |
42 | // get timestamp of 10 days back
43 | var d = new Date();
44 | var timestamp = (d.setDate(d.getDate() - 10)) / 1000 | 0;
45 |
46 | // function to convert name in title case for better email formatting
47 | function titleCase(str) {
48 | var splitStr = str.toLowerCase().split(' ');
49 | for (var i = 0; i < splitStr.length; i++) {
50 | splitStr[i] = splitStr[i].charAt(0).toUpperCase() + splitStr[i].substring(1);
51 | }
52 | // Directly return the joined string
53 | return splitStr.join(' ');
54 | }
55 |
56 | // loop through all books to check overdue book borrowers
57 | for (var i = 0; i <= allBooks.length-1; i++) {
58 | var book = allBooks[i].split(";");
59 | // if book is not borrowed then move to next iteration
60 | if (parseInt(book[8]) === 0) {
61 | continue;
62 | }
63 | // if issued date timestamp is less than the timestamp of
64 | // 10 days back then send borrowed an email notification
65 | if (parseInt(book[8]) < timestamp) {
66 | var borrower = '0x'+book[5].toString();
67 | var owner = '0x'+book[4].toString();
68 | var borrowerDetails = lms.getMemberDetailsByAccount(borrower);
69 | var borrowerEmail = borrowerDetails[2];
70 | var borrowerName = borrowerDetails[0];
71 | var ownerName = lms.getMemberDetailsByAccount(owner)[0];
72 | var issuedDate = new Date(parseInt(book[8])*1000);
73 | // calculate duedate using issuedDate timestamp
74 | var dueDate = new Date(issuedDate.setDate(issuedDate.getDate()+10));
75 | var currentDate = new Date();
76 | // calculate time difference between duedate and currentdate in hours
77 | var timeDiff = Math.abs(currentDate.getTime() - dueDate.getTime());
78 | var timeDiffHours = timeDiff / (1000 * 3600);
79 | var daysDiff = Math.floor(timeDiffHours / 24);
80 | var hoursDiff = Math.floor(timeDiffHours % 24);
81 | // email payload
82 | var data = {"to": {[borrowerEmail] : borrowerName},
83 | "from" : ["bookshelf.admin@pramati.com", "Bookshelf admin"],
84 | "subject" : "Overdue book reminder",
85 | "html" : "
Hi "+titleCase(borrowerName)+",
\
86 | This is to inform you that a book borrowed by you \
87 | - '"+book[1]+"' is overdue by '"+daysDiff+" days and "+hoursDiff+" hours' from the given due date. \
88 | Kindly return it to the book owner - '"+titleCase(ownerName)+"', as per your \
89 | earliest convienence. In the case of any issues, \
90 | kindly contact Bookshelf admin at \
91 | bookshelf@imaginea.com.\
92 |
Note: This is an auto-generated email, \
93 | please do not respond back.
Thanks,Team Bookshelf
"
94 | };
95 | client.send_email(data).on('complete', function(data) {
96 | console.log(data);
97 | });
98 | }
99 | }
100 |
101 | // Keeping this commented code for future purpose, in case
102 | // we have to deploy the contract in nodejs
103 |
104 | // var lms = LMS.new("sanchit", "s@a.com", {
105 | // from:web3.eth.coinbase,
106 | // data:code,
107 | // gas: gasEstimate
108 | // }, function(err, myContract){
109 | // if(!err) {
110 | // if(!myContract.address) {
111 | // console.log(myContract.transactionHash)
112 | // } else {
113 | // console.log(myContract.address)
114 | // }
115 | // }
116 | // });
117 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | name : 'LMS',
3 | version : '0.0.1',
4 | env : process.env.NODE_ENV || 'development',
5 | port : process.env.PORT || 8080,
6 | base_url : process.env.BASE_URL || 'http://localhost:8080'
7 | };
8 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | const request = require('request');
2 |
3 | module.exports = function(app, express) {
4 | const api = express.Router();
5 |
6 | api.route('/create_account')
7 | // create an account
8 | .post(function(req, res) {
9 | console.log(req.body);
10 | request({
11 | url: 'http://localhost:8545',
12 | method: 'POST',
13 | json: req.body
14 | }, function(error, response, body) {
15 | console.log(error);
16 | if (error) {
17 | res.send({
18 | status: "failure",
19 | data : body
20 | });
21 | } else {
22 | res.send({
23 | status: "success",
24 | data: body
25 | });
26 | }
27 | });
28 |
29 | });
30 |
31 | return api;
32 | }
--------------------------------------------------------------------------------
/test/helpers/expectThrow.js:
--------------------------------------------------------------------------------
1 | // Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
2 |
3 | export default async promise => {
4 | try {
5 | await promise;
6 | } catch (error) {
7 | // TODO: Check jump destination to destinguish between a throw
8 | // and an actual invalid jump.
9 | const invalidJump = error.message.search('invalid JUMP') >= 0;
10 | // TODO: When we contract A calls contract B, and B throws, instead
11 | // of an 'invalid jump', we get an 'out of gas' error. How do
12 | // we distinguish this from an actual out of gas event? (The
13 | // testrpc log actually show an 'invalid jump' event.)
14 | const outOfGas = error.message.search('out of gas') >= 0;
15 | assert(
16 | invalidJump || outOfGas,
17 | "Expected throw, got '" + error + "' instead",
18 | );
19 | return;
20 | }
21 | assert.fail('Expected throw not received');
22 | };
23 |
--------------------------------------------------------------------------------
/test/testBooksLibrary.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const DataStore = artifacts.require('../contracts/DataStore.sol');
4 | const BooksLibrary = artifacts.require('../contracts/BooksLibrary.sol');
5 |
6 | contract('BooksLibrary', function(accounts) {
7 | let store, booksLibrary;
8 |
9 | beforeEach(async function() {
10 | store = await DataStore.new();
11 | booksLibrary = await BooksLibrary.new();
12 | });
13 |
14 | describe('bookCount', function() {
15 | it('should fetch the number of books in the library', async function() {
16 | let count = await booksLibrary.bookCount(store.address);
17 | assert.equal(count.valueOf(), 0);
18 | });
19 | });
20 |
21 | });
22 |
--------------------------------------------------------------------------------
/test/testDataStore.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const DataStore = artifacts.require('../contracts/DataStore.sol');
4 |
5 | contract('DataStore', function(accounts) {
6 | let store;
7 |
8 | beforeEach(async function() {
9 | store = await DataStore.new();
10 | });
11 |
12 | it('should have no records by default', async function() {
13 | let count = await store.count()
14 | assert.equal(count, 0);
15 | });
16 |
17 | describe('addNew', function() {
18 | it('should increase the record count', async function() {
19 | await store.addNew();
20 | let count = await store.count();
21 | assert.equal(count, 1);
22 | });
23 | });
24 |
25 | describe('setAddressValue', function() {
26 | it('should set an attribute of type address', async function() {
27 | await store.addNew();
28 | let index = await store.count();
29 | await store.setAddressValue(index, web3.sha3('account'), accounts[0]);
30 | let account = await store.getAddressValue(index, web3.sha3('account'));
31 | assert.equal(account, accounts[0]);
32 | });
33 | });
34 |
35 | describe('setIntValue', function() {
36 | it('should set an attribute of type integer', async function() {
37 | await store.addNew();
38 | let index = await store.count();
39 | await store.setIntValue(index, web3.sha3('balance'), 10000);
40 | let balance = await store.getIntValue(index, web3.sha3('balance'));
41 | assert.equal(balance, 10000);
42 | });
43 | });
44 |
45 | describe('setStringValue', function() {
46 | it('should set an attribute of type string', async function() {
47 | await store.addNew();
48 | let index = await store.count();
49 | await store.setStringValue(index, web3.sha3('email'), 'john.doe@example.com');
50 | let email = await store.getStringValue(index, web3.sha3('email'));
51 | assert.equal(email, 'john.doe@example.com');
52 | });
53 | });
54 |
55 | describe('setAddressIndex', function() {
56 | it('should set an index based on field of type address', async function() {
57 | await store.addNew();
58 | let index = await store.count();
59 | await store.setAddressValue(index, web3.sha3('account'), accounts[0]);
60 | await store.setAddressIndex(web3.sha3('owner'), accounts[0], index);
61 | let found = await store.getAddressIndex(web3.sha3('owner'), accounts[0]);
62 | assert.equal(found.valueOf(), index.valueOf());
63 | index = 2;
64 | await store.setAddressIndex(web3.sha3('borrower'), accounts[0], index);
65 | found = await store.getAddressIndex(web3.sha3('borrower'), accounts[0]);
66 | assert.equal(found.valueOf(), index.valueOf());
67 | });
68 | });
69 |
70 | describe('setBytes32Index', function() {
71 | it('should set an index based on field of type bytes32', async function() {
72 | await store.addNew();
73 | let index = await store.count();
74 | await store.setStringValue(index, web3.sha3('email'), 'john.doe@example.com');
75 | await store.setBytes32Index(web3.sha3('email'), web3.sha3('john.doe@example.com'), index);
76 | let found = await store.getBytes32Index(web3.sha3('email'), web3.sha3('john.doe@example.com'));
77 | assert.equal(found.valueOf(), index.valueOf());
78 | });
79 | });
80 |
81 | describe('setIntIndex', function() {
82 | it('should set an index based on field of type int', async function() {
83 | await store.addNew();
84 | let index = await store.count();
85 | await store.setIntValue(index, web3.sha3('ID'), 1199228);
86 | await store.setIntIndex(web3.sha3('employee_id'), 1199228, index);
87 | let found = await store.getIntIndex(web3.sha3('employee_id'), 1199228);
88 | assert.equal(found.valueOf(), index.valueOf());
89 | });
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/test/testOrgLibrary.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const OrgLibrary = artifacts.require('../contracts/OrgLibrary.sol');
4 |
5 | contract('OrgLibrary', function(accounts) {
6 | let parent;
7 |
8 | beforeEach(async function() {
9 | parent = await OrgLibrary.new();
10 | });
11 |
12 | it('is a placeholder test case');
13 | });
--------------------------------------------------------------------------------
/test/testParent.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Parent = artifacts.require('../contracts/Parent.sol');
4 |
5 | contract('Parent', function(accounts) {
6 | let parent;
7 |
8 | beforeEach(async function() {
9 | parent = await Parent.new();
10 | });
11 |
12 | it('is a placeholder test case');
13 | });
--------------------------------------------------------------------------------
/truffle.js:
--------------------------------------------------------------------------------
1 | // Allows us to use ES6 in our migrations and tests.
2 | require('babel-register');
3 | require('babel-polyfill');
4 |
5 | module.exports = {
6 | networks: {
7 | development: {
8 | host: 'localhost',
9 | port: 8545,
10 | network_id: '*' // Match any network id
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const HTMLWebpackPlugin = require('html-webpack-plugin')
4 | const HTMLWebpackPluginConfig = new HTMLWebpackPlugin({
5 | template: './app/index.html',
6 | filename: 'index.html',
7 | inject: 'body',
8 | favicon: './app/img/favicon.ico'
9 | })
10 |
11 | const ExtractTextPlugin = require('extract-text-webpack-plugin')
12 | const ExtractTextPluginConfig = new ExtractTextPlugin({
13 | filename: 'bundle.css',
14 | disable: false,
15 | allChunks: true
16 | })
17 |
18 | module.exports = {
19 | entry: './app/index.js',
20 | output: {
21 | path: path.resolve('dist'),
22 | filename: 'index_bundle.js'
23 | },
24 | module: {
25 | loaders: [
26 | { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/ },
27 | {
28 | test: /\.css$/,
29 | loader: ExtractTextPluginConfig.extract({
30 | fallback: 'style-loader',
31 | use: 'css-loader'
32 | })
33 | },
34 | { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
35 | ]
36 | },
37 | plugins: [HTMLWebpackPluginConfig, ExtractTextPluginConfig]
38 | }
39 |
--------------------------------------------------------------------------------
/workflow.md:
--------------------------------------------------------------------------------
1 | Workflow:
2 | - Books will remain with the owners when they are in "available" state
3 | - Readers will need to locate the owners to "borrow" the book
4 | - Owner and Reader will both ensure that the book is "borrowed" in the app
5 | - Reader will return the book to the owner.
6 | - Both owner and reader will ensure that the book is "returned" in the app
7 | - Cron task to send reminders for overdue books
8 | - Cron task to send a dairy summary email to admins
9 | - When a person is leaving, all the "available" books of the member should become
10 | "removed" and books in circulation needs to be returned/collected. Member status
11 | should be changed to "inactive"
12 |
13 | Open Items:
14 | - How do you ensure that Cron runs every day (through daily summary email for now)?
15 |
--------------------------------------------------------------------------------
/zeppelin/lifecycle/Killable.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 |
4 | import "zeppelin/ownership/Ownable.sol";
5 |
6 |
7 | /*
8 | * Killable
9 | * Base contract that can be killed by owner. All funds in contract will be sent to the owner.
10 | */
11 | contract Killable is Ownable {
12 | function kill() onlyOwner {
13 | selfdestruct(owner);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/zeppelin/ownership/Ownable.sol:
--------------------------------------------------------------------------------
1 | pragma solidity ^0.4.8;
2 |
3 |
4 | /*
5 | * Ownable
6 | *
7 | * Base contract with an owner.
8 | * Provides onlyOwner modifier, which prevents function from running if it is called by anyone other than the owner.
9 | */
10 | contract Ownable {
11 | address public owner;
12 |
13 | function Ownable() {
14 | owner = msg.sender;
15 | }
16 |
17 | modifier onlyOwner() {
18 | if (msg.sender != owner) {
19 | throw;
20 | }
21 | _;
22 | }
23 |
24 | function transferOwnership(address newOwner) onlyOwner {
25 | if (newOwner != address(0)) {
26 | owner = newOwner;
27 | }
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------