Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New fields for documents and list of documents by document type #406

Open
wants to merge 32 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
230 changes: 192 additions & 38 deletions packages/api/README.md

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion packages/api/src/controllers/DocumentsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const Document = require('../models/Document')

class DocumentsController {
constructor (client, knex, dapi) {
this.documentsDAO = new DocumentsDAO(knex)
this.documentsDAO = new DocumentsDAO(knex, client)
this.datacContractsDAO = new DataContractsDAO(knex)
this.client = client
this.dapi = dapi
Expand Down Expand Up @@ -75,6 +75,15 @@ class DocumentsController {

response.send(documents)
}

getDocumentRevisions = async (request, response) => {
const { identifier } = request.params
const { page = 1, limit = 10, order = 'asc' } = request.query

const transactions = await this.documentsDAO.getDocumentRevisions(identifier, Number(page ?? 1), Number(limit ?? 10), order)

response.send(transactions)
}
}

module.exports = DocumentsController
2 changes: 1 addition & 1 deletion packages/api/src/controllers/MainController.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MainController {
constructor (knex, dapi, client) {
this.blocksDAO = new BlocksDAO(knex, dapi)
this.dataContractsDAO = new DataContractsDAO(knex)
this.documentsDAO = new DocumentsDAO(knex)
this.documentsDAO = new DocumentsDAO(knex, client)
this.transactionsDAO = new TransactionsDAO(knex, dapi)
this.identitiesDAO = new IdentitiesDAO(knex, dapi, client)
this.validatorsDAO = new ValidatorsDAO(knex)
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/dao/DataContractsDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module.exports = class DataContractsDAO {
'data_contracts.version as version', 'state_transitions.hash as tx_hash', 'blocks.timestamp as timestamp')
.select(this.knex('documents').count('*')
.leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id')
.whereRaw(`data_contracts.identifier = '${identifier}'`)
.whereRaw('data_contracts.identifier = ? and revision = ?', [identifier, 1])
.as('documents_count'))
.leftJoin('state_transitions', 'data_contracts.state_transition_hash', 'state_transitions.hash')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
Expand Down
128 changes: 104 additions & 24 deletions packages/api/src/dao/DocumentsDAO.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@
const Document = require('../models/Document')
const PaginatedResultSet = require('../models/PaginatedResultSet')
const DocumentActionEnum = require('../enums/DocumentActionEnum')
const { decodeStateTransition } = require('../utils')

module.exports = class DocumentsDAO {
constructor (knex) {
constructor (knex, client) {
this.knex = knex
this.client = client
}

getDocumentByIdentifier = async (identifier) => {
const subquery = this.knex('documents')
.select('documents.id', 'documents.identifier as identifier', 'documents.owner as document_owner',
'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', 'documents.is_system as is_system', 'document_type_name',
'transition_type'
)
.select(this.knex.raw('rank() over (partition by documents.identifier order by documents.id desc) rank'))
.select('documents.id as id', 'documents.identifier as identifier', 'documents.owner as document_owner',
'data_contracts.identifier as data_contract_identifier', 'documents.data as data', 'document_type_name',
'documents.revision as revision', 'documents.state_transition_hash as tx_hash', 'state_transitions.data as tx_data',
'documents.deleted as deleted', 'documents.is_system as is_system', 'documents.transition_type as transition_type')
.leftJoin('data_contracts', 'data_contracts.id', 'documents.data_contract_id')
.leftJoin('state_transitions', 'hash', 'documents.state_transition_hash')
.where('documents.identifier', '=', identifier)
.as('documents')

const rows = await this.knex(subquery)
.select('identifier', 'document_owner', 'data_contract_identifier', 'data_contract_data',
'revision', 'deleted', 'rank', 'tx_hash', 'is_system', 'blocks.timestamp as timestamp',
'document_type_name', 'transition_type')
.select('identifier', 'document_owner', 'data_contract_identifier', 'transition_type',
'deleted', 'tx_hash', 'is_system', 'blocks.timestamp as timestamp', 'document_type_name')
.select(
this.knex(subquery)
.select('documents.data')
.orderBy('documents.id', 'desc')
.limit(1)
.as('data')
)
.select(
this.knex(subquery)
.select('documents.revision')
.orderBy('documents.revision', 'desc')
.limit(1)
.as('revision')
)
.select(
this.knex(subquery)
.select('documents.tx_data')
.where('documents.transition_type', DocumentActionEnum.Create)
.limit(1)
.as('create_tx_data')
)
.orderBy('documents.id', 'desc')
.leftJoin('state_transitions', 'state_transitions.hash', 'tx_hash')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.limit(1)
Expand All @@ -33,46 +55,77 @@ module.exports = class DocumentsDAO {
return null
}

return Document.fromRow({
let transitions = []

if (row.create_tx_data) {
const decodedTransitions = await decodeStateTransition(this.client, row.create_tx_data)

transitions = decodedTransitions.transitions ?? []
}

const [transitionWithEntropy] = transitions?.filter(transition => transition.entropy !== '')

const document = Document.fromRow({
...row,
data: row.data_contract_data,
owner: row.document_owner
})

return Document.fromObject({
...document,
entropy: transitionWithEntropy?.entropy,
prefundedVotingBalance: transitionWithEntropy?.prefundedVotingBalance,
nonce: transitionWithEntropy?.nonce
})
}

getDocumentsByDataContract = async (identifier, typeName, page, limit, order) => {
const fromRank = ((page - 1) * limit) + 1
const toRank = fromRank + limit - 1

const typeQuery = typeName
? `document_type_name = '${typeName}'`
: 'true'
let typeQuery = 'data_contracts.identifier = ?'
const queryBindings = [identifier]

if (typeName) {
typeQuery = typeQuery + ' and document_type_name = ?'
queryBindings.push(typeName)
}

const dataSubquery = this.knex('documents')
.select('documents.data as data', 'documents.identifier as identifier')
.select(this.knex.raw('rank() over (partition by documents.identifier order by documents.revision desc) rank'))
.orderBy('documents.id', 'desc')
.as('documents_data')

const filterDataSubquery = this.knex(dataSubquery)
.select('data', 'identifier')
.where('documents_data.rank', '1')
.as('documents_data')

const subquery = this.knex('documents')
.select('documents.id as id', 'documents.identifier as identifier', 'documents.owner as document_owner',
'data_contracts.identifier as data_contract_identifier', 'documents.data as document_data',
'data_contracts.identifier as data_contract_identifier', 'document_type_name', 'documents.data as document_data',
'documents.revision as revision', 'documents.state_transition_hash as tx_hash',
'documents.deleted as deleted', 'documents.is_system as is_system', 'document_type_name', 'transition_type')
'documents.deleted as deleted', 'documents.is_system as is_system', 'transition_type', 'prefunded_voting_balance')
.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')
.where('data_contracts.identifier', identifier)
.andWhereRaw(typeQuery)
.whereRaw(typeQuery, queryBindings)

const filteredDocuments = this.knex.with('with_alias', subquery)
.select('id', 'identifier', 'document_owner', 'rank', 'revision', 'data_contract_identifier',
'tx_hash', 'deleted', 'is_system', 'document_data', 'document_type_name', 'transition_type',
.select('id', 'with_alias.identifier as identifier', 'document_owner', 'rank', 'revision', 'data_contract_identifier',
'tx_hash', 'deleted', 'is_system', 'document_data', 'document_type_name', 'transition_type', 'prefunded_voting_balance',
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')
.where('rank', '1')
.as('documents')

const rows = await this.knex(filteredDocuments)
.select('documents.id as id', 'identifier', 'document_owner', 'row_number', 'revision', 'data_contract_identifier',
'tx_hash', 'deleted', 'document_data', 'total_count', 'is_system', 'blocks.timestamp as timestamp',
.select('documents.id as id', 'documents.identifier as identifier', 'document_owner', 'row_number', 'revision', 'data_contract_identifier',
'tx_hash', 'deleted', 'document_data', 'total_count', 'is_system', 'blocks.timestamp as timestamp', 'prefunded_voting_balance',
'document_type_name', 'transition_type')
.whereBetween('row_number', [fromRank, toRank])
.leftJoin('state_transitions', 'tx_hash', 'state_transitions.hash')
.leftJoin(filterDataSubquery, 'documents.identifier', '=', 'documents_data.identifier')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.orderBy('id', order)

Expand All @@ -86,4 +139,31 @@ module.exports = class DocumentsDAO {

return new PaginatedResultSet(resultSet, page, limit, totalCount)
}

getDocumentRevisions = async (identifier, page, limit, order) => {
const fromRank = ((page - 1) * limit) + 1
const toRank = fromRank + limit - 1

const subquery = this.knex('documents')
.select(
'documents.id as id', 'revision', 'transition_type', 'gas_used', 'timestamp',
'documents.owner as owner', 'state_transitions.hash as hash', 'documents.data as data')
.select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`))
.where('documents.identifier', '=', identifier)
.leftJoin('state_transitions', 'state_transition_hash', 'state_transitions.hash')
.leftJoin('blocks', 'state_transitions.block_hash', 'blocks.hash')
.as('subquery')

const rows = await this.knex(subquery)
.select('revision', 'gas_used', 'subquery.owner', 'hash as tx_hash', 'timestamp', 'transition_type', 'data')
.select(this.knex(subquery).count('*').as('total_count'))
.whereBetween('rank', [fromRank, toRank])
.orderBy('id', order)

const [row] = rows

const totalCount = row?.total_count

return new PaginatedResultSet(rows.map(Document.fromRow), page, limit, Number(totalCount))
}
}
19 changes: 11 additions & 8 deletions packages/api/src/dao/IdentitiesDAO.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ module.exports = class IdentitiesDAO {
.limit(1)

const statisticSubquery = this.knex('state_transitions')
.whereRaw(`owner = identifier and type=${IDENTITY_CREDIT_WITHDRAWAL}`)
.whereRaw('owner = identifier and type = ?', [IDENTITY_CREDIT_WITHDRAWAL])
.as('statistic')

const rows = await this.knex.with('with_alias', mainQuery)
Expand Down Expand Up @@ -124,12 +124,12 @@ module.exports = class IdentitiesDAO {
.select(
this.knex(statisticSubquery)
.count('id')
.whereRaw(`type=${IDENTITY_CREDIT_WITHDRAWAL}`)
.whereRaw('type=?', [IDENTITY_CREDIT_WITHDRAWAL])
.as('total_withdrawals'))
.select(
this.knex(statisticSubquery)
.count('id')
.whereRaw(`type=${IDENTITY_TOP_UP}`)
.whereRaw('type=?', [IDENTITY_TOP_UP])
.as('total_top_ups'))
.from('with_alias')

Expand Down Expand Up @@ -363,7 +363,7 @@ module.exports = class IdentitiesDAO {
'documents.revision as revision', 'documents.state_transition_hash as tx_hash',
'documents.deleted as deleted', 'documents.is_system as document_is_system', 'document_type_name', 'transition_type')
.select(this.knex.raw('rank() over (partition by documents.identifier order by documents.id desc) rank'))
.andWhereRaw(typeQuery, queryBindings)
.whereRaw(typeQuery, queryBindings)

const filteredDocuments = this.knex.with('with_alias', subquery)
.select('with_alias.id as document_id', 'identifier', 'document_owner', 'revision', 'data_contract_id',
Expand Down Expand Up @@ -423,14 +423,17 @@ module.exports = class IdentitiesDAO {
const fromRank = (page - 1) * limit + 1
const toRank = fromRank + limit - 1

let searchQuery = `(transfers.sender = '${identifier}' OR transfers.recipient = '${identifier}')`
let searchQuery = '(transfers.sender = ? OR transfers.recipient = ?)'
const searchBingings = [identifier, identifier]

if (typeof type === 'number') {
searchQuery = searchQuery + ` AND state_transitions.type = ${type}`
searchQuery = searchQuery + ' AND state_transitions.type = ?'
searchBingings.push(type)
}

if (hash) {
searchQuery = searchQuery + ` AND state_transitions.hash = '${hash}'`
searchQuery = searchQuery + ' AND state_transitions.hash = ?'
searchBingings.push(hash)
}

const subquery = this.knex('transfers')
Expand All @@ -443,7 +446,7 @@ module.exports = class IdentitiesDAO {
'state_transitions.gas_used as gas_used'
)
.select(this.knex.raw(`rank() over (order by transfers.id ${order}) rank`))
.whereRaw(searchQuery)
.whereRaw(searchQuery, searchBingings)
.leftJoin('state_transitions', 'state_transitions.hash', 'transfers.state_transition_hash')

const rows = await this.knex.with('with_alias', subquery)
Expand Down
21 changes: 14 additions & 7 deletions packages/api/src/models/Document.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,37 @@ module.exports = class Document {
data
timestamp
system
typeName
entropy
prefundedVotingBalance
documentTypeName
transitionType
nonce

constructor (identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, isSystem, typeName, transitionType) {
constructor (identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, isSystem, documentTypeName, transitionType, prefundedVotingBalance, entropy, nonce) {
this.identifier = identifier ? identifier.trim() : null
this.owner = owner ? owner.trim() : null
this.dataContractIdentifier = dataContractIdentifier ? dataContractIdentifier.trim() : null
this.revision = revision ?? null
this.deleted = deleted ?? null

this.data = data ?? null
this.txHash = txHash ?? null
this.data = data ?? null
this.timestamp = timestamp ?? null
this.system = isSystem ?? null
this.typeName = typeName ?? null
this.documentTypeName = documentTypeName ?? null
this.transitionType = transitionType ?? null
this.entropy = entropy ?? null
this.prefundedVotingBalance = prefundedVotingBalance ?? null
this.nonce = nonce ?? null
}

// eslint-disable-next-line camelcase
static fromRow ({ identifier, owner, data_contract_identifier, revision, tx_hash, deleted, data, timestamp, is_system, document_type_name, transition_type }) {
return new Document(identifier, owner, data_contract_identifier, revision, tx_hash, deleted, data ? JSON.stringify(data) : null, timestamp, is_system, document_type_name, Number(transition_type))
static fromRow ({ identifier, owner, data_contract_identifier, revision, tx_hash, deleted, data, timestamp, is_system, document_type_name, transition_type, prefunded_voting_balance }) {
return new Document(identifier, owner, data_contract_identifier, revision, tx_hash, deleted, data ? JSON.stringify(data) : null, timestamp, is_system, document_type_name, Number(transition_type), prefunded_voting_balance)
}

static fromObject ({ identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, system, typeName, transitionType }) {
return new Document(identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, system, typeName, transitionType)
static fromObject ({ identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, system, documentTypeName, transitionType, entropy, prefundedVotingBalance, nonce }) {
return new Document(identifier, owner, dataContractIdentifier, revision, txHash, deleted, data, timestamp, system, documentTypeName, transitionType, prefundedVotingBalance, entropy, nonce)
}
}
13 changes: 13 additions & 0 deletions packages/api/src/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,19 @@ module.exports = ({
}
}
},
{
path: '/document/:identifier/revisions',
method: 'GET',
handler: documentsController.getDocumentRevisions,
schema: {
params: {
type: 'object',
properties: {
identifier: { $ref: 'identifier#' }
}
}
}
},
{
path: '/identities',
method: 'GET',
Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,14 @@ const decodeStateTransition = async (client, base64) => {

switch (documentTransition.getAction()) {
case DocumentActionEnum.Create: {
const prefundedBalance = documentTransition.getPrefundedVotingBalance()
const prefundedVotingBalance = documentTransition.getPrefundedVotingBalance()

out.entropy = Buffer.from(documentTransition.getEntropy()).toString('hex')

out.data = documentTransition.getData()
out.prefundedBalance = prefundedBalance
out.prefundedVotingBalance = prefundedVotingBalance
? Object.fromEntries(
Object.entries(prefundedBalance)
Object.entries(prefundedVotingBalance)
.map(prefund => [prefund[0], Number(prefund[1])])
)
: null
Expand Down
Loading
Loading