diff --git a/src/common/constant.ts b/src/common/constant.ts index 50972d7f9..20d367e7d 100644 --- a/src/common/constant.ts +++ b/src/common/constant.ts @@ -324,6 +324,14 @@ export const SERVICE = { key: 'CrawlIBCIcs20Service', name: 'v1.CrawlIBCIcs20Service', }, + HoroscopeHandlerService: { + key: 'HoroscopeHandlerService', + path: 'v1.HoroscopeHandlerService', + getData: { + key: 'getData', + path: 'v1.HoroscopeHandlerService.getData', + }, + }, }, }; diff --git a/src/services/api-gateways/api_gateway.service.ts b/src/services/api-gateways/api_gateway.service.ts index d706e29b7..2e0a196c3 100644 --- a/src/services/api-gateways/api_gateway.service.ts +++ b/src/services/api-gateways/api_gateway.service.ts @@ -10,6 +10,7 @@ import ApiGateway from 'moleculer-web'; import { ServiceBroker } from 'moleculer'; import { Service } from '@ourparentcenter/moleculer-decorators-extended'; + import BaseService from '../../base/base.service'; import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; @@ -40,6 +41,12 @@ import { bullBoardMixin } from '../../mixins/bullBoard/bullBoard.mixin'; 'v1.job.redecode-tx', ], }, + { + path: '/auth', + autoAliases: true, + mappingPolicy: 'restrict', + whitelist: ['v1.horoscope-handler.getDataByChainId'], + }, ], // empty cors object will have moleculer to generate handler for preflight request and CORS header which allow all origin cors: {}, diff --git a/src/services/api-gateways/horoscope_handler.service.ts b/src/services/api-gateways/horoscope_handler.service.ts new file mode 100644 index 000000000..feeeaec09 --- /dev/null +++ b/src/services/api-gateways/horoscope_handler.service.ts @@ -0,0 +1,48 @@ +import { Post, Service } from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import { SERVICE } from '../../common'; +import BaseService from '../../base/base.service'; +import networks from '../../../network.json' assert { type: 'json' }; + +@Service({ + name: 'horoscope-handler', + version: 1, +}) +export default class HoroscopeHandlerService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Post('/getDataByChainId', { + name: 'getDataByChainId', + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + startBlock: { + type: 'number', + optional: false, + }, + endBlock: { + type: 'number', + optional: false, + }, + }, + }) + async getDataByChainId( + ctx: Context< + { chainid: string; startBlock: number; endBlock: number }, + Record + > + ) { + const selectedChain = networks.find( + (network) => network.chainId === ctx.params.chainid + ); + return this.broker.call( + `${SERVICE.V1.HoroscopeHandlerService.getData.path}@${selectedChain?.moleculerNamespace}`, + ctx.params + ); + } +} diff --git a/src/services/horoscope-handler/handler.service.ts b/src/services/horoscope-handler/handler.service.ts new file mode 100644 index 000000000..01f0a18a9 --- /dev/null +++ b/src/services/horoscope-handler/handler.service.ts @@ -0,0 +1,121 @@ +import { + Action, + Service, +} from '@ourparentcenter/moleculer-decorators-extended'; +import { Context, ServiceBroker } from 'moleculer'; +import _ from 'lodash'; +import BaseService from '../../base/base.service'; +import { SERVICE } from '../../common'; +import { Block, Transaction, Event } from '../../models'; +import networks from '../../../network.json' assert { type: 'json' }; + +@Service({ + name: SERVICE.V1.HoroscopeHandlerService.key, + version: 1, +}) +export default class HoroscopeHandlerService extends BaseService { + public constructor(public broker: ServiceBroker) { + super(broker); + } + + @Action({ + name: SERVICE.V1.HoroscopeHandlerService.getData.key, + params: { + chainid: { + type: 'string', + optional: false, + enum: networks.map((network) => network.chainId), + }, + startBlock: { + type: 'number', + optional: false, + }, + endBlock: { + type: 'number', + optional: false, + }, + }, + }) + public async getData( + ctx: Context< + { chainid: string; startBlock: number; endBlock: number }, + Record + > + ) { + // query to get data + const { startBlock, endBlock } = ctx.params; + const queryBlock = Block.query() + .select('height', 'hash', 'time') + .where('height', '>=', startBlock) + .andWhere('height', '<', endBlock) + .orderBy('height', 'asc'); + + const queryEventBlock = Event.query() + .select('type', 'source', 'block_height as height') + .withGraphFetched('attributes') + .modifyGraph('attributes', (builder) => { + builder.select('key', 'value'); + }) + .whereIn('source', [ + Event.SOURCE.BEGIN_BLOCK_EVENT, + Event.SOURCE.END_BLOCK_EVENT, + ]) + .andWhere('block_height', '>=', startBlock) + .andWhere('block_height', '<', endBlock) + .orderBy('block_height') + .orderBy('id'); + + const queryTransaction = Transaction.query() + .select('height', 'hash', 'code', 'codespace', 'memo', 'index') + .withGraphFetched('messages') + .withGraphFetched('events.[attributes]') + .modifyGraph('messages', (builder) => { + builder + .select('type', 'sender', 'content', 'index') + .whereNull('parent_id'); + }) + .modifyGraph('events', (builder) => { + builder.select('type', 'source', 'tx_msg_index'); + }) + .modifyGraph('events.[attributes]', (builder) => { + builder.select('key', 'value'); + }) + .where('height', '>=', startBlock) + .andWhere('height', '<', endBlock) + .orderBy('height', 'asc') + .orderBy('index', 'asc'); + const [listBlock, listTransaction, listEventBlock] = await Promise.all([ + queryBlock, + queryTransaction, + queryEventBlock, + ]); + + const resultGroupBy = _.groupBy( + [...listBlock, ...listTransaction, ...listEventBlock], + 'height' + ); + const listData: any[] = []; + Object.keys(resultGroupBy).forEach((height) => { + if (resultGroupBy[height].length > 0) { + if (resultGroupBy[height][0] instanceof Block) { + const block: any = resultGroupBy[height].filter( + (e) => e instanceof Block + )[0]; + const eventBlocks = resultGroupBy[height].filter( + (e) => e instanceof Event + ); + const txs = resultGroupBy[height].filter( + (e) => e instanceof Transaction + ); + block.events = eventBlocks.length === 0 ? [] : eventBlocks; + block.txs = txs.length === 0 ? [] : txs; + listData.push(block); + } else { + throw Error('Block not found'); + } + } + }); + // handler response + return listData; + } +}