Merge pull request #16 from 67P/feature/ipfs-metadata

Move all metadata to IPFS with proper schemas
This commit was merged in pull request #16.
This commit is contained in:
2017-06-09 00:28:14 +02:00
committed by GitHub
24 changed files with 478 additions and 177 deletions
+43 -31
View File
@@ -1,41 +1,58 @@
import Ember from 'ember';
import Contributor from 'kredits-web/models/contributor';
export default Ember.Component.extend({
const {
Component,
isPresent,
inject: {
service
},
computed
} = Ember;
id: null,
realName: null,
address: null,
ipfsHash: null,
isCore: false,
export default Component.extend({
kredits: service(),
newContributor: null,
inProgress: false,
isValidId: function() {
return Ember.isPresent(this.get('id'));
}.property('id'),
isValidRealName: function() {
return Ember.isPresent(this.get('realName'));
}.property('realName'),
isValidAddress: function() {
return this.get('kredits.web3Instance').isAddress(this.get('address'));
}.property('address'),
return this.get('kredits.web3')
.isAddress(this.get('newContributor.address'));
}.property('kredits.web3', 'newContributor.address'),
isValid: function() {
return this.get('isValidId') && this.get('isValidRealName') && this.get('isValidAddress');
}.property('isValidAddress', 'isValidId', 'isValidRealName'),
isValidName: function() {
return isPresent(this.get('newContributor.name'));
}.property('newContributor.name'),
isValidURL: function() {
return isPresent(this.get('newContributor.url'));
}.property('newContributor.url'),
isValidGithubUID: function() {
return isPresent(this.get('newContributor.github_uid'));
}.property('newContributor.github_uid'),
isValidGithubUsername: function() {
return isPresent(this.get('newContributor.github_username'));
}.property('newContributor.github_username'),
isValidWikiUsername: function() {
return isPresent(this.get('newContributor.wiki_username'));
}.property('newContributor.wiki_username'),
isValid: computed.and(
'isValidAddress',
'isValidName',
'isValidGithubUID'
),
reset: function() {
this.setProperties({
id: null,
realName: null,
address: null,
ipfsHash: null,
isCore: true,
newContributor: Contributor.create({ kind: 'person' }),
inProgress: false
});
},
actions: {
@@ -49,12 +66,7 @@ export default Ember.Component.extend({
if (this.get('isValid')) {
this.set('inProgress', true);
this.get('kredits').addContributor(
this.get('address'),
this.get('realName'),
this.get('isCore'),
this.get('id')
).then(contributor => {
this.get('kredits').addContributor(this.get('newContributor')).then(contributor => {
this.reset();
this.get('contributors').pushObject(contributor);
window.scroll(0,0);
+43 -16
View File
@@ -1,31 +1,58 @@
<form {{action "save" on="submit"}}>
<p>
{{input type="checkbox" name="is-core" id="is-core" checked=isCore}}
{{input type="checkbox" name="is-core" id="is-core" checked=newContributor.isCore}}
<label for="is-core" class="checkbox">
Core team member (can add contributors)
</label>
</p>
<p>
{{input name="id"
type="text"
placeholder="GitHub UID (123)"
value=id
class=(if isValidId 'valid' '')}}
</p>
<p>
{{input name="realname"
type="text"
placeholder="GitHub username"
value=realName
class=(if isValidRealName 'valid' '')}}
</p>
<p>
{{input name="address"
type="text"
placeholder="0xF18E631Ea191aE4ebE70046Fcb01a436554421BA4"
value=address
value=newContributor.address
class=(if isValidAddress 'valid' '')}}
</p>
<p>
<select required onchange={{action (mut newContributor.kind) value="target.value"}}>
<option value="person" selected={{eq newContributor.kind "person"}}>Person</option>
<option value="organization" selected={{eq newContributor.kind "organization"}}>Organization</option>
</select>
</p>
<p>
{{input name="name"
type="text"
placeholder="Name"
value=newContributor.name
class=(if isValidName 'valid' '')}}
</p>
<p>
{{input name="url"
type="text"
placeholder="URL"
value=newContributor.url
class=(if isValidURL 'valid' '')}}
</p>
<p>
{{input name="github_uid"
type="text"
placeholder="GitHub UID (123)"
value=newContributor.github_uid
class=(if isValidGithubUID 'valid' '')}}
</p>
<p>
{{input name="github_username"
type="text"
placeholder="GitHub username"
value=newContributor.github_username
class=(if isValidGithubUsername 'valid' '')}}
</p>
<p>
{{input name="wiki_username"
type="text"
placeholder="Wiki Username"
value=newContributor.wiki_username
class=(if isValidWikiUsername 'valid' '')}}
</p>
<p class="actions">
{{input type="submit" value=(if inProgress 'Processing' 'Save') disabled=inProgress}}
</p>
+29 -23
View File
@@ -2,6 +2,7 @@ import Ember from 'ember';
const {
Component,
isPresent,
inject: {
service
},
@@ -13,47 +14,52 @@ export default Component.extend({
kredits: service(),
proposal: null,
contributors: null,
inProgress: false,
isValidRecipient: computed('proposal.recipientAddress', function() {
return this.get('kredits.web3Instance').isAddress(this.get('proposal.recipientAddress'));
return this.get('kredits.web3').isAddress(this.get('proposal.recipientAddress'));
}),
isValidAmount: computed('proposal.amount', function() {
// TODO
return true;
return parseInt(this.get('proposal.amount'), 10) > 0;
}),
isValidUrl: computed('proposal.url', function() {
// TODO
return true;
return isPresent(this.get('proposal.url'));
}),
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('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.');
return false;
}
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}}
+5 -7
View File
@@ -3,7 +3,7 @@
<tr class="{{if contributor.isCurrentUser 'current-user'}}" {{action "toggleContributorInfo" contributor}}>
<td class="person">
<img class="avatar" src={{contributor.avatarURL}}>
{{contributor.github_username}}
{{contributor.name}}
</td>
<td class="kredits">
<span class="amount">{{contributor.kredits}}</span>
@@ -12,14 +12,12 @@
</tr>
<tr class="metadata {{if contributor.isCurrentUser 'current-user'}} {{if contributor.showMetadata 'visible'}}">
<td colspan="2">
<dl>
<dt>Ethereum address</dt>
<dd><a href="https://testnet.etherscan.io/address/{{contributor.address}}">{{contributor.address}}</a></dd>
<ul>
<li><a href="https://testnet.etherscan.io/address/{{contributor.address}}">Inspect Ethereum transactions</a></li>
{{#if contributor.ipfsHash}}
<dt>IPFS profile data</dt>
<dd><a href="https://ipfs.io/ipfs/{{contributor.ipfsHash}}">{{contributor.ipfsHash}}</a></dd>
<li><a href="https://ipfs.io/ipfs/{{contributor.ipfsHash}}">Inspect IPFS profile</a></li>
{{/if}}
</dl>
</ul>
</td>
</tr>
{{/each}}
+3 -4
View File
@@ -1,9 +1,8 @@
{{#each proposals as |proposal|}}
<li>
<span class="id">#{{proposal.id}}</span>
Issue{{#if proposal.executed}}d{{/if}}
<li data-proposal-id={{proposal.id}} title="({{proposal.kind}}) {{proposal.description}}">
<span class="category {{proposal.kind}}">♥</span>
<span class="amount">{{proposal.amount}}</span><span class="symbol">₭S</span>
to <span class="recipient" title="{{proposal.recipientAddress}}">{{proposal.recipientName}}</span>
for <span class="recipient">{{proposal.recipientName}}</span>
<span class="votes">({{proposal.votesCount}}/{{proposal.votesNeeded}} votes)</span>
{{#unless proposal.executed}}<button {{action "confirm" proposal.id}}>+1</button>{{/unless}}
+2 -2
View File
@@ -23,7 +23,7 @@ export default Ember.Controller.extend({
let proposals = this.get('model.proposals')
.filterBy('executed', false)
.map(p => {
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).github_username);
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
return p;
});
return proposals;
@@ -33,7 +33,7 @@ export default Ember.Controller.extend({
let proposals = this.get('model.proposals')
.filterBy('executed', true)
.map(p => {
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).github_username);
p.set('recipientName', this.findContributorByAddress(p.get('recipientAddress')).name);
return p;
});
return proposals;
+98 -10
View File
@@ -1,10 +1,18 @@
import Ember from 'ember';
const {
isPresent,
} = Ember;
export default Ember.Object.extend({
address: null,
name: null,
kind: null,
url: null,
github_username: null,
github_uid: null,
wiki_username: null,
ipfsHash: null,
kredits: null,
isCore: false,
@@ -14,18 +22,98 @@ export default Ember.Object.extend({
return `https\:\/\/avatars2.githubusercontent.com/u/${this.get('github_uid')}?v=3&s=128`;
}.property('github_uid'),
serialize() {
return JSON.stringify({
profiles: {
'github.com': {
uid: this.get('github_uid'),
username: this.get('github_username'),
/**
* Loads the contributor's profile data from IPFS and sets local instance
* properties from it
*
* @method
* @public
*/
loadProfile(ipfs) {
let promise = new Ember.RSVP.Promise((resolve, reject) => {
ipfs.getFile(this.get('ipfsHash')).then(content => {
let profileJSON = JSON.parse(content);
let profile = Ember.Object.create(profileJSON);
this.setProperties({
name: profile.get('name'),
kind: profile.get('kind')
});
let accounts = profile.get('accounts');
let github = accounts.findBy('site', 'github.com');
let wiki = accounts.findBy('site', 'wiki.kosmos.org');
if (isPresent(github)) {
this.setProperties({
github_username: github.username,
github_uid: github.uid,
});
}
// 'wiki.kosmos.org': {
// username: this.get('wiki_username')
// }
}
if (isPresent(wiki)) {
this.setProperties({
wiki_username: wiki.username
});
}
Ember.Logger.debug('[contributor] loaded contributor profile', profile);
resolve();
}).catch((err) => {
Ember.Logger.error('[contributor] error trying to load contributor profile', this.get('ipfsHash'), err);
reject(err);
});
});
return promise;
},
/**
* Creates a JSON-LD object of the contributor, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
*
* @method
* @public
*/
toJSON() {
let contributor = {
"@context": "https://schema.kosmos.org",
"@type": "Contributor",
"kind": this.get('kind'),
"name": this.get('name'),
"accounts": []
};
if (Ember.isPresent(this.get('url'))) {
contributor["url"] = this.get('url');
}
if (Ember.isPresent(this.get('github_uid'))) {
contributor.accounts.push({
"site": "github.com",
"uid": this.get('github_uid'),
"username": this.get('github_username'),
"url": `https://github.com/${this.get('github_username')}`
});
}
if (Ember.isPresent(this.get('wiki_username'))) {
contributor.accounts.push({
"site": "wiki.kosmos.org",
"username": this.get('wiki_username'),
"url": `https://wiki.kosmos.org/User:${this.get('wiki_username')}`
});
}
return contributor;
},
/**
* Returns the JSON-LD representation of the model as a string
*
* @method
* @public
*/
serialize() {
return JSON.stringify(this.toJSON());
}
});
+87 -1
View File
@@ -1,16 +1,102 @@
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,
/**
* Loads the contribution details from IPFS and sets local instance
* properties from it
*
* @method
* @public
*/
loadContribution(ipfs) {
let promise = new Ember.RSVP.Promise((resolve, reject) => {
ipfs.getFile(this.get('ipfsHash')).then(content => {
let contributionJSON = JSON.parse(content);
let contribution = Ember.Object.create(contributionJSON);
this.setProperties({
kind: contribution.get('kind'),
description: contribution.get('description'),
url: contribution.get('url')
});
// TODO load details
// let details = profile.get('accounts');
Ember.Logger.debug('[proposal] loaded contribution details', contributionJSON);
resolve();
}).catch((err) => {
Ember.Logger.error('[proposal] error trying to load contribution details', this.get('ipfsHash'), err);
reject(err);
});
});
return promise;
},
/**
* 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());
}
});
+3 -1
View File
@@ -1,4 +1,5 @@
import Ember from 'ember';
import Contributor from 'kredits-web/models/contributor';
export default Ember.Route.extend({
@@ -10,7 +11,8 @@ export default Ember.Route.extend({
return Ember.RSVP.hash({
contributors: kredits.getContributors(),
totalSupply: kredits.getValueFromContract('tokenContract', 'totalSupply'),
proposals: kredits.getProposals()
proposals: kredits.getProposals(),
newContributor: Contributor.create({ kind: 'person' })
});
}
+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
});
+3
View File
@@ -26,6 +26,9 @@ export default Ember.Service.extend({
getFile(hash) {
return this.get('ipfs').cat(hash, { buffer: true }).then(res => {
return res.toString();
}, err => {
Ember.Logger.error('[ipfs] error trying to fetch file', hash, err);
throw err;
});
}
+33 -24
View File
@@ -4,6 +4,7 @@ import config from 'kredits-web/config/environment';
import Contributor from 'kredits-web/models/contributor';
import Proposal from 'kredits-web/models/proposal';
import kreditsContracts from 'npm:kredits-contracts';
import uuid from 'npm:uuid';
const {
Service,
@@ -101,17 +102,19 @@ 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('person', person);
Ember.Logger.debug('[kredits] person', address, person);
let contributor = Contributor.create({
address: address,
github_username: person[1],
github_uid: person[0],
ipfsHash: person[2],
kredits: balance.toNumber(),
isCurrentUser: this.get('currentUserAccounts').includes(address)
});
Ember.Logger.debug('[kredits] contributor', contributor);
resolve(contributor);
contributor.loadProfile(this.get('ipfs')).then(
() => resolve(contributor),
err => reject(err)
);
});
});
}).catch(err => reject(err));
@@ -145,8 +148,16 @@ export default Service.extend({
url : p[6],
ipfsHash : p[7]
});
Ember.Logger.debug('[kredits] proposal', proposal);
resolve(proposal);
if (proposal.get('ipfsHash')) {
proposal.loadContribution(this.get('ipfs')).then(
() => resolve(proposal),
err => reject(err)
);
} else {
Ember.Logger.warn('[kredits] proposal from blockchain is missing IPFS hash', proposal);
resolve(proposal);
}
}).catch(err => reject(err));
});
return promise;
@@ -175,23 +186,20 @@ export default Service.extend({
});
},
addContributor(address, name, isCore, id) {
Ember.Logger.debug('[kredits] add contributor', name, address);
addContributor(contributor) {
Ember.Logger.debug('[kredits] add contributor', contributor);
let contributor = Contributor.create({
address: address,
github_username: name,
github_uid: id,
contributor.setProperties({
kredits: 0,
isCore: isCore,
isCurrentUser: this.get('currentUserAccounts').includes(address)
isCurrentUser: this.get('currentUserAccounts').includes(contributor.address)
});
let id = uuid.v4();
return new Ember.RSVP.Promise((resolve, reject) => {
this.get('ipfs').storeFile(contributor.serialize()).then(ipfsHash => {
contributor.set('ipfsHash', ipfsHash);
Ember.Logger.debug('ADD', address, name, ipfsHash, isCore, id);
this.get('kreditsContract').addContributor(address, name, ipfsHash, isCore, id, (err, data) => {
this.get('kreditsContract').addContributor(contributor.address, contributor.name, contributor.ipfsHash, contributor.isCore, id, (err, data) => {
if (err) { reject(err); return; }
Ember.Logger.debug('[kredits] add contributor response', data);
resolve(contributor);
@@ -205,14 +213,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();
});
});
});
},
+8
View File
@@ -0,0 +1,8 @@
$blue: #68d7fb;
$purple: #8f68fb;
$pink: #e068fb;
$green: #97fb68;
$yellow: #fbe468;
$red: #fb6868;
$primaryColor: $blue;
+7 -6
View File
@@ -15,6 +15,7 @@
@import "neat";
@import "settings/breakpoints";
@import "layout";
@import "colors";
$font-family-sans: 'Open Sans', sans-serif;
@@ -23,7 +24,7 @@ body {
background-repeat: none;
background-attachment: fixed;
font-family: $font-family-sans;
font-size: 16px;
font-size: 14px;
font-weight: 300;
color: #fff;
}
@@ -43,7 +44,7 @@ h1, h2, h3, h4, h5, input, button {
section {
h2 {
font-size: 2.8rem;
color: lightblue;
color: $primaryColor;
@include media($mobile) {
font-size: 2rem;
}
@@ -54,7 +55,7 @@ section {
p.stats {
padding-top: 3rem;
font-size: 1rem;
color: lightblue;
color: white;
text-align: center;
span.number {
font-weight: 600;
@@ -70,14 +71,14 @@ section {
.actions {
padding-top: 3rem;
font-size: 1rem;
color: lightblue;
color: $primaryColor;
text-align: center;
@include media($mobile) {
padding-top: 2rem;
}
a {
color: lightblue;
color: $primaryColor;
}
}
}
@@ -87,7 +88,7 @@ button, input[type=submit] {
display: inline-block;
border: 1px solid rgba(22, 21, 40, 1);
background-color: rgba(22, 21, 40, 0.6);
color: lightblue;
color: $primaryColor;
border-radius: 3px;
font-weight: 500;
text-transform: uppercase;
+1 -1
View File
@@ -9,7 +9,7 @@ section#add-contributor, section#add-proposal {
padding-top: 1rem;
text-align: center;
a {
color: lightblue;
color: $primaryColor;
margin-left: 1rem;
}
}
+17 -31
View File
@@ -15,54 +15,40 @@ table.contributor-list {
&.metadata {
height: 0;
transition: all 0.2s ease-out;
td {
padding: 0 1.2rem;
transition: all 0.2s ease-out;
}
a {
color: lightblue;
color: $primaryColor;
&:hover, &:active {
color: #fff;
}
}
dl {
ul {
list-style: none;
display: block;
width: 100%;
font-size: 1rem;
dt, dd {
display: inline-block;
overflow: hidden;
height: 0;
line-height: 1.6rem;
transition: all 0.2s ease-out;
}
dt {
clear: both;
float: left;
width: 30%;
}
dd {
float: right;
width: 70%;
text-align: right;
}
}
overflow: hidden;
height: 0;
transition: all 0.2s ease-out;
&.visible {
td {
padding: 1rem 1.2rem;
}
dl {
dt, dd {
height: auto;
li {
display: inline;
&+li {
margin-left: 1rem;
}
}
}
&.visible {
height: auto;
ul {
height: auto;
}
}
}
td {
+2 -2
View File
@@ -11,7 +11,7 @@
margin-top: 12rem;
text-align: center;
font-size: 1.4rem;
color: lightblue;
color: $primaryColor;
@include media($mobile) {
margin-top: 6rem;
@@ -23,7 +23,7 @@
margin-bottom: 2rem;
#path-comet {
fill: lightblue;
fill: $primaryColor;
opacity: 0.1;
animation-name: pulse;
+9 -4
View File
@@ -14,9 +14,14 @@ ul.proposal-list {
border-top: 1px solid rgba(255,255,255,0.2);
}
.id {
color: lightblue;
.category {
color: $blue;
padding-right: 0.2rem;
&.community { color: $red; }
&.dev { color: $pink; }
&.design { color: $yellow; }
&.docs { color: $green; }
&.ops { color: $purple; }
}
.amount {
font-weight: 500;
@@ -31,8 +36,8 @@ ul.proposal-list {
}
.votes {
font-size: 1rem;
color: lightblue;
padding-left: 1rem;
color: $primaryColor;
padding-left: 0.5rem;
}
button {
+2 -2
View File
@@ -54,8 +54,8 @@
</header>
<div class="content">
{{add-contributor kredits=kredits
contributors=model.contributors
{{add-contributor contributors=model.contributors
newContributor=model.newContributor
contractInteractionEnabled=contractInteractionEnabled}}
</div>
</section>
+2 -1
View File
@@ -23,6 +23,7 @@ module.exports = function(environment) {
},
browserify: {
tests: true,
transform: [
["babelify", {
presets: ["es2015"],
@@ -38,7 +39,7 @@ module.exports = function(environment) {
ipfs: {
host: 'ipfs.kosmos.org',
port: '5444',
protocol: 'http'
protocol: 'https'
}
};
+4 -1
View File
@@ -3,7 +3,7 @@
"version": "1.0.0",
"description": "Contribution dashboard of the Kosmos project",
"license": "MIT",
"author": "Kosmos Community",
"author": "Kosmos Contributors <mail@kosmos.org>",
"contributors": [
"Garret Alfert <alfert@wevelop.de>",
"Sebastian Kippe <sebastian@kip.pe>",
@@ -55,8 +55,11 @@
"ember-resolver": "^2.0.3",
"ember-truth-helpers": "1.3.0",
"ipfs-api": "^12.1.7",
"kosmos-schemas": "~1.1.1",
"kredits-contracts": "^2.4.0",
"loader.js": "^4.0.10",
"tv4": "^1.3.0",
"uuid": "^3.0.1",
"web3": "^0.18.2"
},
"engines": {
+18 -1
View File
@@ -1,9 +1,26 @@
import { moduleFor, test } from 'ember-qunit';
import schemas from 'npm:kosmos-schemas';
import tv4 from 'npm:tv4';
moduleFor('model:contributor', 'Unit | Model | contributor');
test('avatarURL returns correct URL', function(assert) {
test('#avatarURL() returns correct URL', function(assert) {
let model = this.subject();
model.set('github_uid', '318');
assert.equal(model.get('avatarURL'), 'https://avatars2.githubusercontent.com/u/318?v=3&s=128');
});
test('#toJSON() returns a valid JSON-LD representation of the model', function(assert) {
let model = this.subject();
model.setProperties({
name: 'Satoshi Nakamoto',
kind: 'person',
github_uid: 123,
github_username: 'therealsatoshi',
wiki_username: 'Satoshi',
});
assert.ok(tv4.validate(model.toJSON(), schemas['contributor']));
});
+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']));
});