Merge branch 'master' into fix/ipfs-config

# Conflicts:
#	lib/utils/ipfs.js
#	lib/utils/preflight.js
This commit is contained in:
2019-04-24 20:33:15 +02:00
39 changed files with 1943 additions and 203 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,11 +2,15 @@ const Base = require('./base');
const EthersUtils = require('ethers').utils;
class Acl extends Base {
hasPermission(fromAddress, contractAddress, roleID, params = null) {
hasPermission (fromAddress, contractAddress, roleID, params = null) {
let roleHash = EthersUtils.keccak256(EthersUtils.toUtf8Bytes(roleID));
console.log(roleHash)
return this.functions.hasPermission(fromAddress, contractAddress, roleHash, params);
return this.functions.hasPermission(
fromAddress,
contractAddress,
roleHash,
params
);
}
}

View File

@@ -1,22 +1,22 @@
class Base {
constructor(contract) {
constructor (contract) {
this.contract = contract;
}
get functions() {
get functions () {
return this.contract.functions;
}
get ipfs() {
get ipfs () {
if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); }
return this._ipfsAPI;
}
set ipfs(ipfsAPI) {
set ipfs (ipfsAPI) {
this._ipfsAPI = ipfsAPI;
}
on(type, callback) {
on (type, callback) {
return this.contract.on(type, callback);
}
}

View File

@@ -6,20 +6,19 @@ class Contribution extends Record {
return this.functions.contributionsCount();
}
getById(id) {
getById (id) {
return this.functions.getContribution(id)
.then(data => {
return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
});
}
getByContributorId(contributorId) {
getByContributorId (contributorId) {
return this.functions.getContributorAddressById(contributorId)
.then(address => this.getByContributorAddress(address));
}
getByContributorAddress(address) {
getByContributorAddress (address) {
return this.functions.balanceOf(address)
.then(async (balance) => {
const count = balance.toNumber();
@@ -35,7 +34,7 @@ class Contribution extends Record {
});
}
async addContribution(contributionAttr, callOptions = {}) {
async addContribution (contributionAttr, callOptions = {}) {
const contribution = new ContributionSerializer(contributionAttr);
try { await contribution.validate(); }

View File

@@ -1,27 +1,29 @@
const Record = require('./record');
const ContributorSerializer = require('../serializers/contributor');
const formatKredits = require('../utils/format-kredits');
class Contributor extends Record {
get count () {
return this.functions.contributorsCount();
}
getById(id) {
getById (id) {
return this.functions.getContributorById(id)
.then((data) => {
.then(data => {
data.balanceInt = formatKredits(data.balance);
return this.ipfs.catAndMerge(data, ContributorSerializer.deserialize);
});
}
filterByAccount(search) {
filterByAccount (search) {
return this._byAccount(search, 'filter');
}
findByAccount(search) {
findByAccount (search) {
return this._byAccount(search, 'find');
}
_byAccount(search, method = 'filter') {
_byAccount (search, method = 'filter') {
return this.all().then((contributors) => {
const searchEntries = Object.entries(search);
@@ -37,12 +39,16 @@ class Contributor extends Record {
});
}
add(contributorAttr, callOptions = {}) {
let json = ContributorSerializer.serialize(contributorAttr);
// TODO: validate against schema
async add (contributorAttr, callOptions = {}) {
let contributor = new ContributorSerializer(contributorAttr);
try { await contributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = contributor.serialize();
return this.ipfs
.add(json)
.add(jsonStr)
.then((ipfsHashAttr) => {
let contributor = [
contributorAttr.account,
@@ -54,6 +60,30 @@ class Contributor extends Record {
return this.functions.addContributor(...contributor, callOptions);
});
}
updateProfile (contributorId, updateAttr, callOptions = {}) {
return this.getById(contributorId).then(async (contributor) => {
let updatedContributorAttr = Object.assign(contributor, updateAttr);
let updatedContributor = new ContributorSerializer(updatedContributorAttr);
try { await updatedContributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = updatedContributor.serialize();
return this.ipfs
.add(jsonStr)
.then(ipfsHashAttr => {
return this.functions.updateContributorProfileHash(
contributorId,
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
callOptions
);
});
});
}
}
module.exports = Contributor;

View File

@@ -4,5 +4,5 @@ module.exports = {
Proposal: require('./proposal'),
Token: require('./token'),
Kernel: require('./kernel'),
Acl: require('./acl')
Acl: require('./acl'),
};

View File

@@ -4,19 +4,19 @@ const Base = require('./base');
const KERNEL_APP_ADDR_NAMESPACE = '0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb';
class Kernel extends Base {
constructor(contract) {
constructor (contract) {
super(contract);
this.apm = 'aragonpm.eth'; // can be overwritten if needed
}
getApp(appName) {
getApp (appName) {
if (appName === 'Acl') {
return this.functions.acl();
}
return this.functions.getApp(KERNEL_APP_ADDR_NAMESPACE, this.appNamehash(appName));
}
appNamehash(appName) {
appNamehash (appName) {
return namehash(`kredits-${appName.toLowerCase()}.${this.apm}`);
}
}

View File

@@ -6,14 +6,14 @@ class Proposal extends Record {
return this.functions.proposalsCount();
}
getById(id) {
getById (id) {
return this.functions.getProposal(id)
.then(data => {
return this.ipfs.catAndMerge(data, ContributionSerializer.deserialize);
});
}
async addProposal(proposalAttr, callOptions = {}) {
async addProposal (proposalAttr, callOptions = {}) {
const contribution = new ContributionSerializer(proposalAttr);
try { await contribution.validate(); }
@@ -37,4 +37,4 @@ class Proposal extends Record {
}
}
module.exports = Proposal
module.exports = Proposal;

View File

@@ -2,7 +2,7 @@ const Base = require('./base');
const paged = require('../utils/pagination');
class Record extends Base {
all(options = {}) {
all (options = {}) {
return this.count
.then((count) => {
let records = paged(count, options).map((id) => this.getById(id));
@@ -11,4 +11,4 @@ class Record extends Base {
}
}
module.exports = Record
module.exports = Record;

View File

@@ -1,6 +1,7 @@
const ethers = require('ethers');
const Preflight = require('./utils/preflight');
const deprecate = require('./utils/deprecate');
const ABIS = {
Contributor: require('./abis/Contributor.json'),
@@ -8,29 +9,29 @@ const ABIS = {
Token: require('./abis/Token.json'),
Proposal: require('./abis/Proposal.json'),
Kernel: require('./abis/Kernel.json'),
Acl: require('./abis/ACL.json')
Acl: require('./abis/ACL.json'),
};
const APP_CONTRACTS = [
'Contributor',
'Contribution',
'Token',
'Proposal',
'Acl'
'Acl',
];
const DaoAddresses = require('./addresses/dao.json');
const Contracts = require('./contracts');
const IPFS = require('./utils/ipfs')
const IPFS = require('./utils/ipfs');
// Helpers
function capitalize(word) {
function capitalize (word) {
let [first, ...rest] = word;
return `${first.toUpperCase()}${rest.join('')}`;
}
class Kredits {
constructor(provider, signer, options = {}) {
constructor (provider, signer, options = {}) {
let { addresses, abis, ipfsConfig } = options;
this.provider = provider;
@@ -43,7 +44,7 @@ class Kredits {
this.networkId = null;
}
init(names) {
init (names) {
let contractsToLoad = names || APP_CONTRACTS;
return this.provider.getNetwork().then(network => {
this.networkId = network.chainId.toString();
@@ -53,23 +54,22 @@ class Kredits {
return this.Kernel.getApp(contractName).then((address) => {
this.addresses[contractName] = address;
}).catch((error) => {
console.log(error);
throw new Error(`Failed to get address for ${contractName} from DAO at ${this.Kernel.contract.address}
- ${error.message}`
);
});
});
return Promise.all(addressPromises).then(() => { return this });
return Promise.all(addressPromises).then(() => { return this; });
});
}
static setup(provider, signer, ipfsConfig = null) {
console.log('Kredits.setup() is deprecated use new Kredits().init() instead');
static setup (provider, signer, ipfsConfig = null) {
deprecate('Kredits.setup() is deprecated use new Kredits().init() instead');
return new Kredits(provider, signer, { ipfsConfig: ipfsConfig }).init();
}
get Kernel() {
get Kernel () {
let k = this.contractFor('Kernel');
// in case we want to use a special apm (e.g. development vs. production)
if (this.options.apm) {
@@ -78,37 +78,37 @@ class Kredits {
return k;
}
get Contributor() {
get Contributor () {
return this.contractFor('Contributor');
}
get Contributors() {
console.log('Contributors is deprecated use Contributor instead');
get Contributors () {
deprecate('Contributors is deprecated use Contributor instead');
return this.Contributor;
}
get Proposal() {
get Proposal () {
return this.contractFor('Proposal');
}
get Operator() {
get Operator () {
return this.Proposal;
}
get Token() {
get Token () {
return this.contractFor('Token');
}
get Contribution() {
get Contribution () {
return this.contractFor('Contribution');
}
get Acl() {
get Acl () {
return this.contractFor('Acl');
}
// Should be private
contractFor(name) {
contractFor (name) {
if (this.contracts[name]) {
return this.contracts[name];
}
@@ -128,7 +128,7 @@ class Kredits {
return this.contracts[name];
}
preflightChecks() {
preflightChecks () {
return new Preflight(this).check();
}
}

View File

@@ -9,7 +9,7 @@ const validator = require('../utils/validator');
*/
class Contribution {
constructor(attrs) {
constructor (attrs) {
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
}
@@ -26,24 +26,24 @@ class Contribution {
kind,
description,
url,
details
details,
} = this;
let data = {
"@context": "https://schema.kosmos.org",
"@type": "Contribution",
"contributor": {
"ipfs": contributorIpfsHash
'@context': 'https://schema.kosmos.org',
'@type': 'Contribution',
'contributor': {
'ipfs': contributorIpfsHash,
},
date,
time,
kind,
description,
"details": details || {}
'details': details || {},
};
if (url) {
data["url"] = url;
data['url'] = url;
}
// Write it pretty to ipfs

View File

@@ -1,3 +1,5 @@
const schemas = require('kosmos-schemas');
const validator = require('../utils/validator');
/**
* Handle serialization for JSON-LD object of the contributor, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
@@ -6,13 +8,87 @@
* @public
*/
class Contributor {
constructor (attrs) {
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
}
/**
* Serialize object to JSON
*
* @method
* @public
*/
serialize () {
let {
name,
kind,
url,
github_uid,
github_username,
gitea_username,
wiki_username,
} = this;
let data = {
'@context': 'https://schema.kosmos.org',
'@type': 'Contributor',
kind,
name,
'accounts': [],
};
if (url) {
data['url'] = url;
}
if (github_uid) {
data.accounts.push({
'site': 'github.com',
'uid': github_uid,
'username': github_username,
'url': `https://github.com/${github_username}`,
});
}
if (gitea_username) {
data.accounts.push({
'site': 'gitea.kosmos.org',
'username': gitea_username,
'url': `https://gitea.kosmos.org/${gitea_username}`,
});
}
if (wiki_username) {
data.accounts.push({
'site': 'wiki.kosmos.org',
'username': wiki_username,
'url': `https://wiki.kosmos.org/User:${wiki_username}`,
});
}
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
/**
* Validate serialized data against schema
*
* @public
*/
validate () {
const serialized = JSON.parse(this.serialize());
const valid = validator.validate(serialized, schemas['contributor']);
return valid ? Promise.resolve() : Promise.reject(validator.error);
}
/**
* Deserialize JSON to object
*
* @method
* @public
*/
static deserialize(serialized) {
static deserialize (serialized) {
let {
name,
kind,
@@ -20,13 +96,17 @@ class Contributor {
accounts,
} = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, wiki_username;
let github = accounts.find((a) => a.site === 'github.com');
let wiki = accounts.find((a) => a.site === 'wiki.kosmos.org');
let github_username, github_uid, gitea_username, wiki_username;
let github = accounts.find(a => a.site === 'github.com');
let gitea = accounts.find(a => a.site === 'gitea.kosmos.org');
let wiki = accounts.find(a => a.site === 'wiki.kosmos.org');
if (github) {
(({ username: github_username, uid: github_uid} = github));
}
if (gitea) {
(({ username: gitea_username } = gitea));
}
if (wiki) {
(({ username: wiki_username } = wiki));
}
@@ -38,59 +118,12 @@ class Contributor {
accounts,
github_uid,
github_username,
gitea_username,
wiki_username,
ipfsData: serialized,
};
}
/**
* Serialize object to JSON
*
* @method
* @public
*/
static serialize(deserialized) {
let {
name,
kind,
url,
github_uid,
github_username,
wiki_username,
} = deserialized;
let data = {
"@context": "https://schema.kosmos.org",
"@type": "Contributor",
kind,
name,
"accounts": []
};
if (url) {
data["url"] = url;
}
if (github_uid) {
data.accounts.push({
"site": "github.com",
"uid": github_uid,
"username": github_username,
"url": `https://github.com/${github_username}`
});
}
if (wiki_username) {
data.accounts.push({
"site": "wiki.kosmos.org",
"username": wiki_username,
"url": `https://wiki.kosmos.org/User:${wiki_username}`
});
}
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
}
module.exports = Contributor;

5
lib/utils/deprecate.js Normal file
View File

@@ -0,0 +1,5 @@
/*eslint no-console: ["error", { allow: ["warn"] }] */
module.exports = function deprecate (msg) {
console.warn(msg);
};

View File

@@ -0,0 +1,10 @@
const ethersUtils = require('ethers').utils;
module.exports = function(value, options = {}) {
let etherValue = ethersUtils.formatEther(value);
if (options.asFloat) {
return parseFloat(etherValue);
} else {
return parseInt(etherValue);
}
};

View File

@@ -2,8 +2,7 @@ const ipfsClient = require('ipfs-http-client');
const multihashes = require('multihashes');
class IPFS {
constructor(config) {
constructor (config) {
if (!config) {
config = { host: 'localhost', port: '5001', protocol: 'http' };
}
@@ -11,15 +10,15 @@ class IPFS {
this._config = config;
}
get config() {
get config () {
return this._config;
}
get peerId() {
get peerId () {
return this._ipfsAPI.id();
}
catAndMerge(data, deserialize) {
catAndMerge (data, deserialize) {
// if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) {
return data;
@@ -34,7 +33,7 @@ class IPFS {
});
}
add(data) {
add (data) {
return this._ipfsAPI
.add(ipfsClient.Buffer.from(data))
.then((res) => {
@@ -42,7 +41,7 @@ class IPFS {
});
}
cat(hashData) {
cat (hashData) {
let ipfsHash = hashData; // default - if it is a string
if (hashData.hasOwnProperty('hashSize')) {
ipfsHash = this.encodeHash(hashData);
@@ -50,21 +49,20 @@ class IPFS {
return this._ipfsAPI.cat(ipfsHash);
}
decodeHash(ipfsHash) {
decodeHash (ipfsHash) {
let multihash = multihashes.decode(multihashes.fromB58String(ipfsHash));
return {
hashDigest: '0x' + multihashes.toHexString(multihash.digest),
hashSize: multihash.length,
hashFunction: multihash.code,
ipfsHash: ipfsHash
ipfsHash: ipfsHash,
};
}
encodeHash(hashData) {
encodeHash (hashData) {
let digest = ipfsClient.Buffer.from(hashData.hashDigest.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
}
}
module.exports = IPFS;

View File

@@ -1,4 +1,4 @@
function pageNumber(number, size, recordCount) {
function pageNumber (number, size, recordCount) {
let numberOfPages = Math.ceil(recordCount / size);
number = parseInt(number) || 1;
@@ -10,7 +10,7 @@ function pageNumber(number, size, recordCount) {
return number;
}
function buildIds(order, number, size, recordCount) {
function buildIds (order, number, size, recordCount) {
let offset = size * (number - 1);
let start;
@@ -34,7 +34,7 @@ function buildIds(order, number, size, recordCount) {
return Array.from({ length: size }, mapFunction);
}
module.exports = function paged(recordCount, options = {}) {
module.exports = function paged (recordCount, options = {}) {
let { order, page } = options;
order = order || 'desc';
page = page || {};

View File

@@ -1,9 +1,9 @@
class Preflight {
constructor(kredits) {
constructor (kredits) {
this.kredits = kredits;
}
check() {
check () {
return this.kredits.ipfs.peerId()
.catch((error) => {
const ipfsConfig = JSON.stringify(this.kredits.ipfs.config);

View File

@@ -4,12 +4,12 @@ const validator = tv4.freshApi();
validator.addFormat({
'date': function(value) {
const dateRegexp = /^[0-9]{4,}-[0-9]{2}-[0-9]{2}$/;
return dateRegexp.test(value) ? null : "A valid ISO 8601 full-date string is expected";
return dateRegexp.test(value) ? null : 'A valid ISO 8601 full-date string is expected';
},
'time': function(value) {
const timeRegexp = /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
return timeRegexp.test(value) ? null : "A valid ISO 8601 full-time string is expected";
}
})
const timeRegexp = /^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([+|-]([01][0-9]|2[0-3]):[0-5][0-9]))$/;
return timeRegexp.test(value) ? null : 'A valid ISO 8601 full-time string is expected';
},
});
module.exports = validator;