Create contribution IPFS objects, attach to proposals

This commit is contained in:
2017-06-06 21:27:05 +02:00
parent 0afbeea01b
commit 7b91e64af4
6 changed files with 149 additions and 37 deletions
+18 -12
View File
@@ -2,6 +2,7 @@ import Ember from 'ember';
const { const {
Component, Component,
isPresent,
inject: { inject: {
service service
}, },
@@ -13,6 +14,7 @@ export default Component.extend({
kredits: service(), kredits: service(),
proposal: null, proposal: null,
contributors: null,
inProgress: false, inProgress: false,
isValidRecipient: computed('proposal.recipientAddress', function() { isValidRecipient: computed('proposal.recipientAddress', function() {
@@ -24,24 +26,31 @@ export default Component.extend({
}), }),
isValidUrl: computed('proposal.url', function() { isValidUrl: computed('proposal.url', function() {
// TODO return isPresent(this.get('proposal.url'));
return true;
}), }),
isValidIpfsHash: computed('proposal.ipfsHash', function() { isValidDescription: computed('proposal.description', function() {
// TODO return isPresent(this.get('proposal.description'));
return true;
}), }),
isValid: computed.and('isValidRecipient', 'isValidAmount', 'isValidUrl', isValid: computed.and('isValidRecipient',
'isValidIpfsHash'), 'isValidAmount',
'isValidDescription'),
actions: { actions: {
save() { save() {
if (this.get('isValid')) { if (! this.get('isValid')) {
alert('Invalid data. Please review and try again.');
}
this.set('inProgress', true); this.set('inProgress', true);
let proposal = this.get('proposal');
this.get('kredits').addProposal(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(() => { .then(() => {
this.attrs.onSave(); this.attrs.onSave();
}).catch((error) => { }).catch((error) => {
@@ -50,9 +59,6 @@ export default Component.extend({
}).finally(() => { }).finally(() => {
this.set('inProgress', false); this.set('inProgress', false);
}); });
} else {
alert('Invalid data. Please review and try again.');
}
} }
} }
+15 -6
View File
@@ -7,6 +7,15 @@
{{/each}} {{/each}}
</select> </select>
</p> </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> <p>
{{input type="text" {{input type="text"
placeholder="100" placeholder="100"
@@ -15,15 +24,15 @@
</p> </p>
<p> <p>
{{input type="text" {{input type="text"
placeholder="URL" placeholder="Description"
value=proposal.url value=proposal.description
class=(if isValidUrl 'valid' '')}} class=(if isValidDescription 'valid' '')}}
</p> </p>
<p> <p>
{{input type="text" {{input type="text"
placeholder="IPFS Hash" placeholder="URL (optional)"
value=proposal.ipfsHash value=proposal.url
class=(if isValidIpfsHash 'valid' '')}} class=(if isValidUrl 'valid' '')}}
</p> </p>
<p class="actions"> <p class="actions">
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}} {{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
+54 -1
View File
@@ -1,16 +1,69 @@
import Ember from 'ember'; import Ember from 'ember';
const {
isPresent,
isEmpty,
} = Ember;
export default Ember.Object.extend({ export default Ember.Object.extend({
id: null, id: null,
creatorAddress: null, creatorAddress: null,
recipientAddress: null, recipientAddress: null,
recipientName: null, recipientName: null,
recipientProfile: null,
votesCount: null, votesCount: null,
votesNeeded: null, votesNeeded: null,
amount: null, amount: null,
executed: null, executed: null,
contribution: null,
kind: null,
description: null,
url: 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, recipientAddress: params.recipient,
amount: params.amount, amount: params.amount,
url: params.url, url: params.url,
kind: params.kind || 'dev',
ipfsHash: params.ipfsHash ipfsHash: params.ipfsHash
}); });
+6 -3
View File
@@ -102,6 +102,8 @@ export default Service.extend({
this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => { this.getValueFromContract('kreditsContract', 'contributorAddresses', i).then(address => {
this.getValueFromContract('kreditsContract', 'contributors', address).then(person => { this.getValueFromContract('kreditsContract', 'contributors', address).then(person => {
this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => { this.getValueFromContract('tokenContract', 'balanceOf', address).then(balance => {
Ember.Logger.debug('[kredits] person', address, person);
let contributor = Contributor.create({ let contributor = Contributor.create({
address: address, address: address,
ipfsHash: person[2], ipfsHash: person[2],
@@ -203,16 +205,17 @@ export default Service.extend({
const { const {
recipientAddress, recipientAddress,
amount, amount,
url, url
ipfsHash } = proposal.getProperties('recipientAddress', 'amount', 'url');
} = proposal.getProperties('recipientAddress', 'amount', 'url', 'ipfsHash');
this.get('ipfs').storeFile(proposal.serializeContribution()).then(ipfsHash => {
this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => { this.get('kreditsContract').addProposal(recipientAddress, amount, url, ipfsHash, (err, data) => {
if (err) { reject(err); return; } if (err) { reject(err); return; }
Ember.Logger.debug('[kredits] add proposal response', data); Ember.Logger.debug('[kredits] add proposal response', data);
resolve(); resolve();
}); });
}); });
});
}, },
// logKreditsContract: function() { // logKreditsContract: function() {
+43 -3
View File
@@ -1,9 +1,49 @@
import { moduleFor, test } from 'ember-qunit'; import { moduleFor, test } from 'ember-qunit';
import schemas from 'npm:kosmos-schemas';
import tv4 from 'npm:tv4';
moduleFor('model:proposal', 'Unit | Model | proposal'); 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 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']));
}); });