diff --git a/backend/src/api/mining.ts b/backend/src/api/mining.ts index c89ea9324..7431dc0b3 100644 --- a/backend/src/api/mining.ts +++ b/backend/src/api/mining.ts @@ -7,23 +7,26 @@ class Mining { constructor() { } + private getSqlInterval(interval: string | null): string | null { + switch (interval) { + case '24h': return '1 DAY'; + case '3d': return '3 DAY'; + case '1w': return '1 WEEK'; + case '1m': return '1 MONTH'; + case '3m': return '3 MONTH'; + case '6m': return '6 MONTH'; + case '1y': return '1 YEAR'; + case '2y': return '2 YEAR'; + case '3y': return '3 YEAR'; + default: return null; + } + } + /** * Generate high level overview of the pool ranks and general stats */ public async $getPoolsStats(interval: string | null) : Promise { - let sqlInterval: string | null = null; - switch (interval) { - case '24h': sqlInterval = '1 DAY'; break; - case '3d': sqlInterval = '3 DAY'; break; - case '1w': sqlInterval = '1 WEEK'; break; - case '1m': sqlInterval = '1 MONTH'; break; - case '3m': sqlInterval = '3 MONTH'; break; - case '6m': sqlInterval = '6 MONTH'; break; - case '1y': sqlInterval = '1 YEAR'; break; - case '2y': sqlInterval = '2 YEAR'; break; - case '3y': sqlInterval = '3 YEAR'; break; - default: sqlInterval = null; break; - } + const sqlInterval = this.getSqlInterval(interval); const poolsStatistics = {}; @@ -64,6 +67,24 @@ class Mining { return poolsStatistics; } + + /** + * Get all mining pool stats for a pool + */ + public async $getPoolStat(interval: string | null, poolId: number): Promise { + const pool = await PoolsRepository.$getPool(poolId); + if (!pool) { + throw new Error("This mining pool does not exist"); + } + + const sqlInterval = this.getSqlInterval(interval); + const blocks = await BlocksRepository.$getBlocksByPool(sqlInterval, poolId); + + return { + pool: pool, + blocks: blocks, + }; + } } export default new Mining(); diff --git a/backend/src/index.ts b/backend/src/index.ts index 07808c98a..b8ca55339 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -256,6 +256,14 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/1y', routes.$getStatisticsByTime.bind(routes, '1y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/2y', routes.$getStatisticsByTime.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/3y', routes.$getStatisticsByTime.bind(routes, '3y')) + ; + } + + const indexingAvailable = + ['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && + config.DATABASE.ENABLED === true; + if (indexingAvailable) { + this.app .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/24h', routes.$getPools.bind(routes, '24h')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3d', routes.$getPools.bind(routes, '3d')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/1w', routes.$getPools.bind(routes, '1w')) @@ -266,7 +274,8 @@ class Server { .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/2y', routes.$getPools.bind(routes, '2y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/3y', routes.$getPools.bind(routes, '3y')) .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pools/all', routes.$getPools.bind(routes, 'all')) - ; + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId', routes.$getPool) + .get(config.MEMPOOL.API_URL_PREFIX + 'mining/pool/:poolId/:interval', routes.$getPool); } if (config.BISQ.ENABLED) { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index 18023760f..03545f730 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -135,6 +135,23 @@ class BlocksRepository { return rows[0].blockTimestamp; } + /** + * Get blocks mined by a specific mining pool + */ + public async $getBlocksByPool(interval: string | null, poolId: number): Promise { + const query = ` + SELECT * + FROM blocks + WHERE pool_id = ${poolId}` + + (interval != null ? ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()` : ``); + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows; + } + /** * Get one block by height */ @@ -156,4 +173,4 @@ class BlocksRepository { } } -export default new BlocksRepository(); \ No newline at end of file +export default new BlocksRepository(); diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index b89725452..50f5268a7 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -41,6 +41,23 @@ class PoolsRepository { return rows; } + + /** + * Get mining pool statistics for one pool + */ + public async $getPool(poolId: number) : Promise { + const query = ` + SELECT * + FROM pools + WHERE pools.id = ${poolId} + `; + + const connection = await DB.pool.getConnection(); + const [rows] = await connection.query(query); + connection.release(); + + return rows[0]; + } } export default new PoolsRepository(); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index e06177ddd..6811f9190 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -22,6 +22,8 @@ import elementsParser from './api/liquid/elements-parser'; import icons from './api/liquid/icons'; import miningStats from './api/mining'; import axios from 'axios'; +import PoolsRepository from './repositories/PoolsRepository'; +import mining from './api/mining'; class Routes { constructor() {} @@ -533,6 +535,19 @@ class Routes { } } + public async $getPool(req: Request, res: Response) { + try { + const poolId = parseInt(req.params.poolId); + const stats = await mining.$getPoolStat(req.params.interval ?? null, poolId); + res.header('Pragma', 'public'); + res.header('Cache-control', 'public'); + res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString()); + res.json(stats); + } catch (e) { + res.status(500).send(e instanceof Error ? e.message : e); + } + } + public async $getPools(interval: string, req: Request, res: Response) { try { let stats = await miningStats.$getPoolsStats(interval);