From 223a3c9ff7a2fb8f25db6675da063d084787d32b Mon Sep 17 00:00:00 2001 From: pshenmic Date: Thu, 26 Oct 2023 22:46:48 +0700 Subject: [PATCH 1/3] Add get Identity by identifier API --- packages/api/index.js | 4 +- .../src/controllers/IdentitiesController.js | 21 +++++++ .../src/controllers/TransactionsController.js | 1 - packages/api/src/dao/IdentitiesDAO.js | 57 +++++++++++++++++++ packages/api/src/models/Identity.js | 19 +++++++ packages/api/src/routes.js | 7 ++- 6 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 packages/api/src/controllers/IdentitiesController.js create mode 100644 packages/api/src/dao/IdentitiesDAO.js create mode 100644 packages/api/src/models/Identity.js diff --git a/packages/api/index.js b/packages/api/index.js index 17bc3fb38..54fd62f16 100644 --- a/packages/api/index.js +++ b/packages/api/index.js @@ -10,6 +10,7 @@ const MainController = require("./src/controllers/MainController"); const TransactionsController = require("./src/controllers/TransactionsController"); const BlocksController = require("./src/controllers/BlocksController"); const DocumentsController = require("./src/controllers/DocumentsController"); +const IdentitiesController = require("./src/controllers/IdentitiesController"); const packageVersion = require('./package.json').version const Worker = require('./src/worker/index') const {BLOCK_TIME} = require("./src/constants"); @@ -74,8 +75,9 @@ const init = async () => { const transactionsController = new TransactionsController(client, knex) const dataContractsController = new DataContractsController(knex) const documentsController = new DocumentsController(knex) + const identitiesController = new IdentitiesController(knex) - Routes({fastify, mainController, blocksController, transactionsController, dataContractsController, documentsController}) + Routes({fastify, mainController, blocksController, transactionsController, dataContractsController, documentsController, identitiesController}) fastify.setErrorHandler(errorHandler) fastify.listen({ host: "0.0.0.0", port: 3005, listenTextResolver: (address) => console.log(`Platform indexer API has started on the ${address}`)}); diff --git a/packages/api/src/controllers/IdentitiesController.js b/packages/api/src/controllers/IdentitiesController.js new file mode 100644 index 000000000..96275dbc7 --- /dev/null +++ b/packages/api/src/controllers/IdentitiesController.js @@ -0,0 +1,21 @@ +const IdentitiesDAO = require("../dao/IdentitiesDAO"); + +class IdentitiesController { + constructor(knex) { + this.identitiesDAO = new IdentitiesDAO(knex) + } + + getIdentityByIdentifier = async (request, response) => { + const {identifier} = request.params; + + const identity = await this.identitiesDAO.getIdentityByIdentifier(identifier) + + if (!identity) { + return response.status(404).send({message: 'not found'}) + } + + response.send(identity) + } +} + +module.exports = IdentitiesController diff --git a/packages/api/src/controllers/TransactionsController.js b/packages/api/src/controllers/TransactionsController.js index 32e8e9c75..975a148f6 100644 --- a/packages/api/src/controllers/TransactionsController.js +++ b/packages/api/src/controllers/TransactionsController.js @@ -1,5 +1,4 @@ const cache = require("../cache"); -const Transaction = require("../models/Transaction"); const TransactionsDAO = require("../dao/TransactionsDAO"); class TransactionsController { diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js new file mode 100644 index 000000000..64baabb76 --- /dev/null +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -0,0 +1,57 @@ +const Identity = require("../models/Identity"); + +module.exports = class IdentitiesDAO { + constructor(knex) { + this.knex = knex; + } + + getIdentityByIdentifier = async (identifier) => { + const subquery = this.knex('identities') + .select('identities.id','identities.identifier as identifier', 'identities.state_transition_hash as st_hash', + 'identities.revision as revision', 'identities.state_transition_hash as state_transition_hash') + .select(this.knex.raw('rank() over (partition by identities.identifier order by identities.id desc) rank')) + .where('identities.identifier', '=', identifier) + .as('all_identities') + + const lastRevisionIdentities = this.knex(subquery) + .select('identifier', 'revision', 'st_hash', + 'transfers.id as transfer_id', 'transfers.sender as sender', + 'transfers.recipient as recipient', 'transfers.amount as amount') + .where('rank', 1) + .leftJoin('transfers', 'transfers.recipient', 'identifier') + + + const documentSubQuery = this.knex('documents') + .select('documents.id as id', 'documents.state_transition_hash as st_hash') + .join('state_transitions', 'st_hash', 'state_transitions.hash') + .where('state_transitions.owner', identifier) + + const dataContractsSubQuery = this.knex('data_contracts') + .select('data_contracts.id as id', 'data_contracts.state_transition_hash as st_hash') + .join('state_transitions', 'st_hash', 'state_transitions.hash') + .where('state_transitions.owner', identifier) + + const rows = await this.knex.with('with_alias', lastRevisionIdentities) + .select('identifier', 'revision', + 'transfer_id', 'sender', 'st_hash', 'blocks.timestamp as timestamp', + 'recipient', 'amount') + .join('state_transitions', 'state_transitions.hash', 'st_hash') + .join('blocks', 'state_transitions.block_hash', 'blocks.hash') + .select(this.knex('with_alias').sum('amount').where('recipient', identifier).as('total_received')) + .select(this.knex('with_alias').sum('amount').where('sender', identifier).as('total_sent')) + .select(this.knex('state_transitions').count('*').where('owner', identifier).as('total_txs')) + .select(this.knex.with('with_documents', documentSubQuery).count('*').where('owner', identifier).as('total_documents')) + .select(this.knex.with('with_data_contracts', dataContractsSubQuery).count('*').where('owner', identifier).as('total_data_contracts')) + .from('with_alias') + + if (!rows.length) { + return null + } + + const [row] = rows + + const balance = Number(row.total_received ?? 0) - Number(row.total_sent ?? 0) + + return Identity.fromRow({...row, balance}) + } +} diff --git a/packages/api/src/models/Identity.js b/packages/api/src/models/Identity.js new file mode 100644 index 000000000..657be8c50 --- /dev/null +++ b/packages/api/src/models/Identity.js @@ -0,0 +1,19 @@ +module.exports = class Identity { + identifier + revision + balance + timestamp + totalTxs + + constructor(identifier, revision, balance, timestamp, totalTxs) { + this.identifier = identifier; + this.revision = revision; + this.balance = balance; + this.timestamp = timestamp; + this.totalTxs = totalTxs; + } + + static fromRow({identifier, revision, balance, timestamp, total_txs}) { + return new Identity(identifier, revision, balance, timestamp, total_txs) + } +} diff --git a/packages/api/src/routes.js b/packages/api/src/routes.js index 8a10afe25..d62c35774 100644 --- a/packages/api/src/routes.js +++ b/packages/api/src/routes.js @@ -5,7 +5,7 @@ * @param blockController {BlocksController} * @param transactionsController {TransactionsController} */ -module.exports = ({fastify, mainController, blocksController, transactionsController, dataContractsController, documentsController}) => { +module.exports = ({fastify, mainController, blocksController, transactionsController, dataContractsController, documentsController, identitiesController}) => { const routes = [ { path: '/status', @@ -52,6 +52,11 @@ module.exports = ({fastify, mainController, blocksController, transactionsContro method: 'GET', handler: documentsController.getDocumentByIdentifier }, + { + path: '/identities/:identifier', + method: 'GET', + handler: identitiesController.getIdentityByIdentifier + }, { path: '/search', method: 'GET', From 971e1d83071ece6b0924d265be0d9cc0fb6e118c Mon Sep 17 00:00:00 2001 From: pshenmic Date: Fri, 27 Oct 2023 21:00:31 +0700 Subject: [PATCH 2/3] Add get Identity by identifier route path --- .../src/controllers/DocumentsController.js | 2 +- packages/api/src/dao/DataContractsDAO.js | 17 ++++++---- packages/api/src/dao/DocumentsDAO.js | 31 ++++++++++------- packages/api/src/dao/IdentitiesDAO.js | 34 ++++++++++++------- packages/api/src/models/DataContract.js | 9 +++-- packages/api/src/models/Document.js | 13 ++++--- packages/api/src/models/Identity.js | 11 ++++-- 7 files changed, 75 insertions(+), 42 deletions(-) diff --git a/packages/api/src/controllers/DocumentsController.js b/packages/api/src/controllers/DocumentsController.js index b481de6e2..038bf1be8 100644 --- a/packages/api/src/controllers/DocumentsController.js +++ b/packages/api/src/controllers/DocumentsController.js @@ -15,7 +15,7 @@ class DocumentsController { response.status(404).send({message: 'not found'}) } - response.send(Document.fromRow(document)); + response.send(document); } getDocumentsByDataContract = async (request, response) => { diff --git a/packages/api/src/dao/DataContractsDAO.js b/packages/api/src/dao/DataContractsDAO.js index f9f9ce144..696a3d961 100644 --- a/packages/api/src/dao/DataContractsDAO.js +++ b/packages/api/src/dao/DataContractsDAO.js @@ -12,11 +12,11 @@ module.exports = class DataContractsDAO { const subquery = this.knex('data_contracts') .select('data_contracts.id as id', 'data_contracts.identifier as identifier', - 'data_contracts.version as version') + 'data_contracts.version as version', 'data_contracts.state_transition_hash as tx_hash') .select(this.knex.raw(`rank() over (partition by identifier order by version desc) rank`)) const filteredContracts = this.knex.with('with_alias', subquery) - .select( 'id', 'identifier', 'version', 'rank', + .select( 'id', 'identifier', 'version', 'tx_hash', 'rank', this.knex('with_alias').count('*').as('total_count').where('rank', '1')) .select(this.knex.raw(`rank() over (order by id ${order}) row_number`)) .from('with_alias') @@ -25,9 +25,11 @@ module.exports = class DataContractsDAO { .as('filtered_data_contracts') const rows = await this.knex(filteredContracts) - .select('total_count', 'id', 'identifier', 'version', 'row_number') + .select('total_count', 'identifier', 'version', 'row_number', 'filtered_data_contracts.tx_hash', + 'blocks.timestamp as timestamp', 'blocks.hash as block_hash') + .join('state_transitions', 'state_transitions.hash', 'filtered_data_contracts.tx_hash') + .join('blocks', 'blocks.hash', 'state_transitions.block_hash') .whereBetween('row_number', [fromRank, toRank]) - .orderBy('id', order); const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0; @@ -38,9 +40,12 @@ module.exports = class DataContractsDAO { getDataContractByIdentifier = async (identifier) => { const rows = await this.knex('data_contracts') - .select('data_contracts.identifier as identifier', 'data_contracts.schema as schema', 'data_contracts.version as version') + .select('data_contracts.identifier as identifier', 'data_contracts.schema as schema', + 'data_contracts.version as version', 'state_transitions.hash as tx_hash', 'blocks.timestamp as timestamp') + .join('state_transitions', 'data_contracts.state_transition_hash', 'state_transitions.hash') + .join('blocks', 'blocks.hash', 'state_transitions.block_hash') .where('data_contracts.identifier', identifier) - .orderBy('id', 'desc') + .orderBy('data_contracts.id', 'desc') .limit(1); const [row] = rows diff --git a/packages/api/src/dao/DocumentsDAO.js b/packages/api/src/dao/DocumentsDAO.js index f875285c4..6fd51c6e7 100644 --- a/packages/api/src/dao/DocumentsDAO.js +++ b/packages/api/src/dao/DocumentsDAO.js @@ -8,9 +8,9 @@ module.exports = class DocumentsDAO { getDocumentByIdentifier = async (identifier) => { const subquery = this.knex('documents') - .select('documents.id as id', 'documents.identifier as identifier', - 'data_contracts.identifier as data_contract_identifier', 'documents.data as data', - 'documents.revision as revision', 'documents.state_transition_hash as state_transition_hash', + .select('documents.id', 'documents.identifier as identifier', + 'data_contracts.identifier as data_contract_identifier', 'documents.data as data_contract_data', + 'documents.revision as revision', 'documents.state_transition_hash as tx_hash', 'documents.deleted as deleted') .select(this.knex.raw('rank() over (partition by documents.identifier order by documents.id desc) rank')) .leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id') @@ -18,8 +18,10 @@ module.exports = class DocumentsDAO { .as('documents') const rows = await this.knex(subquery) - .select('id', 'identifier', 'data_contract_identifier', 'data', - 'revision', 'deleted', 'rank', 'state_transition_hash') + .select('identifier', 'data_contract_identifier', 'data_contract_data', + 'revision', 'deleted', 'rank', 'tx_hash', 'blocks.timestamp as timestamp') + .join('state_transitions', 'state_transitions.hash', 'tx_hash') + .join('blocks', 'blocks.hash', 'state_transitions.block_hash') .limit(1); const [row] = rows @@ -28,7 +30,10 @@ module.exports = class DocumentsDAO { return null } - return Document.fromRow(row); + return Document.fromRow({ + ...row, + data: row.data_contract_data + }); } getDocumentsByDataContract = async (identifier, page, limit, order) => { @@ -37,8 +42,8 @@ module.exports = class DocumentsDAO { const subquery = this.knex('documents') .select('documents.id as id', 'documents.identifier as identifier', - 'data_contracts.identifier as data_contract_identifier', 'documents.data as data', - 'documents.revision as revision', 'documents.state_transition_hash as state_transition_hash', + 'data_contracts.identifier as data_contract_identifier', 'documents.data as document_data', + 'documents.revision as revision', 'documents.state_transition_hash as tx_hash', 'documents.deleted as deleted') .select(this.knex.raw('rank() over (partition by documents.identifier order by documents.id desc) rank')) .leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id') @@ -46,7 +51,7 @@ module.exports = class DocumentsDAO { const filteredDocuments = this.knex.with('with_alias', subquery) .select('id', 'identifier', 'rank', 'revision', 'data_contract_identifier', - 'state_transition_hash', 'deleted', 'data', + 'tx_hash', 'deleted', 'document_data', this.knex('with_alias').count('*').as('total_count').where('rank', '1')) .select(this.knex.raw(`rank() over (order by id ${order}) row_number`)) .from('with_alias') @@ -55,13 +60,15 @@ module.exports = class DocumentsDAO { .as('documents') const rows = await this.knex(filteredDocuments) - .select('id', 'identifier', 'row_number', 'revision', 'data_contract_identifier', 'state_transition_hash', 'deleted', 'data', 'total_count') + .select('identifier', 'row_number', 'revision', 'data_contract_identifier', + 'tx_hash', 'deleted', 'document_data', 'total_count', 'blocks.timestamp as timestamp') .whereBetween('row_number', [fromRank, toRank]) - .orderBy('id', order); + .join('state_transitions', 'tx_hash', 'state_transitions.hash') + .join('blocks', 'blocks.hash', 'state_transitions.block_hash') const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0; - const resultSet = rows.map((row) => Document.fromRow(row)); + const resultSet = rows.map((row) => Document.fromRow({...row, data: row.document_data})); return new PaginatedResultSet(resultSet, page, limit, totalCount) } diff --git a/packages/api/src/dao/IdentitiesDAO.js b/packages/api/src/dao/IdentitiesDAO.js index 64baabb76..e8aaca28f 100644 --- a/packages/api/src/dao/IdentitiesDAO.js +++ b/packages/api/src/dao/IdentitiesDAO.js @@ -7,42 +7,52 @@ module.exports = class IdentitiesDAO { getIdentityByIdentifier = async (identifier) => { const subquery = this.knex('identities') - .select('identities.id','identities.identifier as identifier', 'identities.state_transition_hash as st_hash', + .select('identities.id','identities.identifier as identifier', 'identities.state_transition_hash as tx_hash', 'identities.revision as revision', 'identities.state_transition_hash as state_transition_hash') .select(this.knex.raw('rank() over (partition by identities.identifier order by identities.id desc) rank')) .where('identities.identifier', '=', identifier) .as('all_identities') const lastRevisionIdentities = this.knex(subquery) - .select('identifier', 'revision', 'st_hash', + .select('identifier', 'revision', 'tx_hash', 'transfers.id as transfer_id', 'transfers.sender as sender', 'transfers.recipient as recipient', 'transfers.amount as amount') .where('rank', 1) .leftJoin('transfers', 'transfers.recipient', 'identifier') - - const documentSubQuery = this.knex('documents') - .select('documents.id as id', 'documents.state_transition_hash as st_hash') - .join('state_transitions', 'st_hash', 'state_transitions.hash') + const documentsSubQuery = this.knex('documents') + .select('documents.id', 'documents.state_transition_hash', 'state_transitions.owner as owner') + .select(this.knex.raw('rank() over (partition by documents.identifier order by documents.id desc) rank')) + .leftJoin('state_transitions', 'documents.state_transition_hash', 'state_transitions.hash') .where('state_transitions.owner', identifier) + .as('as_documents') const dataContractsSubQuery = this.knex('data_contracts') - .select('data_contracts.id as id', 'data_contracts.state_transition_hash as st_hash') - .join('state_transitions', 'st_hash', 'state_transitions.hash') + .select('data_contracts.id', 'data_contracts.state_transition_hash', 'state_transitions.owner as owner') + .select(this.knex.raw('rank() over (partition by data_contracts.identifier order by data_contracts.id desc) rank')) + .leftJoin('state_transitions', 'data_contracts.state_transition_hash', 'state_transitions.hash') + .where('state_transitions.owner', identifier) + .as('as_data_contracts') + + const transfersSubquery = this.knex('transfers') + .select('transfers.id as id', 'transfers.state_transition_hash as tx_hash') + .leftJoin('state_transitions', 'tx_hash', 'state_transitions.hash') .where('state_transitions.owner', identifier) const rows = await this.knex.with('with_alias', lastRevisionIdentities) .select('identifier', 'revision', - 'transfer_id', 'sender', 'st_hash', 'blocks.timestamp as timestamp', + 'transfer_id', 'sender', 'tx_hash', 'blocks.timestamp as timestamp', 'recipient', 'amount') - .join('state_transitions', 'state_transitions.hash', 'st_hash') + .join('state_transitions', 'state_transitions.hash', 'tx_hash') .join('blocks', 'state_transitions.block_hash', 'blocks.hash') .select(this.knex('with_alias').sum('amount').where('recipient', identifier).as('total_received')) .select(this.knex('with_alias').sum('amount').where('sender', identifier).as('total_sent')) .select(this.knex('state_transitions').count('*').where('owner', identifier).as('total_txs')) - .select(this.knex.with('with_documents', documentSubQuery).count('*').where('owner', identifier).as('total_documents')) - .select(this.knex.with('with_data_contracts', dataContractsSubQuery).count('*').where('owner', identifier).as('total_data_contracts')) + .select(this.knex(documentsSubQuery).count('*').where('rank', 1).as('total_documents')) + .select(this.knex(dataContractsSubQuery).count('*').where('rank', 1).as('total_data_contracts')) + .select(this.knex.with('with_transfers', transfersSubquery).count('*').as('total_transfers')) .from('with_alias') + .limit(1) if (!rows.length) { return null diff --git a/packages/api/src/models/DataContract.js b/packages/api/src/models/DataContract.js index 34aa3b267..b2b77a273 100644 --- a/packages/api/src/models/DataContract.js +++ b/packages/api/src/models/DataContract.js @@ -2,14 +2,17 @@ module.exports = class DataContract { identifier schema version + txHash - constructor(identifier, schema, version) { + constructor(identifier, schema, version, txHash, timestamp) { this.identifier = identifier; this.schema = schema; this.version = version; + this.txHash = txHash; + this.timestamp = timestamp; } - static fromRow({identifier, schema, version}) { - return new DataContract(identifier, schema, version) + static fromRow({identifier, schema, version, tx_hash, timestamp}) { + return new DataContract(identifier, schema, version, tx_hash, timestamp) } } diff --git a/packages/api/src/models/Document.js b/packages/api/src/models/Document.js index fe2068677..9a4dabe3e 100644 --- a/packages/api/src/models/Document.js +++ b/packages/api/src/models/Document.js @@ -2,20 +2,23 @@ module.exports = class Document { identifier dataContractIdentifier revision - stateTransitionHash + txHash deleted data + timestamp - constructor(identifier, dataContractIdentifier, revision, stateTransitionHash, deleted, data) { + constructor(identifier, dataContractIdentifier, revision, txHash, deleted, data, timestamp) { this.identifier = identifier; this.dataContractIdentifier = dataContractIdentifier; this.revision = revision; - this.stateTransitionHash = stateTransitionHash; this.deleted = deleted; this.data = data; + this.txHash = txHash; + this.data = data; + this.timestamp = timestamp; } - static fromRow({identifier, data_contract_identifier, revision, state_transition_hash, deleted, data}) { - return new Document(identifier, data_contract_identifier, revision, state_transition_hash, deleted, data) + static fromRow({identifier, data_contract_identifier, revision, tx_hash, deleted, data, timestamp}) { + return new Document(identifier, data_contract_identifier, revision, tx_hash, deleted, data, timestamp) } } diff --git a/packages/api/src/models/Identity.js b/packages/api/src/models/Identity.js index 657be8c50..4ecf9c67c 100644 --- a/packages/api/src/models/Identity.js +++ b/packages/api/src/models/Identity.js @@ -4,16 +4,21 @@ module.exports = class Identity { balance timestamp totalTxs + txHash - constructor(identifier, revision, balance, timestamp, totalTxs) { + constructor(identifier, revision, balance, timestamp, totalTxs, totalDataContracts, totalDocuments, totalTransfers, txHash) { this.identifier = identifier; this.revision = revision; this.balance = balance; this.timestamp = timestamp; this.totalTxs = totalTxs; + this.totalDocuments = totalDocuments; + this.totalDataContracts = totalDataContracts; + this.totalTransfers = totalTransfers; + this.txHash = txHash } - static fromRow({identifier, revision, balance, timestamp, total_txs}) { - return new Identity(identifier, revision, balance, timestamp, total_txs) + static fromRow({identifier, revision, balance, timestamp, total_txs, total_data_contracts, total_documents, total_transfers, tx_hash}) { + return new Identity(identifier, revision, balance, timestamp, total_txs, total_data_contracts, total_documents, total_transfers, tx_hash) } } From 59d4406849a7ff7f19dca496f6bffdab5bc6689d Mon Sep 17 00:00:00 2001 From: pshenmic Date: Fri, 27 Oct 2023 21:05:02 +0700 Subject: [PATCH 3/3] Add some missing fields --- packages/api/src/models/DataContract.js | 1 + packages/api/src/models/Identity.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/api/src/models/DataContract.js b/packages/api/src/models/DataContract.js index b2b77a273..4d921ad6e 100644 --- a/packages/api/src/models/DataContract.js +++ b/packages/api/src/models/DataContract.js @@ -3,6 +3,7 @@ module.exports = class DataContract { schema version txHash + timestamp constructor(identifier, schema, version, txHash, timestamp) { this.identifier = identifier; diff --git a/packages/api/src/models/Identity.js b/packages/api/src/models/Identity.js index 4ecf9c67c..7bf464b73 100644 --- a/packages/api/src/models/Identity.js +++ b/packages/api/src/models/Identity.js @@ -3,8 +3,11 @@ module.exports = class Identity { revision balance timestamp - totalTxs txHash + totalTxs + totalTransfers + totalDocuments + totalDataContracts constructor(identifier, revision, balance, timestamp, totalTxs, totalDataContracts, totalDocuments, totalTransfers, txHash) { this.identifier = identifier;