Merge pull request #42 from 67P/kredits-module

Refactor contract interaction in its own module
This commit was merged in pull request #42.
This commit is contained in:
2018-04-10 13:46:57 +00:00
committed by GitHub
25 changed files with 379 additions and 377 deletions
+1 -1
View File
@@ -53,7 +53,7 @@ export default Component.extend({
saved.then(() => { saved.then(() => {
this.reset(); this.reset();
window.scroll(0,0); window.scroll(0,0);
window.alert('Contributor added.'); window.alert('Proposal added.');
}); });
} }
} }
+3 -5
View File
@@ -8,13 +8,11 @@ export default Controller.extend({
init() { init() {
this._super(...arguments); this._super(...arguments);
this.get('kredits.kreditsContract') let contract = this.get('kredits.kredits').Operator.contract;
.then((contract) => {
contract.onproposalvoted = this._handleProposalVoted.bind(this); contract.onproposalvoted = this._handleProposalVoted.bind(this);
contract.onproposalcreated = this._handleProposalCreated.bind(this); contract.onproposalcreated = this._handleProposalCreated.bind(this);
contract.onproposalexecuted = this._handleProposalExecuted.bind(this); contract.onproposalexecuted = this._handleProposalExecuted.bind(this);
// TODO: transfer on the token contract // TODO: transfer on the token contract
});
}, },
contributors: alias('model.contributors'), contributors: alias('model.contributors'),
@@ -50,7 +48,7 @@ export default Controller.extend({
return; return;
} }
proposal = this.get('kredits').getProposalById(proposalId); proposal = this.get('kredits.kredits').Operator.getById(proposalId);
this.get('proposals').pushObject(proposal); this.get('proposals').pushObject(proposal);
}, },
@@ -70,7 +68,7 @@ export default Controller.extend({
this.get('contributors') this.get('contributors')
.findBy('id', recipientId.toString()) .findBy('id', recipientId.toString())
.incrementProperty('balance', amount.toNumber()); .incrementProperty('balance', amount);
}, },
_handleProposalVoted(proposalId, voter, totalVotes) { _handleProposalVoted(proposalId, voter, totalVotes) {
+10
View File
@@ -0,0 +1,10 @@
export default class Base {
constructor(contract) {
this.contract = contract;
}
get functions() {
return this.contract.functions;
}
}
+61
View File
@@ -0,0 +1,61 @@
import ethers from 'npm:ethers';
import RSVP from 'rsvp';
import Kredits from '../kredits';
import ContributorSerializer from '../serializers/contributor';
import Base from './base';
export default class Contributor extends Base {
all() {
return this.functions.contributorsCount()
.then((count) => {
count = count.toNumber();
let contributors = [];
for (let id = 1; id <= count; id++) {
contributors.push(this.getById(id));
}
return RSVP.all(contributors);
});
}
getById(id) {
id = ethers.utils.bigNumberify(id);
return this.functions.getContributorById(id)
.then((data) => {
// TODO: remove as soon as the contract provides the id
data.id = id;
// TODO: rename address to account
data.address = data.account;
return data;
})
// Fetch IPFS data if available
.then((data) => {
return Kredits.ipfs.catAndMerge(data, ContributorSerializer.deserialize);
});
}
add(contributorAttr) {
let json = ContributorSerializer.serialize(contributorAttr);
// TODO: validate against schema
return Kredits.ipfs
.add(json)
.then((ipfsHashAttr) => {
let contributor = [
contributorAttr.address,
ipfsHashAttr.ipfsHash,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
contributorAttr.isCore,
];
console.log('[kredits] addContributor', ...contributor);
return this.functions.addContributor(...contributor);
});
}
}
+9
View File
@@ -0,0 +1,9 @@
import Contributor from './contributor';
import Operator from './operator';
import Token from './token';
export default {
Contributors: Contributor,
Operator,
Token
};
+61
View File
@@ -0,0 +1,61 @@
import ethers from 'npm:ethers';
import RSVP from 'rsvp';
import Kredits from '../kredits';
import ContributionSerializer from '../serializers/contribution';
import Base from './base';
export default class Operator extends Base {
all() {
return this.functions.proposalsCount()
.then((count) => {
count = count.toNumber();
let proposals = [];
for (let id = 0; id < count; id++) {
proposals.push(this.getById(id));
}
return RSVP.all(proposals);
});
}
getById(id) {
id = ethers.utils.bigNumberify(id);
return this.functions.proposals(id)
.then((data) => {
// TODO: remove as soon as the contract provides the id
data.id = id;
// TODO: rename creatorAddress to creator
data.creatorAddress = data.creator;
return data;
})
// Fetch IPFS data if available
.then((data) => {
return Kredits.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
});
}
addProposal(proposalAttr) {
let json = ContributionSerializer.serialize(proposalAttr);
// TODO: validate against schema
return Kredits.ipfs
.add(json)
.then((ipfsHashAttr) => {
let proposal = [
proposalAttr.recipientId,
proposalAttr.amount,
ipfsHashAttr.ipfsHash,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
];
console.log('[kredits] addProposal', ...proposal);
return this.functions.addProposal(...proposal);
});
}
}
+4
View File
@@ -0,0 +1,4 @@
import Base from './base';
export default class Token extends Base {
}
+1 -10
View File
@@ -1,10 +1 @@
import ContributionSerializer from './serializers/contribution'; export { default } from './kredits';
import ContributorSerializer from './serializers/contributor';
import { fromBytes32, toBytes32 } from './utils/multihash';
export {
ContributionSerializer,
ContributorSerializer,
fromBytes32,
toBytes32
};
+77
View File
@@ -0,0 +1,77 @@
import ethers from 'npm:ethers';
import RSVP from 'rsvp';
import abis from 'contracts/abis';
import addresses from 'contracts/addresses';
import contracts from './contracts';
import IPFS from './utils/ipfs';
// Helpers
function capitalize(word) {
let [first, ...rest] = word;
return `${first.toUpperCase()}${rest.join('')}`;
}
export default class Kredits {
constructor(provider, signer, addresses) {
this.provider = provider;
this.signer = signer;
// Initialize our registry contract
this.addresses = addresses;
this.contracts = {};
}
static setup(provider, signer, ipfsConfig) {
this.ipfsConfig = ipfsConfig;
this.ipfs = new IPFS(ipfsConfig);
let registryContract = this.initRegistryContract(provider);
let addresses = Object.keys(contracts).reduce((mem, name) => {
let contractName = capitalize(name);
mem[contractName] = registryContract.functions.getProxyFor(contractName);
return mem;
}, {});
return RSVP.hash(addresses)
.then((addresses) => {
return new Kredits(provider, signer, addresses);
});
}
static initRegistryContract(provider) {
let address = addresses['Registry'][provider.chainId];
let abi = abis['Registry'];
console.log('Initialize registry contract:', address, abi, provider);
return new ethers.Contract(address, abi, provider);
}
get Contributor() {
// TODO: rename to contributor
return this.contractFor('contributors');
}
get Operator() {
return this.contractFor('operator');
}
get Token() {
return this.contractFor('token');
}
// Should be private
contractFor(name) {
if (this.contracts[name]) {
return this.contracts[name];
}
let contractName = capitalize(name);
let address = this.addresses[contractName];
let contract = new ethers.Contract(address, abis[contractName], this.signer);
this.contracts[name] = new contracts[contractName](contract);
return this.contracts[name];
}
}
+1 -1
View File
@@ -18,7 +18,7 @@ export default class Contributor {
description, description,
details, details,
url, url,
} = JSON.parse(serialized); } = JSON.parse(serialized.toString('utf8'));
return { return {
kind, kind,
+1 -1
View File
@@ -18,7 +18,7 @@ export default class Contributor {
kind, kind,
url, url,
accounts, accounts,
} = JSON.parse(serialized); } = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, wiki_username; let github_username, github_uid, wiki_username;
let github = accounts.find((a) => a.site === 'github.com'); let github = accounts.find((a) => a.site === 'github.com');
+55
View File
@@ -0,0 +1,55 @@
import ipfsAPI from 'npm:ipfs-api';
import multihashes from 'npm:multihashes';
export default class IPFS {
constructor(config) {
this._ipfsAPI = ipfsAPI(config);
this._config = config;
}
catAndMerge(data, deserialize) {
// if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) {
return data;
}
return this.cat(data)
.then(deserialize)
.then((attributes) => {
return Object.assign({}, data, attributes);
});
}
add(data) {
return this._ipfsAPI
.add(new this._ipfsAPI.Buffer(data))
.then((res) => {
return this.decodeHash(res[0].hash);
});
}
cat(hashData) {
let ipfsHash = hashData; // default - if it is a string
if (hashData.hasOwnProperty('hashSize')) {
ipfsHash = this.encodeHash(hashData);
}
return this._ipfsAPI.cat(ipfsHash);
}
decodeHash(ipfsHash) {
let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash));
return {
ipfsHash: '0x' + multihashes.toHexString(multihash.digest),
hashSize: multihash.length,
hashFunction: multihash.code,
sourceHash: ipfsHash
};
}
encodeHash(hashData) {
let digest = this._ipfsAPI.Buffer.from(hashData.ipfsHash.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
}
}
-33
View File
@@ -1,33 +0,0 @@
import bs58 from 'npm:bs58';
import NpmBuffer from 'npm:buffer';
const Buffer = NpmBuffer.Buffer;
function fromBytes32({ digest, hashFunction, hashSize }) {
if (hashSize === 0) {
return;
}
const hashBytes = Buffer.from(digest.slice(2), 'hex');
const multiHashBytes = new (hashBytes.constructor)(2 + hashBytes.length);
multiHashBytes[0] = hashFunction;
multiHashBytes[1] = hashSize;
multiHashBytes.set(hashBytes, 2);
return bs58.encode(multiHashBytes);
}
function toBytes32(string) {
const decoded = bs58.decode(string);
return {
digest: `0x${decoded.slice(2).toString('hex')}`,
hashFunction: decoded[0],
hashSize: decoded[1],
};
}
export {
fromBytes32,
toBytes32
};
+3 -2
View File
@@ -1,12 +1,13 @@
import computed from 'ember-computed'; import computed from 'ember-computed';
import EmberObject from 'ember-object'; import EmberObject from 'ember-object';
import bignumber from 'kredits-web/utils/cps/bignumber';
export default EmberObject.extend({ export default EmberObject.extend({
// Contract // Contract
id: null, id: bignumber('idRaw', 'toString'),
// TODO: Should we rename it to account like in the contract? // TODO: Should we rename it to account like in the contract?
address: null, address: null,
balance: 0, balance: bignumber('balanceRaw', 'toNumber'),
isCore: false, isCore: false,
ipfsHash: null, ipfsHash: null,
+6 -5
View File
@@ -1,14 +1,15 @@
import EmberObject from 'ember-object'; import EmberObject from 'ember-object';
import { alias } from 'ember-computed'; import { alias } from 'ember-computed';
import bignumber from 'kredits-web/utils/cps/bignumber';
export default EmberObject.extend({ export default EmberObject.extend({
// Contract // Contract
id: null, id: bignumber('idRaw', 'toString'),
creatorAddress: null, creatorAddress: null,
recipientId: null, recipientId: bignumber('recipientIdRaw', 'toString'),
amount: null, amount: bignumber('amountRaw', 'toNumber'),
votesCount: null, votesCount: bignumber('votesCountRaw', 'toNumber'),
votesNeeded: null, votesNeeded: bignumber('votesNeededRaw', 'toNumber'),
executed: null, executed: null,
ipfsHash: null, ipfsHash: null,
+1 -1
View File
@@ -7,7 +7,7 @@ export default Route.extend({
beforeModel(transition) { beforeModel(transition) {
const kredits = this.get('kredits'); const kredits = this.get('kredits');
return kredits.initEthProvider().then(() => { return kredits.setup().then(() => {
if (kredits.get('accountNeedsUnlock')) { if (kredits.get('accountNeedsUnlock')) {
if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) { if (confirm('It looks like you have an Ethereum wallet available. Please unlock your account.')) {
transition.retry(); transition.retry();
+1 -2
View File
@@ -9,8 +9,7 @@ export default Ember.Route.extend({
newContributor.set('kind', 'person'); newContributor.set('kind', 'person');
let kredits = this.get('kredits'); let kredits = this.get('kredits');
let totalSupply = kredits.get('tokenContract') let totalSupply = kredits.get('kredits').Token.functions.totalSupply();
.then((contract) => contract.totalSupply());
return Ember.RSVP.hash({ return Ember.RSVP.hash({
contributors: kredits.getContributors(), contributors: kredits.getContributors(),
-34
View File
@@ -1,34 +0,0 @@
import Ember from 'ember';
import ipfsAPI from 'npm:ipfs-api';
import config from 'kredits-web/config/environment';
export default Ember.Service.extend({
ipfsInstance: null,
ipfs: function() {
if (this.get('ipfsInstance')) {
return this.get('ipfsInstance');
}
let ipfs = ipfsAPI(config.ipfs);
this.set('ipfsInstance', ipfs);
return ipfs;
}.property('ipfsInstance'),
storeFile(content) {
let ipfs = this.get('ipfs');
return ipfs.add(new ipfs.Buffer(content)).then(res => {
Ember.Logger.debug('[ipfs] stored content in IPFS', content, res[0].hash);
return res[0].hash;
});
},
getFile(hash) {
return this.get('ipfs').cat(hash).then(res => {
return res.toString('utf8');
}, err => {
Ember.Logger.warn('[ipfs] error trying to fetch file', hash, err);
});
}
});
+51 -253
View File
@@ -1,36 +1,21 @@
import ethers from 'npm:ethers'; import ethers from 'npm:ethers';
import Kredits from 'kredits-web/lib/kredits';
import RSVP from 'rsvp'; import RSVP from 'rsvp';
import Ember from 'ember'; import Ember from 'ember';
import Service from 'ember-service'; import Service from 'ember-service';
import injectService from 'ember-service/inject';
import computed, { alias } from 'ember-computed'; import computed, { alias } from 'ember-computed';
import { isEmpty, isPresent } from 'ember-utils'; import { isEmpty, isPresent } from 'ember-utils';
import config from 'kredits-web/config/environment'; import config from 'kredits-web/config/environment';
import abis from 'contracts/abis';
import addresses from 'contracts/addresses';
import {
ContributionSerializer,
ContributorSerializer,
fromBytes32,
toBytes32
} from 'kredits-web/lib/kredits';
const { const {
getOwner, getOwner,
Logger: { Logger: {
debug, debug
error
} }
} = Ember; } = Ember;
export default Service.extend({ export default Service.extend({
ipfs: injectService(),
ethProvider: null, ethProvider: null,
currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded. currentUserAccounts: null, // default to not having an account. this is the wen web3 is loaded.
currentUser: null, currentUser: null,
@@ -48,7 +33,7 @@ export default Service.extend({
// this is called called in the routes beforeModel(). So it is initialized before everything else // this is called called in the routes beforeModel(). So it is initialized before everything else
// and we can rely on the ethProvider and the potential currentUserAccounts to be available // and we can rely on the ethProvider and the potential currentUserAccounts to be available
initEthProvider: function() { initEthProvider: function() {
return new Ember.RSVP.Promise((resolve) => { return new RSVP.Promise((resolve) => {
let ethProvider; let ethProvider;
let networkId; let networkId;
if (typeof window.web3 !== 'undefined') { if (typeof window.web3 !== 'undefined') {
@@ -58,15 +43,8 @@ export default Service.extend({
ethProvider.listAccounts().then((accounts) => { ethProvider.listAccounts().then((accounts) => {
this.set('currentUserAccounts', accounts); this.set('currentUserAccounts', accounts);
this.set('ethProvider', ethProvider); this.set('ethProvider', ethProvider);
if (accounts.length > 0) {
this.get('getCurrentUser').then((contributorData) => {
this.set('currentUser', contributorData);
resolve(ethProvider); resolve(ethProvider);
}); });
} else {
resolve(ethProvider);
}
});
} else { } else {
debug('[kredits] Creating new instance from npm module class'); debug('[kredits] Creating new instance from npm module class');
let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl; let providerUrl = localStorage.getItem('config:web3ProviderUrl') || config.web3ProviderUrl;
@@ -79,36 +57,24 @@ export default Service.extend({
}); });
}, },
registryContract: computed('ethProvider', function() { setup() {
let networkId = this.get('ethProvider').chainId; return this.initEthProvider().then((ethProvider) => {
let registry = new ethers.Contract( let signer = ethProvider.getSigner();
addresses['Registry'][networkId], return Kredits.setup(ethProvider, signer, config.ipfs).then((kredits) => {
abis['Registry'], this.set('kredits', kredits);
this.get('ethProvider')
);
return registry;
}),
contributorsContract: computed('ethProvider', function() { // TODO: Cleanup
return this.contractFor('Contributors'); if (this.get('currentUserAccounts').length > 0) {
}), this.get('getCurrentUser').then((contributorData) => {
this.set('currentUser', contributorData);
kreditsContract: computed('ethProvider', function() { });
return this.contractFor('Operator'); }
}), return kredits;
});
tokenContract: computed('ethProvider', function() {
return this.contractFor('Token');
}),
contractFor(name) {
return this.get('registryContract').functions.getProxyFor(name)
.then((address) => {
debug('[kredits] get contract', name, address);
return new ethers.Contract(address, abis[name], this.get('ethProvider').getSigner());
}); });
}, },
// TODO: Only assign valid attributes
buildModel(name, attributes) { buildModel(name, attributes) {
debug('[kredits] build', name, attributes); debug('[kredits] build', name, attributes);
let model = getOwner(this).lookup(`model:${name}`); let model = getOwner(this).lookup(`model:${name}`);
@@ -122,237 +88,69 @@ export default Service.extend({
return model; return model;
}, },
getContributorById(id) {
id = ethers.utils.bigNumberify(id);
return this.get('contributorsContract')
.then((contract) => contract.getContributorById(id))
.then(this.reassembleIpfsHash)
// Set basic data
.then(({ account: address, balance, ipfsHash, isCore }) => {
let isCurrentUser = this.get('currentUserAccounts').includes(address);
return {
id: id,
address,
balance: balance.toNumber(),
ipfsHash,
isCore,
isCurrentUser,
};
})
// Fetch IPFS data if available
.then((data) => {
return this.fetchAndMergeIpfsData(data, ContributorSerializer);
})
.then((attributes) => {
return this.buildModel('contributor', attributes);
});
},
getContributors() {
return this.get('contributorsContract')
.then((contract) => contract.contributorsCount())
.then((count) => {
count = count.toNumber();
debug('[kredits] contributors count:', count);
let contributors = [];
for(var id = 1; id <= count; id++) {
contributors.push(this.getContributorById(id));
}
return RSVP.all(contributors);
});
},
reassembleIpfsHash(data) {
let { ipfsHash: digest, hashFunction, hashSize } = data;
data.ipfsHash = fromBytes32({ digest, hashFunction, hashSize });
delete data.hashFunction;
delete data.hashSize;
return data;
},
/**
* Loads the contributor's profile data from IPFS and returns the attributes
*
* @method
* @public
*/
fetchAndMergeIpfsData(data, Serializer) {
let ipfsHash = data.ipfsHash;
if (!ipfsHash) {
return data;
}
return this.get('ipfs')
.getFile(ipfsHash)
.then(Serializer.deserialize)
.then((attributes) => {
debug('[kredits] fetched ipfs data:', attributes);
return Object.assign({}, data, attributes);
})
.catch((err) => {
error('[kredits] error trying to fetch', ipfsHash, err);
});
},
getProposalById(id) {
id = ethers.utils.bigNumberify(id);
return this.get('kreditsContract')
.then((contract) => contract.proposals(id))
.then(this.reassembleIpfsHash)
// Set basic data
.then(({
creator: creatorAddress,
recipientId,
votesCount,
votesNeeded,
amount,
executed,
ipfsHash,
}) => {
return {
id: id,
creatorAddress,
recipientId: recipientId.toString(),
votesCount: votesCount.toNumber(),
votesNeeded: votesNeeded.toNumber(),
amount: amount.toNumber(),
executed,
ipfsHash
};
})
// Fetch IPFS data if available
.then((data) => {
return this.fetchAndMergeIpfsData(data, ContributionSerializer);
})
.then((attributes) => {
return this.buildModel('proposal', attributes);
});
},
getProposals() {
return this.get('kreditsContract')
.then((contract) => contract.proposalsCount())
.then((count) => {
count = count.toNumber();
debug('[kredits] proposals count:', count);
let proposals = [];
for(var i = 0; i < count; i++) {
proposals.push(this.getProposalById(i));
}
return RSVP.all(proposals);
});
},
vote(proposalId) {
debug('[kredits] vote for', proposalId);
return this.get('kreditsContract')
.then((contract) => contract.vote(proposalId))
.then((data) => {
debug('[kredits] vote response', data);
return data;
});
},
// TODO: extract common logic to module
addContributor(attributes) { addContributor(attributes) {
debug('[kredits] add contributor', attributes); debug('[kredits] add contributor', attributes);
let json = ContributorSerializer.serialize(attributes); return this.get('kredits').Contributor.add(attributes)
// TODO: validate against schema
return this.get('ipfs')
.storeFile(json)
// Set ipfsHash
.then((ipfsHash) => {
attributes.ipfsHash = ipfsHash;
return attributes;
})
.then((attributes) => {
return this.get('contributorsContract')
.then((contract) => {
let { address, isCore, ipfsHash } = attributes;
let { digest, hashFunction, hashSize } = toBytes32(ipfsHash);
let contributor = [
address,
digest,
hashFunction,
hashSize,
isCore,
];
debug('[kredits] addContributor', ...contributor);
return contract.addContributor(...contributor);
});
})
.then((data) => { .then((data) => {
debug('[kredits] add contributor response', data); debug('[kredits] add contributor response', data);
return this.buildModel('contributor', attributes); return this.buildModel('contributor', attributes);
}); });
}, },
getContributors() {
return this.get('kredits').Contributor.all()
.then((contributors) => {
return contributors.map((contributor) => {
return this.buildModel('contributor', contributor);
});
});
},
addProposal(attributes) { addProposal(attributes) {
debug('[kredits] add proposal', attributes); debug('[kredits] add proposal', attributes);
let json = ContributionSerializer.serialize(attributes); return this.get('kredits').Operator.addProposal(attributes)
// TODO: validate against schema
return this.get('ipfs')
.storeFile(json)
// Set ipfsHash
.then((ipfsHash) => {
delete attributes.contributorIpfsHash;
attributes.ipfsHash = ipfsHash;
return attributes;
})
.then((attributes) => {
return this.get('kreditsContract')
.then((contract) => {
let { recipientId, amount, ipfsHash } = attributes;
let { digest, hashFunction, hashSize } = toBytes32(ipfsHash);
let proposal = [
recipientId,
amount,
digest,
hashFunction,
hashSize,
];
debug('[kredits] addProposal', ...proposal);
return contract.addProposal(...proposal);
});
})
.then((data) => { .then((data) => {
debug('[kredits] add proposal response', data); debug('[kredits] add proposal response', data);
return this.buildModel('proposal', attributes); return this.buildModel('proposal', attributes);
}); });
}, },
getProposals() {
return this.get('kredits').Operator.all()
.then((proposals) => {
return proposals.map((proposal) => {
return this.buildModel('proposal', proposal);
});
});
},
vote(proposalId) {
debug('[kredits] vote for', proposalId);
return this.get('kredits').Operator.functions.vote(proposalId)
.then((data) => {
debug('[kredits] vote response', data);
return data;
});
},
// TODO: Cleanup
getCurrentUser: computed('ethProvider', function() { getCurrentUser: computed('ethProvider', function() {
if (isEmpty(this.get('currentUserAccounts'))) { if (isEmpty(this.get('currentUserAccounts'))) {
return RSVP.resolve(); return RSVP.resolve();
} }
return this.get('contributorsContract') return this.get('kredits').Contributor
.then((contract) => { .functions.getContributorIdByAddress(this.get('currentUserAccounts.firstObject'))
return contract.getContributorIdByAddress(this.get('currentUserAccounts.firstObject'))
.then((id) => { .then((id) => {
id = id.toNumber(); id = id.toNumber();
// check if the user is a contributor or not // check if the user is a contributor or not
if (id === 0) { if (id === 0) {
return RSVP.resolve(); return RSVP.resolve();
} else { } else {
return this.getContributorById(id); return this.get('kredits').Contributor.getById(id);
} }
}); });
});
}) })
}); });
+1 -1
View File
@@ -68,7 +68,7 @@
</header> </header>
<div class="content"> <div class="content">
{{#if currentUser.isCore}} {{#if kredits.currentUser.isCore}}
{{add-contributor contributors=model.contributors save=(action 'save')}} {{add-contributor contributors=model.contributors save=(action 'save')}}
{{else}} {{else}}
Only core team members can add new contributors. Please ask someone to set you up. Only core team members can add new contributors. Please ask someone to set you up.
+13
View File
@@ -0,0 +1,13 @@
import computed from 'ember-computed';
export default function(dependentKey, converterMethod) {
return computed(dependentKey, {
get () {
return this.get(dependentKey)[converterMethod]();
},
set (key, value) {
this.set(dependentKey, value);
return value[converterMethod]();
}
});
}
+4 -1
View File
@@ -8,7 +8,7 @@ const {
} = Ember; } = Ember;
moduleFor('controller:index', 'Unit | Controller | index', { moduleFor('controller:index', 'Unit | Controller | index', {
needs: ['service:ipfs', 'service:kredits'] needs: ['service:kredits']
}); });
let addFixtures = function(controller) { let addFixtures = function(controller) {
@@ -23,6 +23,9 @@ let addFixtures = function(controller) {
{ github_username: "trinity", github_uid: "123", balance: 5000 }, { github_username: "trinity", github_uid: "123", balance: 5000 },
{ github_username: "mouse", github_uid: "696", balance: 0 } { github_username: "mouse", github_uid: "696", balance: 0 }
].forEach(fixture => { ].forEach(fixture => {
// we expect a bignumer but I don't want to add the bignumber dependency here... so this is some hack to return an object that looks good enough for the test
let fakeBignumber = function(balance) { return { toNumber: function() { return balance; } }; };
fixture.balance = fakeBignumber(fixture.balance);
controller.get('model.contributors').push(Contributor.create(fixture)); controller.get('model.contributors').push(Contributor.create(fixture));
}); });
}; };
@@ -1,7 +1,7 @@
import { module, test } from 'ember-qunit'; import { module, test } from 'ember-qunit';
import schemas from 'npm:kosmos-schemas'; import schemas from 'npm:kosmos-schemas';
import tv4 from 'npm:tv4'; import tv4 from 'npm:tv4';
import { ContributionSerializer } from 'kredits-web/lib/kredits'; import ContributionSerializer from 'kredits-web/lib/kredits/serializers/contribution';
module('Serializers contribution'); module('Serializers contribution');
@@ -1,7 +1,7 @@
import { module, test } from 'ember-qunit'; import { module, test } from 'ember-qunit';
import schemas from 'npm:kosmos-schemas'; import schemas from 'npm:kosmos-schemas';
import tv4 from 'npm:tv4'; import tv4 from 'npm:tv4';
import { ContributorSerializer } from 'kredits-web/lib/kredits'; import ContributorSerializer from 'kredits-web/lib/kredits/serializers/contributor';
module('Serializers contributor'); module('Serializers contributor');
-12
View File
@@ -1,12 +0,0 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('service:ipfs', 'Unit | Service | ipfs', {
// Specify the other units that are required for this test.
// needs: ['service:foo']
});
// Replace this with your real tests.
test('it exists', function(assert) {
let service = this.subject();
assert.ok(service);
});