Move all metadata to IPFS with proper schemas #16

Merged
raucao merged 23 commits from feature/ipfs-metadata into master 2017-06-08 22:28:15 +00:00
6 changed files with 149 additions and 37 deletions
Showing only changes of commit 7b91e64af4 - Show all commits
+26 -20
View File
@@ -2,6 +2,7 @@ import Ember from 'ember';
const {
Component,
isPresent,
inject: {
service
},
@@ -13,6 +14,7 @@ export default Component.extend({
kredits: service(),
galfert commented 2017-06-08 13:52:17 +00:00 (Migrated from github.com)
Review

You should always use a radix parameter of 10 with parseInt.

You should always use a radix parameter of `10` with `parseInt`.
raucao commented 2017-06-08 14:23:30 +00:00 (Migrated from github.com)
Review

What does that mean?

What does that mean?
galfert commented 2017-06-08 14:36:58 +00:00 (Migrated from github.com)
Review

The second parameter of parseInt should always be 10 if you want it to be parsed as decimal number. So in this case it should be parseInt(this.get('proposal.amount'), 10).
Otherwise, in certain situations the number will be interpreted with a different base (e.g. when the number starts with a 0, it will be octal).

The second parameter of `parseInt` should always be `10` if you want it to be parsed as decimal number. So in this case it should be `parseInt(this.get('proposal.amount'), 10)`. Otherwise, in certain situations the number will be interpreted with a different base (e.g. when the number starts with a 0, it will be octal).
raucao commented 2017-06-08 14:54:04 +00:00 (Migrated from github.com)
Review

I see, thanks.

I see, thanks.
proposal: null,
contributors: null,
inProgress: false,
isValidRecipient: computed('proposal.recipientAddress', function() {
@@ -24,35 +26,39 @@ export default Component.extend({
}),
isValidUrl: computed('proposal.url', function() {
// TODO
return true;
return isPresent(this.get('proposal.url'));
}),
galfert commented 2017-06-08 13:50:55 +00:00 (Migrated from github.com)
Review

Is this supposed to be kredits.web3 now? Because in the add-contributor component it's still using kredits.web3Instance.

Is this supposed to be `kredits.web3` now? Because in the `add-contributor` component it's still using `kredits.web3Instance`.
raucao commented 2017-06-08 14:23:11 +00:00 (Migrated from github.com)
Review

Not "now", but in general, yes. I don't know why it's not in the add-contributor component.

Not "now", but in general, yes. I don't know why it's not in the add-contributor component.
isValidIpfsHash: computed('proposal.ipfsHash', function() {
// TODO
return true;
isValidDescription: computed('proposal.description', function() {
return isPresent(this.get('proposal.description'));
}),
isValid: computed.and('isValidRecipient', 'isValidAmount', 'isValidUrl',
'isValidIpfsHash'),
isValid: computed.and('isValidRecipient',
'isValidAmount',
'isValidDescription'),
actions: {
save() {
if (this.get('isValid')) {
this.set('inProgress', true);
this.get('kredits').addProposal(this.get('proposal'))
.then(() => {
this.attrs.onSave();
}).catch((error) => {
Ember.Logger.error('[add-proposal] error creating the proposal', error);
alert('Something went wrong.');
}).finally(() => {
this.set('inProgress', false);
});
} else {
if (! this.get('isValid')) {
alert('Invalid data. Please review and try again.');
}
this.set('inProgress', true);
let proposal = this.get('proposal');
// Set the recipient's IPFS profile hash so it can be used in the
// contribution object (which is to be stored in IPFS as well)
let contributor = this.get('contributors').findBy('address', proposal.get('recipientAddress'));
proposal.set('recipientProfile', contributor.get('ipfsHash'));
this.get('kredits').addProposal(proposal)
.then(() => {
this.attrs.onSave();
}).catch((error) => {
Ember.Logger.error('[add-proposal] error creating the proposal', error);
alert('Something went wrong.');
}).finally(() => {
this.set('inProgress', false);
});
}
}
+15 -6
View File
@@ -7,6 +7,15 @@
{{/each}}
</select>
</p>
<p>
<select required onchange={{action (mut proposal.kind) value="target.value"}}>
<option value="community" selected={{eq proposal.kind "community"}}>Community</option>
<option value="design" selected={{eq proposal.kind "design"}}>Design</option>
<option value="dev" selected={{eq proposal.kind "dev"}}>Development</option>
<option value="docs" selected={{eq proposal.kind "docs"}}>Documentation</option>
<option value="ops" selected={{eq proposal.kind "ops"}}>IT Operations</option>
</select>
</p>
<p>
{{input type="text"
placeholder="100"
@@ -15,15 +24,15 @@
</p>
<p>
{{input type="text"
placeholder="URL"
value=proposal.url
class=(if isValidUrl 'valid' '')}}
placeholder="Description"
value=proposal.description
class=(if isValidDescription 'valid' '')}}
</p>
<p>
{{input type="text"
placeholder="IPFS Hash"
value=proposal.ipfsHash
class=(if isValidIpfsHash 'valid' '')}}
placeholder="URL (optional)"
value=proposal.url
class=(if isValidUrl 'valid' '')}}
</p>
<p class="actions">
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
+54 -1
View File
@@ -1,16 +1,69 @@
import Ember from 'ember';
const {
isPresent,
isEmpty,
} = Ember;
export default Ember.Object.extend({
id: null,
creatorAddress: null,
recipientAddress: null,
recipientName: null,
recipientProfile: null,
votesCount: null,
votesNeeded: null,
amount: null,
executed: null,
contribution: null,
kind: null,
description: null,
url: null,
ipfsHash: null
details: null,
ipfsHash: null,
/**
* Creates a JSON-LD object of the contribution, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contribution.json
*
* @method
* @public
*/
contributionToJSON() {
if (isEmpty(this.get('recipientProfile'))) {
throw new Error('IPFS hash for recipient profile missing from proposal object');
}
if (isEmpty(this.get('kind')) || isEmpty(this.get('description'))) {
throw new Error('Missing one or more required properties: kind, description');
}
let contribution = {
"@context": "https://schema.kosmos.org",
"@type": "Contribution",
"contributor": {
"ipfs": this.get('recipientProfile')
},
"kind": this.get('kind'),
"description": this.get('description'),
"details": {}
};
if (isPresent(this.get('url'))) {
contribution["url"] = this.get('url');
}
return contribution;
},
/**
* Returns the JSON-LD representation of the contribution as a string
*
* @method
* @public
*/
serializeContribution() {
return JSON.stringify(this.contributionToJSON());
}
});
+1
View File
@@ -18,6 +18,7 @@ export default Route.extend({
recipientAddress: params.recipient,
amount: params.amount,
url: params.url,
kind: params.kind || 'dev',
ipfsHash: params.ipfsHash
});
+10 -7
View File
@@ -102,6 +102,8 @@ export default Service.extend({
this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => {
this.getValueFromContract('kreditsContract', 'contributors', address).then(person => {
this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => {
Ember.Logger.debug('[kredits] person', address, person);
let contributor = Contributor.create({
address: address,
ipfsHash: person[2],
@@ -203,14 +205,15 @@ export default Service.extend({
const {
recipientAddress,
amount,
url,
ipfsHash
} = proposal.getProperties('recipientAddress', 'amount', 'url', 'ipfsHash');
url
} = proposal.getProperties('recipientAddress', 'amount', 'url');
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => {
if (err) { reject(err); return; }
Ember.Logger.debug('[kredits] add proposal response', data);
resolve();
this.get('ipfs').storeFile(proposal.serializeContribution()).then(ipfsHash => {
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => {
if (err) { reject(err); return; }
Ember.Logger.debug('[kredits] add proposal response', data);
resolve();
});
});
});
},
+43 -3
View File
@@ -1,9 +1,49 @@
import { moduleFor, test } from 'ember-qunit';
import schemas from 'npm:kosmos-schemas';
import tv4 from 'npm:tv4';
moduleFor('model:proposal', 'Unit | Model | proposal');
test('it exists', function(assert) {
test('#toJSON() requires a recipient profile IPFS hash to be set', function(assert) {
let model = this.subject();
// let store = this.store();
assert.ok(!!model);
model.setProperties({
recipientAddress: '0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac'
});
try {
let json = model.contributionToJSON();
assert.notOk(json, true);
} catch(e) {
assert.ok(e.message.match(/IPFS hash .* missing/i));
}
});
test('#toJSON() requires kind and description to be set', function(assert) {
let model = this.subject();
model.setProperties({
recipientProfile: 'QmT2A7rY4e7uoKktkcFHQNN7BD1oXdZTgd8wNkr1u9nNVE'
});
try {
let json = model.contributionToJSON();
assert.notOk(json, true);
} catch(e) {
assert.ok(e.message.match(/Missing .* kind.*description/i));
}
});
test('#toJSON() returns a valid JSON-LD representation of the model', function(assert) {
let model = this.subject();
model.setProperties({
recipientAddress: '0xd4a64570b12da659ee4bbd41c3509b7b1f9c51ac',
kind: 'design',
description: 'New logo design',
url: 'http://opensourcedesign.org',
recipientProfile: 'QmT2A7rY4e7uoKktkcFHQNN7BD1oXdZTgd8wNkr1u9nNVE'
});
assert.ok(tv4.validate(model.contributionToJSON(), schemas['contribution']));
});