Skip to content

Commit

Permalink
Merge pull request #65 from pshenmic/feat/api-pagination
Browse files Browse the repository at this point in the history
Implement pagination on the API
  • Loading branch information
pshenmic authored Oct 13, 2023
2 parents c118557 + e4a85a7 commit 868c1ef
Show file tree
Hide file tree
Showing 10 changed files with 138 additions and 63 deletions.
8 changes: 6 additions & 2 deletions packages/api/src/controllers/BlocksController.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ class BlocksController {
}

getBlocks = async (request, response) => {
const {from, to, order = 'desc'} = request.query
const {page = 1, limit = 10, order = 'asc'} = request.query

const blocks = await this.blocksDAO.getBlocksPaginated(from, to, order)
if (order !== 'asc' && order !== 'desc') {
return response.status(400).send({message: `invalid ordering value ${order}. only 'asc' or 'desc' is valid values`})
}

const blocks = await this.blocksDAO.getBlocks(Number(page), Number(limit), order)

response.send(blocks);
}
Expand Down
8 changes: 7 additions & 1 deletion packages/api/src/controllers/DataContractsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ class DataContractsController {
}

getDataContracts = async (request, response) => {
const dataContracts = await this.dataContractsDAO.getDataContracts();
const {page = 1, limit = 10, order = 'asc'} = request.query

if (order !== 'asc' && order !== 'desc') {
return response.status(400).send({message: `invalid ordering value ${order}. only 'asc' or 'desc' is valid values`})
}

const dataContracts = await this.dataContractsDAO.getDataContracts(Number(page), Number(limit), order);

response.send(dataContracts)
}
Expand Down
7 changes: 6 additions & 1 deletion packages/api/src/controllers/DocumentsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ class DocumentsController {

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

const documents = await this.documentsDAO.getDocumentsByDataContract(identifier)
if (order !== 'asc' && order !== 'desc') {
return response.status(400).send({message: `invalid ordering value ${order}. only 'asc' or 'desc' is valid values`})
}

const documents = await this.documentsDAO.getDocumentsByDataContract(identifier, Number(page), Number(limit), order)

response.send(documents);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/api/src/controllers/TransactionsController.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,13 @@ class TransactionsController {
}

getTransactions = async (request, response) => {
const {from, to} = request.query
const {page = 1, limit = 10, order = 'asc'} = request.query

const transactions = await this.transactionsDAO.getTransactions(from, to)
if (order !== 'asc' && order !== 'desc') {
return response.status(400).send({message: `invalid ordering value ${order}. only 'asc' or 'desc' is valid values`})
}

const transactions = await this.transactionsDAO.getTransactions(Number(page), Number(limit), order)

response.send(transactions);
}
Expand Down
33 changes: 17 additions & 16 deletions packages/api/src/dao/BlocksDAO.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Block = require("../models/Block");
const PaginatedResultSet = require("../models/PaginatedResultSet");

module.exports = class BlockDAO {
constructor(knex) {
Expand Down Expand Up @@ -62,23 +63,21 @@ module.exports = class BlockDAO {
return Block.fromRow({header: block, txs});
}

getBlocksPaginated = async (from, to, order = 'desc') => {
getBlocks = async (page, limit, order) => {
const fromRank = (page - 1) * limit
const toRank = fromRank + limit - 1

const subquery = this.knex('blocks')
.select('blocks.hash as hash', 'blocks.height as height', 'blocks.timestamp as timestamp', 'blocks.block_version as block_version', 'blocks.app_version as app_version', 'blocks.l1_locked_height as l1_locked_height').as('blocks')
.where(function () {
if (from && to) {
this.where('height', '>=', from)
this.where('height', '<=', to)
}
})
.limit(30)
.orderBy('blocks.height', order);
.select(this.knex('blocks').count('height').as('total_count'), 'blocks.hash as hash', 'blocks.height as height', 'blocks.timestamp as timestamp', 'blocks.block_version as block_version', 'blocks.app_version as app_version', 'blocks.l1_locked_height as l1_locked_height').as('blocks')
.select(this.knex.raw(`rank() over (order by blocks.height ${order}) rank`))

const rows = await this.knex(subquery)
.select('blocks.hash as hash', 'height', 'timestamp', 'block_version', 'app_version', 'l1_locked_height', 'state_transitions.hash as st_hash')
.select('rank', 'total_count', 'blocks.hash as hash', 'height', 'timestamp', 'block_version', 'app_version', 'l1_locked_height', 'state_transitions.hash as st_hash')
.leftJoin('state_transitions', 'state_transitions.block_hash', 'blocks.hash')
.groupBy('blocks.hash', 'height', 'blocks.timestamp', 'block_version', 'app_version', 'l1_locked_height', 'state_transitions.hash')
.orderBy('height', 'desc')
.whereBetween('rank', [fromRank, toRank])
.orderBy('blocks.height', order);

const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0;

// map-reduce Blocks with Transactions
const blocksMap = rows.reduce((blocks, row) => {
Expand All @@ -93,8 +92,10 @@ module.exports = class BlockDAO {
return {...blocks, [row.hash]: {...row, txs}}
}, {})

return Object.keys(blocksMap).map(blockHash => Block.fromRow({
header: blocksMap[blockHash], txs: blocksMap[blockHash].txs
}))
const resultSet = Object.keys(blocksMap).map(blockHash => Block.fromRow({
header: blocksMap[blockHash], txs: blocksMap[blockHash].txs
}))

return new PaginatedResultSet(resultSet, page, limit, totalCount)
}
}
27 changes: 22 additions & 5 deletions packages/api/src/dao/DataContractsDAO.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
const DataContract = require("../models/DataContract");
const PaginatedResultSet = require("../models/PaginatedResultSet");

module.exports = class DataContractsDAO {
constructor(knex) {
this.knex = knex;
}

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

const subquery = this.knex('data_contracts')
.select(this.knex.raw('data_contracts.id as id, data_contracts.identifier as identifier, data_contracts.version as version, rank() over (partition by identifier order by version desc) rank'))
.select(this.knex('data_contracts').countDistinct('identifier').as('total_count'), 'data_contracts.id as id', 'data_contracts.identifier as identifier', 'data_contracts.version as version')
.select(this.knex.raw(`rank() over (partition by identifier order by version desc) rank`))
.as('data_contracts')

const rows = await this.knex(subquery)
.select('id', 'identifier', 'version', 'rank')
const filteredContracts = this.knex(subquery)
.select('total_count', 'id', 'identifier', 'version', 'rank')
.select(this.knex.raw(`rank() over (order by id ${order}) row_number`))
.where('rank', '=', 1)
.orderBy('id', order)
.as('temp')

const rows = await this.knex(filteredContracts)
.select('total_count', 'id', 'identifier', 'version', 'row_number')
.whereBetween('row_number', [fromRank, toRank])
.orderBy('id', order);

const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0;

const resultSet = rows.map(dataContract => DataContract.fromRow(dataContract));

return rows.map(dataContract => DataContract.fromRow(dataContract));
return new PaginatedResultSet(resultSet, page, limit, totalCount);
}

getDataContractByIdentifier = async (identifier) => {
Expand Down
39 changes: 31 additions & 8 deletions packages/api/src/dao/DocumentsDAO.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Document = require("../models/Document");
const PaginatedResultSet = require("../models/PaginatedResultSet");

module.exports = class DocumentsDAO {
constructor(knex) {
Expand All @@ -7,7 +8,8 @@ module.exports = class DocumentsDAO {

getDocumentByIdentifier = async (identifier) => {
const subquery = this.knex('documents')
.select(this.knex.raw('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, documents.deleted as deleted, rank() over (partition by documents.identifier order by documents.id desc) rank'))
.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', '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')
.where('documents.identifier', '=', identifier)
.as('documents')
Expand All @@ -25,17 +27,38 @@ module.exports = class DocumentsDAO {
return Document.fromRow(row);
}

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

const subquery = this.knex('documents')
.select(this.knex.raw('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, documents.deleted as deleted, rank() over (partition by documents.identifier order by documents.id desc) rank'))
.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',
'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')
.where('data_contracts.identifier', identifier)
.as('documents')

const rows = await this.knex(subquery)
.select('id', 'identifier', 'data_contract_identifier', 'data', 'revision', 'deleted', 'rank', 'state_transition_hash')
.orderBy('documents.id', 'asc')
const filteredDocuments = this.knex.with('with_alias', subquery)
.select('id', 'identifier', 'rank', 'revision', 'data_contract_identifier',
'state_transition_hash', 'deleted', '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')
.where('rank', '1')
.orderBy('id', order)
.as('test')

const rows = await this.knex(filteredDocuments)
.select('id', 'identifier', 'row_number', 'revision', 'data_contract_identifier', 'state_transition_hash', 'deleted', 'data', 'total_count')
.whereBetween('row_number', [fromRank, toRank])
.orderBy('id', order);

const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0;

const resultSet = rows.map((row) => Document.fromRow(row));

return rows.map((row) => Document.fromRow(row));
return new PaginatedResultSet(resultSet, page, limit, totalCount)
}
}
38 changes: 22 additions & 16 deletions packages/api/src/dao/TransactionsDAO.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const Transaction = require("../models/Transaction");
const PaginatedResultSet = require("../models/PaginatedResultSet");

module.exports = class TransactionsDAO {
constructor(knex) {
Expand All @@ -19,21 +20,26 @@ module.exports = class TransactionsDAO {
return Transaction.fromRow(row)
}

getTransactions = async (from, to) => {
const rows = await this.knex
.select('state_transitions.hash as hash', 'state_transitions.data as data', 'state_transitions.type as type',
'state_transitions.index as index', 'blocks.height as block_height', 'blocks.timestamp as timestamp')
.from('state_transitions')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.block_hash')
.where((builder) => {
if (from && to) {
builder.where('block_height', '<', to);
builder.where('block_height', '>', from);
}
})
.limit(30)
.orderBy('blocks.height', 'desc')

return rows.map((row) => Transaction.fromRow(row))
getTransactions = async (page, limit, order) => {
const fromRank = (page - 1) * limit
const toRank = fromRank + limit - 1

const subquery = this.knex('state_transitions')
.select(this.knex('state_transitions').count('hash').as('total_count'), 'state_transitions.hash as hash', 'state_transitions.data as data', 'state_transitions.type as type',
'state_transitions.index as index')
.select(this.knex.raw(`rank() over (order by state_transitions.id ${order}) rank`))
.as('state_transitions')

const rows = await this.knex(subquery)
.select('total_count', 'data', 'type', 'index', 'rank', 'state_transitions.hash as hash', 'blocks.height as block_height', 'blocks.timestamp as timestamp')
.leftJoin('blocks', 'blocks.hash', 'state_transitions.hash')
.whereBetween('rank', [fromRank, toRank])
.orderBy('block_height', order);

const totalCount = rows.length > 0 ? Number(rows[0].total_count) : 0;

const resultSet = rows.map((row) => Transaction.fromRow(row))

return new PaginatedResultSet(resultSet, page, limit, totalCount)
}
}
9 changes: 9 additions & 0 deletions packages/api/src/models/PaginatedResultSet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = class PaginatedResultSet {
resultSet
pagination

constructor(resultSet, page, limit, total) {
this.resultSet = resultSet;
this.pagination = {page, limit, total: resultSet.length ? total : -1};
}
}
24 changes: 12 additions & 12 deletions packages/indexer/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 868c1ef

Please sign in to comment.