diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index ad552ffb1..1c439a755 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -240,4 +240,9 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/lightning/api/v1/channels/txids/', { params }); } + lightningSearch$(searchText: string): Observable { + let params = new HttpParams().set('searchText', searchText); + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/lightning/api/v1/search', { params }); + } + } diff --git a/lightning-backend/src/api/explorer/channels.api.ts b/lightning-backend/src/api/explorer/channels.api.ts index be2c953f5..8551a23bd 100644 --- a/lightning-backend/src/api/explorer/channels.api.ts +++ b/lightning-backend/src/api/explorer/channels.api.ts @@ -13,6 +13,18 @@ class ChannelsApi { } } + public async $searchChannelsById(search: string): Promise { + try { + const searchStripped = search.replace('%', '') + '%'; + const query = `SELECT id, short_id, capacity FROM channels WHERE id LIKE ? OR short_id LIKE ? LIMIT 10`; + const [rows]: any = await DB.query(query, [searchStripped, searchStripped]); + return rows; + } catch (e) { + logger.err('$searchChannelsById error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } + public async $getChannelsByStatus(status: number): Promise { try { const query = `SELECT * FROM channels WHERE status = ?`; @@ -46,11 +58,12 @@ class ChannelsApi { } } - public async $getChannelByTransactionId(transactionId: string): Promise { + public async $getChannelsByTransactionId(transactionIds: string[]): Promise { try { - const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key WHERE channels.transaction_id = ?`; - const [rows]: any = await DB.query(query, [transactionId]); - return rows[0]; + transactionIds = transactionIds.map((id) => '\'' + id + '\''); + const query = `SELECT n1.alias AS alias_left, n2.alias AS alias_right, channels.* FROM channels LEFT JOIN nodes AS n1 ON n1.public_key = channels.node1_public_key LEFT JOIN nodes AS n2 ON n2.public_key = channels.node2_public_key WHERE channels.transaction_id IN (${transactionIds.join(', ')})`; + const [rows]: any = await DB.query(query); + return rows; } catch (e) { logger.err('$getChannelByTransactionId error: ' + (e instanceof Error ? e.message : e)); throw e; diff --git a/lightning-backend/src/api/explorer/channels.routes.ts b/lightning-backend/src/api/explorer/channels.routes.ts index 07dabdd31..61b4846e1 100644 --- a/lightning-backend/src/api/explorer/channels.routes.ts +++ b/lightning-backend/src/api/explorer/channels.routes.ts @@ -8,11 +8,21 @@ class ChannelsRoutes { public initRoutes(app: Express) { app .get(config.MEMPOOL.API_URL_PREFIX + 'channels/txids', this.$getChannelsByTransactionIds) + .get(config.MEMPOOL.API_URL_PREFIX + 'channels/search/:search', this.$searchChannelsById) .get(config.MEMPOOL.API_URL_PREFIX + 'channels/:short_id', this.$getChannel) .get(config.MEMPOOL.API_URL_PREFIX + 'channels', this.$getChannels) ; } + private async $searchChannelsById(req: Request, res: Response) { + try { + const channels = await channelsApi.$searchChannelsById(req.params.search); + res.json(channels); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getChannel(req: Request, res: Response) { try { const channel = await channelsApi.$getChannel(req.params.short_id); @@ -32,8 +42,8 @@ class ChannelsRoutes { res.status(501).send('Missing parameter: public_key'); return; } - const channels = await channelsApi.$getChannelsForNode(req.query.public_key); - res.json(channels); + const channels = await channelsApi.$getChannelsForNode(req.query.public_key); + res.json(channels); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } @@ -51,12 +61,18 @@ class ChannelsRoutes { txIds.push(req.query.txId[_txId].toString()); } } - const channels: any[] = []; - for (const txId of txIds) { - const channel = await channelsApi.$getChannelByTransactionId(txId); - channels.push(channel); + const channels = await channelsApi.$getChannelsByTransactionId(txIds); + const result: any[] = []; + for (const txid of txIds) { + const foundChannel = channels.find((channel) => channel.transaction_id === txid); + if (foundChannel) { + result.push(foundChannel); + } else { + result.push(null); + } } - res.json(channels); + + res.json(result); } catch (e) { res.status(500).send(e instanceof Error ? e.message : e); } diff --git a/lightning-backend/src/api/explorer/general.routes.ts b/lightning-backend/src/api/explorer/general.routes.ts new file mode 100644 index 000000000..326602d55 --- /dev/null +++ b/lightning-backend/src/api/explorer/general.routes.ts @@ -0,0 +1,32 @@ +import config from '../../config'; +import { Express, Request, Response } from 'express'; +import nodesApi from './nodes.api'; +import channelsApi from './channels.api'; +class GeneralRoutes { + constructor() { } + + public initRoutes(app: Express) { + app + .get(config.MEMPOOL.API_URL_PREFIX + 'search', this.$searchNodesAndChannels) + ; + } + + private async $searchNodesAndChannels(req: Request, res: Response) { + if (typeof req.query.searchText !== 'string') { + res.status(501).send('Missing parameter: searchText'); + return; + } + try { + const nodes = await nodesApi.$searchNodeByPublicKeyOrAlias(req.query.searchText); + const channels = await channelsApi.$searchChannelsById(req.query.searchText); + res.json({ + nodes: nodes, + channels: channels, + }); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } +} + +export default new GeneralRoutes(); diff --git a/lightning-backend/src/api/explorer/nodes.api.ts b/lightning-backend/src/api/explorer/nodes.api.ts index 119336723..e8e18b082 100644 --- a/lightning-backend/src/api/explorer/nodes.api.ts +++ b/lightning-backend/src/api/explorer/nodes.api.ts @@ -59,6 +59,18 @@ class NodesApi { throw e; } } + + public async $searchNodeByPublicKeyOrAlias(search: string) { + try { + const searchStripped = search.replace('%', '') + '%'; + const query = `SELECT public_key, alias, color FROM nodes WHERE public_key LIKE ? OR alias LIKE ? LIMIT 10`; + const [rows]: any = await DB.query(query, [searchStripped, searchStripped]); + return rows; + } catch (e) { + logger.err('$searchNodeByPublicKeyOrAlias error: ' + (e instanceof Error ? e.message : e)); + throw e; + } + } } export default new NodesApi(); diff --git a/lightning-backend/src/api/explorer/nodes.routes.ts b/lightning-backend/src/api/explorer/nodes.routes.ts index 1b86abb69..e21617f45 100644 --- a/lightning-backend/src/api/explorer/nodes.routes.ts +++ b/lightning-backend/src/api/explorer/nodes.routes.ts @@ -7,12 +7,22 @@ class NodesRoutes { public initRoutes(app: Express) { app .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats) + .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/search/:search', this.$searchNode) .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes) .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key/statistics', this.$getHistoricalNodeStats) .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/:public_key', this.$getNode) ; } + private async $searchNode(req: Request, res: Response) { + try { + const nodes = await nodesApi.$searchNodeByPublicKeyOrAlias(req.params.search); + res.json(nodes); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + private async $getNode(req: Request, res: Response) { try { const node = await nodesApi.$getNode(req.params.public_key); diff --git a/lightning-backend/src/server.ts b/lightning-backend/src/server.ts index 954be4f0a..afee2e070 100644 --- a/lightning-backend/src/server.ts +++ b/lightning-backend/src/server.ts @@ -3,6 +3,7 @@ import * as express from 'express'; import * as http from 'http'; import logger from './logger'; import config from './config'; +import generalRoutes from './api/explorer/general.routes'; import nodesRoutes from './api/explorer/nodes.routes'; import channelsRoutes from './api/explorer/channels.routes'; @@ -30,6 +31,7 @@ class Server { } private initRoutes() { + generalRoutes.initRoutes(this.app); nodesRoutes.initRoutes(this.app); channelsRoutes.initRoutes(this.app); }