forked from lthn/blockchain
api pagination block?limit=20&offset=200&start=1337
This commit is contained in:
parent
0c7c242d28
commit
0ba4c334bb
4 changed files with 247 additions and 55 deletions
|
|
@ -16,8 +16,7 @@
|
|||
#include "controller/ApiCoreInfo.hpp"
|
||||
#include "controller/path/info.hpp"
|
||||
#include "controller/path/block.hpp"
|
||||
#include "controller/path/block/hash.hpp"
|
||||
#include "controller/path/block/id.hpp"
|
||||
#include "controller/path/block/identifier.hpp"
|
||||
#include "controller/path/info/version.hpp"
|
||||
|
||||
#include "oatpp/network/Server.hpp"
|
||||
|
|
@ -82,14 +81,9 @@ void ApiServer::run() {
|
|||
docEndpoints->append(blockController->getEndpoints());
|
||||
router->addController(blockController);
|
||||
|
||||
auto blockByHashController = std::make_shared<BlockByHashController>();
|
||||
docEndpoints->append(blockByHashController->getEndpoints());
|
||||
router->addController(blockByHashController);
|
||||
|
||||
auto blockByIdController = std::make_shared<BlockByIdController>();
|
||||
docEndpoints->append(blockByIdController->getEndpoints());
|
||||
router->addController(blockByIdController);
|
||||
|
||||
auto blockIdentifierController = std::make_shared<BlockIdentifierController>();
|
||||
docEndpoints->append(blockIdentifierController->getEndpoints());
|
||||
router->addController(blockIdentifierController);
|
||||
|
||||
OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::swagger::DocumentInfo>, swaggerDocumentInfo)
|
||||
([]
|
||||
|
|
|
|||
|
|
@ -24,61 +24,21 @@
|
|||
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include <string> // For std::string and std::stoull
|
||||
#include <string>
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
/**
|
||||
* Block Controller
|
||||
* Acts as a proxy to fetch blocks by hash or by height (ID).
|
||||
* Retrieves one or more blocks with optional pagination.
|
||||
*/
|
||||
class BlockController : public oatpp::web::server::api::ApiController {
|
||||
private:
|
||||
OATPP_COMPONENT(std::shared_ptr<ApiCoreInfo>, m_core_info);
|
||||
public:
|
||||
explicit BlockController(OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper)
|
||||
{}
|
||||
public:
|
||||
|
||||
ENDPOINT_INFO(getBlock) {
|
||||
info->summary = "Get a block by its hash or height (ID)";
|
||||
info->addTag("Block");
|
||||
info->pathParams["identifier"].description = "The hash (hex string) or height (integer) of the block to retrieve.";
|
||||
info->addResponse<Object<BlockDetailsModel>>(Status::CODE_200, "application/json");
|
||||
info->addResponse(Status::CODE_404, "text/plain");
|
||||
info->addResponse(Status::CODE_400, "text/plain");
|
||||
}
|
||||
ENDPOINT("GET", "/block/{identifier}", getBlock, PATH(String, identifier)) {
|
||||
|
||||
currency::block_rpc_extended_info rpc_details;
|
||||
bool block_found = false;
|
||||
|
||||
// Check if the identifier consists only of digits
|
||||
if (identifier->find_first_not_of("0123456789") == std::string::npos) {
|
||||
// It's a numeric ID (height)
|
||||
try {
|
||||
uint64_t height = std::stoull(identifier->c_str());
|
||||
block_found = m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(height, rpc_details);
|
||||
} catch (const std::exception& e) {
|
||||
return createResponse(Status::CODE_400, "Invalid block height format");
|
||||
}
|
||||
} else {
|
||||
// It's a hash
|
||||
crypto::hash block_hash{};
|
||||
if (!epee::string_tools::hex_to_pod(*identifier, block_hash)) {
|
||||
return createResponse(Status::CODE_400, "Invalid block hash format");
|
||||
}
|
||||
block_found = m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(block_hash, rpc_details);
|
||||
}
|
||||
|
||||
if (!block_found) {
|
||||
return createResponse(Status::CODE_404, "Block not found");
|
||||
}
|
||||
|
||||
// Common logic to populate the DTO
|
||||
// Helper function to populate a block details model from RPC details
|
||||
oatpp::Object<BlockDetailsModel> populateBlockDetailsModel(const currency::block_rpc_extended_info& rpc_details) {
|
||||
auto blockDetails = BlockDetailsModel::createShared();
|
||||
|
||||
blockDetails->id = rpc_details.id;
|
||||
blockDetails->height = rpc_details.height;
|
||||
blockDetails->timestamp = rpc_details.timestamp;
|
||||
|
|
@ -111,8 +71,99 @@ public:
|
|||
tx_details_list->push_back(tx_model);
|
||||
}
|
||||
blockDetails->transactions_details = tx_details_list;
|
||||
return blockDetails;
|
||||
}
|
||||
|
||||
return createDtoResponse(Status::CODE_200, blockDetails);
|
||||
public:
|
||||
explicit BlockController(OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper)
|
||||
{}
|
||||
public:
|
||||
|
||||
ENDPOINT_INFO(getBlocks) {
|
||||
info->summary = "Get one or more blocks, with optional pagination.";
|
||||
info->addTag("Block");
|
||||
info->queryParams["limit"].description = "Number of blocks to retrieve. Default is 1. If limit is 1, a single block object is returned. Otherwise, a list of blocks is returned.";
|
||||
info->queryParams["offset"].description = "Number of blocks to skip from the start height. Default is 0.";
|
||||
info->queryParams["start"].description = "The starting block height. If not provided, the current top block height is used.";
|
||||
info->addResponse<Object<BlockDetailsModel>>(Status::CODE_200, "application/json", "A single block object.");
|
||||
info->addResponse<List<Object<BlockDetailsModel>>>(Status::CODE_200, "application/json", "A list of block objects.");
|
||||
info->addResponse(Status::CODE_404, "text/plain");
|
||||
info->addResponse(Status::CODE_400, "text/plain");
|
||||
}
|
||||
ENDPOINT("GET", "/block", getBlocks, QUERIES(QueryParams, queryParams)) {
|
||||
const auto limitStr = queryParams.get("limit");
|
||||
const auto offsetStr = queryParams.get("offset");
|
||||
const auto startStr = queryParams.get("start");
|
||||
|
||||
uint64_t limit = 1;
|
||||
if (limitStr) {
|
||||
try {
|
||||
limit = std::stoull(limitStr->c_str());
|
||||
} catch (const std::exception& e) {
|
||||
return createResponse(Status::CODE_400, "Invalid 'limit' parameter");
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t offset = 0;
|
||||
if (offsetStr) {
|
||||
try {
|
||||
offset = std::stoull(offsetStr->c_str());
|
||||
} catch (const std::exception& e) {
|
||||
return createResponse(Status::CODE_400, "Invalid 'offset' parameter");
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t start_height;
|
||||
if (startStr) {
|
||||
try {
|
||||
start_height = std::stoull(startStr->c_str());
|
||||
} catch (const std::exception& e) {
|
||||
return createResponse(Status::CODE_400, "Invalid 'start' parameter");
|
||||
}
|
||||
} else {
|
||||
start_height = m_core_info->getCore().get_blockchain_storage().get_current_blockchain_size() - 1;
|
||||
}
|
||||
|
||||
if (limit == 0) {
|
||||
return createResponse(Status::CODE_400, "'limit' must be greater than 0");
|
||||
}
|
||||
|
||||
if (start_height < offset) {
|
||||
return createResponse(Status::CODE_400, "'start' height cannot be less than 'offset'");
|
||||
}
|
||||
|
||||
uint64_t current_height = start_height - offset;
|
||||
|
||||
if (limit == 1) {
|
||||
currency::block_rpc_extended_info rpc_details;
|
||||
if (!m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(current_height, rpc_details)) {
|
||||
return createResponse(Status::CODE_404, "Block not found at specified height");
|
||||
}
|
||||
return createDtoResponse(Status::CODE_200, populateBlockDetailsModel(rpc_details));
|
||||
}
|
||||
|
||||
auto block_list = oatpp::List<oatpp::Object<BlockDetailsModel>>::createShared();
|
||||
for(uint64_t i = 0; i < limit; ++i)
|
||||
{
|
||||
if(current_height < i)
|
||||
{
|
||||
break; // Reached genesis
|
||||
}
|
||||
uint64_t height_to_fetch = current_height - i;
|
||||
currency::block_rpc_extended_info rpc_details;
|
||||
if(m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(height_to_fetch, rpc_details))
|
||||
{
|
||||
block_list->push_back(populateBlockDetailsModel(rpc_details));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Could be that we requested past genesis, or a block is missing for some reason.
|
||||
// We'll just stop here.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return createDtoResponse(Status::CODE_200, block_list);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
|||
122
src/api/controller/path/block/identifier.hpp
Normal file
122
src/api/controller/path/block/identifier.hpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
// Copyright (c) 2017-2025 Lethean (https://lt.hn)
|
||||
//
|
||||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||
// You may obtain a copy of the licence at:
|
||||
//
|
||||
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
|
||||
//
|
||||
// The EUPL is a copyleft licence that is compatible with the MIT/X11
|
||||
// licence used by the original projects; but maintains OSS status,
|
||||
// where regional copyright law requires ownership to dictate licence terms.
|
||||
//
|
||||
// SPDX‑License‑Identifier: EUPL-1.2
|
||||
//
|
||||
|
||||
#ifndef BlockIdentifierController_hpp
|
||||
#define BlockIdentifierController_hpp
|
||||
|
||||
#include "modal/block/details.hpp"
|
||||
#include "modal/transaction/details.hpp"
|
||||
#include "controller/ApiCoreInfo.hpp"
|
||||
|
||||
#include "oatpp/web/server/api/ApiController.hpp"
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/parser/json/mapping/ObjectMapper.hpp"
|
||||
|
||||
#include "rpc/core_rpc_server_commands_defs.h"
|
||||
#include <string> // For std::string and std::stoull
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(ApiController)
|
||||
|
||||
/**
|
||||
* Block Identifier Controller
|
||||
* Acts as a proxy to fetch blocks by hash or by height (ID).
|
||||
*/
|
||||
class BlockIdentifierController : public oatpp::web::server::api::ApiController {
|
||||
private:
|
||||
OATPP_COMPONENT(std::shared_ptr<ApiCoreInfo>, m_core_info);
|
||||
public:
|
||||
explicit BlockIdentifierController(OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, objectMapper))
|
||||
: oatpp::web::server::api::ApiController(objectMapper)
|
||||
{}
|
||||
public:
|
||||
|
||||
ENDPOINT_INFO(getBlock) {
|
||||
info->summary = "Get a block by its hash or height (ID)";
|
||||
info->addTag("Block");
|
||||
info->pathParams["identifier"].description = "The hash (hex string) or height (integer) of the block to retrieve.";
|
||||
info->addResponse<Object<BlockDetailsModel>>(Status::CODE_200, "application/json");
|
||||
info->addResponse(Status::CODE_404, "text/plain");
|
||||
info->addResponse(Status::CODE_400, "text/plain");
|
||||
}
|
||||
ENDPOINT("GET", "/block/{identifier}", getBlock, PATH(String, identifier)) {
|
||||
|
||||
currency::block_rpc_extended_info rpc_details;
|
||||
bool block_found = false;
|
||||
|
||||
// Check if the identifier consists only of digits
|
||||
if (identifier->find_first_not_of("0123456789") == std::string::npos) {
|
||||
// It's a numeric ID (height)
|
||||
try {
|
||||
uint64_t height = std::stoull(identifier->c_str());
|
||||
block_found = m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(height, rpc_details);
|
||||
} catch (const std::exception& e) {
|
||||
return createResponse(Status::CODE_400, "Invalid block height format");
|
||||
}
|
||||
} else {
|
||||
// It's a hash
|
||||
crypto::hash block_hash{};
|
||||
if (!epee::string_tools::hex_to_pod(*identifier, block_hash)) {
|
||||
return createResponse(Status::CODE_400, "Invalid block hash format");
|
||||
}
|
||||
block_found = m_core_info->getCore().get_blockchain_storage().get_main_block_rpc_details(block_hash, rpc_details);
|
||||
}
|
||||
|
||||
if (!block_found) {
|
||||
return createResponse(Status::CODE_404, "Block not found");
|
||||
}
|
||||
|
||||
// Common logic to populate the DTO
|
||||
auto blockDetails = BlockDetailsModel::createShared();
|
||||
|
||||
blockDetails->id = rpc_details.id;
|
||||
blockDetails->height = rpc_details.height;
|
||||
blockDetails->timestamp = rpc_details.timestamp;
|
||||
blockDetails->actual_timestamp = rpc_details.actual_timestamp;
|
||||
blockDetails->difficulty = rpc_details.difficulty;
|
||||
blockDetails->prev_id = rpc_details.prev_id;
|
||||
blockDetails->is_orphan = rpc_details.is_orphan;
|
||||
blockDetails->base_reward = rpc_details.base_reward;
|
||||
blockDetails->summary_reward = rpc_details.summary_reward;
|
||||
blockDetails->total_fee = rpc_details.total_fee;
|
||||
blockDetails->penalty = rpc_details.penalty;
|
||||
blockDetails->already_generated_coins = rpc_details.already_generated_coins;
|
||||
blockDetails->block_cumulative_size = rpc_details.block_cumulative_size;
|
||||
blockDetails->total_txs_size = rpc_details.total_txs_size;
|
||||
blockDetails->cumulative_diff_adjusted = rpc_details.cumulative_diff_adjusted;
|
||||
blockDetails->cumulative_diff_precise = rpc_details.cumulative_diff_precise;
|
||||
blockDetails->blob = rpc_details.blob;
|
||||
blockDetails->miner_text_info = rpc_details.miner_text_info;
|
||||
blockDetails->type = rpc_details.type;
|
||||
|
||||
auto tx_details_list = oatpp::List<oatpp::Object<TransactionDetailsModel>>::createShared();
|
||||
for(const auto& tx_rpc_info : rpc_details.transactions_details) {
|
||||
auto tx_model = TransactionDetailsModel::createShared();
|
||||
tx_model->id = tx_rpc_info.id;
|
||||
tx_model->fee = tx_rpc_info.fee;
|
||||
tx_model->amount = tx_rpc_info.amount;
|
||||
tx_model->blob_size = tx_rpc_info.blob_size;
|
||||
tx_model->keeper_block = tx_rpc_info.keeper_block;
|
||||
tx_model->timestamp = tx_rpc_info.timestamp;
|
||||
tx_details_list->push_back(tx_model);
|
||||
}
|
||||
blockDetails->transactions_details = tx_details_list;
|
||||
|
||||
return createDtoResponse(Status::CODE_200, blockDetails);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#include OATPP_CODEGEN_END(ApiController)
|
||||
|
||||
#endif /* BlockIdentifierController_hpp */
|
||||
25
src/api/modal/meta/page.hpp
Normal file
25
src/api/modal/meta/page.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
#ifndef PAGEDTO_HPP
|
||||
#define PAGEDTO_HPP
|
||||
|
||||
#include "oatpp/core/macro/codegen.hpp"
|
||||
#include "oatpp/core/Types.hpp"
|
||||
|
||||
#include OATPP_CODEGEN_BEGIN(DTO)
|
||||
|
||||
template<class T>
|
||||
class PageDto : public oatpp::DTO {
|
||||
|
||||
DTO_INIT(PageDto, DTO);
|
||||
|
||||
DTO_FIELD(UInt32, offset);
|
||||
DTO_FIELD(UInt32, limit);
|
||||
DTO_FIELD(UInt32, count);
|
||||
DTO_FIELD(Vector<T>, items);
|
||||
|
||||
};
|
||||
|
||||
|
||||
#include OATPP_CODEGEN_END(DTO)
|
||||
|
||||
#endif //PAGEDTO_HPP
|
||||
Loading…
Add table
Reference in a new issue