From b21d2ad864ff21a4e40d8a2689ff78f3534e1818 Mon Sep 17 00:00:00 2001 From: Manuel Wiedenmann Date: Fri, 6 Apr 2018 18:35:21 +0200 Subject: [PATCH] Extract serializer logic into lib To use the same code in hubot I extracted the serializer logic into lib to later move it into `truffle-kredits` when we are done with all other common logic. --- app/lib/kredits/index.js | 5 + app/lib/kredits/serializers/contributor.js | 93 +++++++++++ app/models/contributor.js | 106 +------------ app/services/kredits.js | 147 ++++++++++++------ .../kredits/serializers/contributor-test.js | 53 +++++++ tests/unit/models/contributor-test.js | 16 -- 6 files changed, 255 insertions(+), 165 deletions(-) create mode 100644 app/lib/kredits/index.js create mode 100644 app/lib/kredits/serializers/contributor.js create mode 100644 tests/unit/lib/kredits/serializers/contributor-test.js diff --git a/app/lib/kredits/index.js b/app/lib/kredits/index.js new file mode 100644 index 0000000..0960559 --- /dev/null +++ b/app/lib/kredits/index.js @@ -0,0 +1,5 @@ +import ContributorSerializer from './serializers/contributor'; + +export { + ContributorSerializer +}; diff --git a/app/lib/kredits/serializers/contributor.js b/app/lib/kredits/serializers/contributor.js new file mode 100644 index 0000000..80df182 --- /dev/null +++ b/app/lib/kredits/serializers/contributor.js @@ -0,0 +1,93 @@ +/** + * Handle serialization for JSON-LD object of the contributor, according to + * https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json + * + * @class + * @public + */ +export default class Contributor { + /** + * Deserialize JSON to object + * + * @method + * @public + */ + static deserialize(serialized) { + let { + name, + kind, + url, + accounts, + } = JSON.parse(serialized); + + 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'); + + if (github) { + ({ username: github_username, uid: github_uid} = github); + } + if (wiki) { + ({ username: wiki_username } = wiki); + } + + return { + name, + kind, + url, + github_uid, + github_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); + } +} diff --git a/app/models/contributor.js b/app/models/contributor.js index de16bfb..2303a1a 100644 --- a/app/models/contributor.js +++ b/app/models/contributor.js @@ -1,14 +1,7 @@ -import Ember from 'ember'; import computed from 'ember-computed'; -import injectService from 'ember-service/inject'; - -const { - isPresent, -} = Ember; - -export default Ember.Object.extend({ - ipfs: injectService(), +import EmberObject from 'ember-object'; +export default EmberObject.extend({ id: null, address: null, name: null, @@ -28,99 +21,4 @@ export default Ember.Object.extend({ return `https://avatars2.githubusercontent.com/u/${github_uid}?v=3&s=128`; } }), - - /** - * Loads the contributor's profile data from IPFS and sets local instance - * properties from it - * - * @method - * @public - */ - loadProfile() { - let profileHash = this.get('profileHash'); - if (!profileHash) { - return; - } - - return this.get('ipfs') - .getFile(profileHash) - .then((content) => { - let profile = Ember.Object.create(JSON.parse(content)); - this.set('ipfsData', JSON.stringify(profile, null, 2)); - - Ember.Logger.debug('[contributor] loaded contributor profile', profile); - - 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, - }); - } - if (isPresent(wiki)) { - this.setProperties({ - wiki_username: wiki.username - }); - } - }).catch((err) => { - Ember.Logger.error('[contributor] error trying to load contributor profile', profileHash, err); - }); - }, - - /** - * 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()); - } - }); diff --git a/app/services/kredits.js b/app/services/kredits.js index 3af0ecd..530b0d6 100644 --- a/app/services/kredits.js +++ b/app/services/kredits.js @@ -14,12 +14,14 @@ import Proposal from 'kredits-web/models/proposal'; import abis from 'contracts/abis'; import addresses from 'contracts/addresses'; +import { ContributorSerializer } from 'kredits-web/lib/kredits'; const { getOwner, Logger: { debug, - warn + warn, + error } } = Ember; @@ -103,37 +105,55 @@ export default Service.extend({ }); }, - getContributorData(id) { + // TODO: Should be part of the service + buildContributor(attributes) { + debug('[kredits] buildContributor', attributes); + + let contributor = getOwner(this).lookup('model:contributor'); + contributor.setProperties(attributes); + return contributor; + }, + + getContributorById(id) { return this.get('contributorsContract') .then((contract) => contract.contributors(id)) - .then((data) => { - debug('[kredits] contributor', data); + // Set basic data + .then(({ + account: address, + hashFunction, + hashSize, + isCore, + profileHash: digest, + }) => { - let [ address, digest, hashFunction, size, isCore ] = data; let isCurrentUser = this.get('currentUserAccounts').includes(address); let profileHash = this.getMultihashFromBytes32({ digest, - hashFunction: hashFunction, - size: size + hashFunction, + size: hashSize }); - return this.get('tokenContract') - .then((contract) => contract.balanceOf(address)) - .then((balance) => { - balance = balance.toNumber(); - let contributor = getOwner(this).lookup('model:contributor'); - contributor.setProperties({ - id, - address, - profileHash, - isCore, - isCurrentUser, - balance - }); - // Load data from IPFS - contributor.loadProfile(); - return contributor; + return { + id, + address, + isCore, + isCurrentUser, + profileHash, + }; + }) + // Add the balance + .then((data) => { + return this.get('tokenContract') + .then((contract) => contract.balanceOf(data.address)) + .then((balance) => { + data.balance = balance.toNumber(); + return data; }); + }) + // Fetch IPFS data if available + .then(this.loadContributorProfile.bind(this)) + .then((attributes) => { + return this.buildContributor(attributes); }); }, @@ -145,13 +165,43 @@ export default Service.extend({ let contributors = []; for(var id = 1; id <= contributorsCount.toNumber(); id++) { - contributors.push(this.getContributorData(id)); + contributors.push(this.getContributorById(id)); } return RSVP.all(contributors); }); }, + + /** + * Loads the contributor's profile data from IPFS and returns the attributes + * + * @method + * @public + */ + loadContributorProfile(data) { + let profileHash = data.profileHash; + + if (!profileHash) { + return data; + } + + return this.get('ipfs') + .getFile(profileHash) + .then(ContributorSerializer.deserialize) + .then((attributes) => { + debug('[kredits] loaded contributor profile', attributes); + return Object.assign(data, attributes); + }) + .catch((err) => { + error( + '[kredits] error trying to load contributor profile', + profileHash, + err + ); + }); + }, + getProposalData(i) { return this.get('kreditsContract') .then((contract) => contract.proposals(i)) @@ -234,36 +284,42 @@ export default Service.extend({ }; }, - addContributor(contributor) { - debug('[kredits] add contributor', contributor); + + // TODO: extract common logic to module + addContributor(attributes) { + debug('[kredits] add contributor', attributes); + + let json = ContributorSerializer.serialize(attributes); return this.get('ipfs') - .storeFile(contributor.serialize()) - .then(profileHash => { - contributor.setProperties({ - profileHash: profileHash, - balance: 0, - isCurrentUser: this.get('currentUserAccounts').includes(contributor.address) - }); - - let { - digest, hashFunction, size - } = this.getBytes32FromMultihash(profileHash); + .storeFile(json) + .then((profileHash) => { + // Set new attributes + attributes.profileHash = profileHash; + attributes.balance = 0; + attributes.isCurrentUser = this.get('currentUserAccounts') + .includes(attributes.address); return this.get('kreditsContract') .then((contract) => { + let { address, isCore } = attributes; + let { + digest, hashFunction, size + } = this.getBytes32FromMultihash(profileHash); + + debug('[kredits] addContributor', address, digest, hashFunction, size, isCore); return contract.addContributor( - contributor.address, + address, digest, hashFunction, size, - contributor.isCore + isCore ); - }) - .then((data) => { - debug('[kredits] add contributor response', data); - return contributor; }); + }) + .then((data) => { + debug('[kredits] add contributor response', data); + return this.buildContributor(attributes); }); }, @@ -301,11 +357,12 @@ export default Service.extend({ .then((contract) => { return contract.getContributorIdByAddress(this.get('currentUserAccounts.firstObject')) .then((id) => { + id = id.toNumber(); // check if the user is a contributor or not - if( id.toNumber() === 0) { + if (id === 0) { return RSVP.resolve(); } else { - return this.getContributorData(id.toNumber()); + return this.getContributorById(id); } }); }); diff --git a/tests/unit/lib/kredits/serializers/contributor-test.js b/tests/unit/lib/kredits/serializers/contributor-test.js new file mode 100644 index 0000000..9c08650 --- /dev/null +++ b/tests/unit/lib/kredits/serializers/contributor-test.js @@ -0,0 +1,53 @@ +import { module, test } from 'ember-qunit'; +import schemas from 'npm:kosmos-schemas'; +import tv4 from 'npm:tv4'; +import { ContributorSerializer } from 'kredits-web/lib/kredits'; + +module('Serializers contributor'); + +test('#serialize returns a valid JSON-LD representation', function(assert) { + let serialized = ContributorSerializer.serialize({ + name: 'Satoshi Nakamoto', + kind: 'person', + github_uid: 123, + github_username: 'therealsatoshi', + wiki_username: 'Satoshi', + }); + + let valid = tv4.validate(JSON.parse(serialized), schemas['contributor']); + assert.ok(valid); +}); + +test('#deserialize returns a valid object representation', function(assert) { + let json = JSON.stringify({ + "@context": "https://schema.kosmos.org", + "@type": "Contributor", + "kind": "person", + "name": "Satoshi Nakamoto", + "accounts": [ + { + "site": "github.com", + "uid": 123, + "username": "therealsatoshi", + "url": "https://github.com/therealsatoshi" + }, + { + "site": "wiki.kosmos.org", + "username": "Satoshi", + "url": "https://wiki.kosmos.org/User:Satoshi" + } + ] + }); + let deserialized = ContributorSerializer.deserialize(json); + + let expected = { + name: 'Satoshi Nakamoto', + kind: 'person', + github_uid: 123, + github_username: 'therealsatoshi', + wiki_username: 'Satoshi', + url: undefined, + ipfsData: json, + }; + assert.deepEqual(expected, deserialized); +}); diff --git a/tests/unit/models/contributor-test.js b/tests/unit/models/contributor-test.js index 39651f1..4261724 100644 --- a/tests/unit/models/contributor-test.js +++ b/tests/unit/models/contributor-test.js @@ -1,6 +1,4 @@ import { moduleFor, test } from 'ember-qunit'; -import schemas from 'npm:kosmos-schemas'; -import tv4 from 'npm:tv4'; moduleFor('model:contributor', 'Unit | Model | contributor'); @@ -10,17 +8,3 @@ test('#avatarURL() returns correct URL', function(assert) { 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'])); -});