diff --git a/src/modules/orbitdb/indexes/abstract.js b/src/modules/orbitdb/indexes/abstract.js index cb09fc3..bfc0ad4 100644 --- a/src/modules/orbitdb/indexes/abstract.js +++ b/src/modules/orbitdb/indexes/abstract.js @@ -164,6 +164,10 @@ class ChluAbstractIndex { return await this._getReviewsWrittenByDID(didId) } + async getReputationScore(didId) { + return await this._getReputationScore(didId) + } + async _getReviewsAboutDID() { notImplemented(); } @@ -172,6 +176,10 @@ class ChluAbstractIndex { notImplemented(); } + async _getReputationScore() { + notImplemented() + } + } function notImplemented() { diff --git a/src/modules/orbitdb/indexes/inmemory.js b/src/modules/orbitdb/indexes/inmemory.js index 3f9db04..ea26911 100644 --- a/src/modules/orbitdb/indexes/inmemory.js +++ b/src/modules/orbitdb/indexes/inmemory.js @@ -133,6 +133,11 @@ class ChluInMemoryIndex extends ChluAbstractIndex { .map(multihash => ({ multihash })) } + _getReputationScore() { + // The InMemory Index cannot do this. + return null + } + } function slice(array, offset, limit) { diff --git a/src/modules/orbitdb/indexes/sql.js b/src/modules/orbitdb/indexes/sql.js index 700d68b..a819fe5 100644 --- a/src/modules/orbitdb/indexes/sql.js +++ b/src/modules/orbitdb/indexes/sql.js @@ -289,6 +289,35 @@ class ChluSQLIndex extends ChluAbstractIndex { return formatReviewRecords(list) } + async _getReputationScore(didId) { + const result = await this.ReviewRecord.findAll({ + attributes: [ + [this.sequelize.fn('AVERAGE', this.sequelize.col('data.rating_details.value')), 'rating_average'], + [this.sequelize.fn('COUNT'), 'rating_count'], + ], + where: { + // Only take out reviews about the didId + [Sequelize.Op.or]: [ + { 'data.popr.vendor_did': didId }, + { 'data.subject.did': didId }, + ], + // that also have a rating between 1 and 5 included + 'data.rating_details.min': 1, + 'data.rating_details.max': 5, + 'data.rating_details.value': { + [Sequelize.Op.gte]: 1, + [Sequelize.Op.lte]: 5 + }, + // don't consider records not about the latest version of a review + 'latestVersionData': null + } + }) + return { + rating_average: get(result[0], 'rating_average', null), + rating_count: get(result[0], 'rating_count', 0), + } + } + } function formatReviewRecords(list) { diff --git a/tests/OrbitDB.test.js b/tests/OrbitDB.test.js index e2bfeb1..34c56e7 100644 --- a/tests/OrbitDB.test.js +++ b/tests/OrbitDB.test.js @@ -12,6 +12,7 @@ const ChluInMemoryIndex = require('../src/modules/orbitdb/indexes/inmemory'); const ChluAbstractIndex = require('../src/modules/orbitdb/indexes/abstract'); const { genMultihash } = require('./utils/ipfs'); const { getFakeReviewRecord, makeResolved } = require('./utils/protobuf'); +const { cloneDeep } = require('lodash') async function applyOperation(idx, op) { return await idx.updateIndex({ @@ -40,7 +41,7 @@ describe('OrbitDB Module', () => { directory }); chluIpfs.reviewRecords.readReviewRecord = sinon.stub() - .callsFake(async multihash => Object.assign({ multihash }, makeResolved(await getFakeReviewRecord()), reviewOverride || {})) + .callsFake(async multihash => Object.assign({ multihash }, cloneDeep(makeResolved(await getFakeReviewRecord()), reviewOverride || {}))) chluIpfs.didIpfsHelper.readPublicDIDDocument = sinon.stub() .callsFake(async () => Object.assign({ id: 'did:chlu:random' @@ -207,12 +208,10 @@ describe('OrbitDB Module', () => { reviewOverride = { popr: { vendor_did: didId } } await applyOperation(idx, { op: ChluInMemoryIndex.operations.ADD_REVIEW_RECORD, - didId, // Retrocompatibility multihash: genMultihash(1) }) await applyOperation(idx, { op: ChluInMemoryIndex.operations.ADD_REVIEW_RECORD, - subjectDidId: didId, multihash: genMultihash(2) }) expect((await idx.getReviewsAboutDID(didId)).map(x => x.multihash)).to.deep.equal([ @@ -222,6 +221,37 @@ describe('OrbitDB Module', () => { expect(await idx.getReviewsWrittenByDID(didId)).to.deep.equal([]) }) + function getRating(x) { return { min: 1, max: 5, value: x } } + const skipForInMemory = item.name === 'InMemory' ? it.skip : it.only + + skipForInMemory('computes reputation score about subject DID', async () => { + const didId = 'did:chlu:abc' + reviewOverride = { popr: { vendor_did: didId }, rating_details: getRating(1) } + await applyOperation(idx, { + op: ChluInMemoryIndex.operations.ADD_REVIEW_RECORD, + subjectDidId: didId, + multihash: genMultihash(1) + }) + reviewOverride = { popr: { vendor_did: didId }, rating_details: getRating(1) } + await applyOperation(idx, { + op: ChluInMemoryIndex.operations.ADD_REVIEW_RECORD, + subjectDidId: didId, + multihash: genMultihash(2) + }) + reviewOverride = { subject: { did: didId }, previous_version_multihash: genMultihash(2), history: [ + { multihash: genMultihash(2) } + ], rating_details: getRating(5) } + await applyOperation(idx, { + op: ChluInMemoryIndex.operations.ADD_REVIEW_RECORD, + subjectDidId: didId, + multihash: genMultihash(3) + }) + expect(await idx.getReputationScore(didId)).to.deep.equal({ + average_score: 3, + count: 2 + }) + }) + it('returns reviews written by author DID', async () => { const didId = 'did:chlu:abc' reviewOverride = { customer_signature: { creator: didId } }