diff --git a/README.md b/README.md index 0a55dc88..9be909a1 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ Base URL: http://stats.filspark.com/ http://stats.filspark.com/participants/top-measurements?from=yesterday&to=yesterday +- `GET /participants/summary` + + http://stats.filspark.com/participants/summary + - `GET /participant/:address/scheduled-rewards?address=
&from=&to=` http://stats.filspark.com/participant/0x000000000000000000000000000000000000dEaD/scheduled-rewards diff --git a/stats/lib/platform-routes.js b/stats/lib/platform-routes.js index a5fbd28a..3bb17a09 100644 --- a/stats/lib/platform-routes.js +++ b/stats/lib/platform-routes.js @@ -1,3 +1,4 @@ +import { json } from 'http-responders' import { getStatsWithFilterAndCaching } from './request-helpers.js' import { fetchDailyStationCount, @@ -5,7 +6,8 @@ import { fetchDailyRewardTransfers, fetchTopEarningParticipants, fetchParticipantsWithTopMeasurements, - fetchDailyStationMeasurementCounts + fetchDailyStationMeasurementCounts, + fetchParticipantsSummary } from './platform-stats-fetchers.js' const createRespondWithFetchFn = (pathname, searchParams, res) => (pgPool, fetchFn) => { @@ -36,6 +38,8 @@ export const handlePlatformRoutes = async (req, res, pgPools) => { await respond(pgPools.evaluate, fetchParticipantsWithTopMeasurements) } else if (req.method === 'GET' && url === '/participants/top-earning') { await respond(pgPools.stats, fetchTopEarningParticipants) + } else if (req.method === 'GET' && url === '/participants/summary') { + await respondWithParticipantsSummary(res, pgPools) } else if (req.method === 'GET' && url === '/transfers/daily') { await respond(pgPools.stats, fetchDailyRewardTransfers) } else { @@ -43,3 +47,8 @@ export const handlePlatformRoutes = async (req, res, pgPools) => { } return true } + +export const respondWithParticipantsSummary = async (res, pgPools) => { + res.setHeader('cache-control', `public, max-age=${24 * 3600 /* one day */}`) + json(res, await fetchParticipantsSummary(pgPools.evaluate)) +} diff --git a/stats/lib/platform-stats-fetchers.js b/stats/lib/platform-stats-fetchers.js index a154be5b..2efa5651 100644 --- a/stats/lib/platform-stats-fetchers.js +++ b/stats/lib/platform-stats-fetchers.js @@ -104,3 +104,15 @@ export const fetchTopEarningParticipants = async (pgPool, filter) => { `, [filter.from, filter.to]) return rows } + +/** + * @param {Queryable} pgPool + */ +export const fetchParticipantsSummary = async (pgPool) => { + const { rows } = await pgPool.query(` + SELECT COUNT(DISTINCT participant_id) FROM daily_participants + `) + return { + participant_count: Number(rows[0].count) + } +} diff --git a/stats/test/platform-routes.test.js b/stats/test/platform-routes.test.js index 919d3db7..4fba293b 100644 --- a/stats/test/platform-routes.test.js +++ b/stats/test/platform-routes.test.js @@ -7,6 +7,7 @@ import { getPgPools } from '@filecoin-station/spark-stats-db' import { assertResponseStatus, getPort } from './test-helpers.js' import { createHandler } from '../lib/handler.js' import { getLocalDayAsISOString, today, yesterday } from '../lib/request-helpers.js' +import { givenDailyParticipants } from '@filecoin-station/spark-stats-db/test-helpers.js' const debug = createDebug('test') @@ -338,6 +339,29 @@ describe('Platform Routes HTTP request handler', () => { await assertResponseStatus(res, 400) }) }) + + describe('GET /participants/summary', () => { + it('counts participants', async () => { + await givenDailyParticipants( + pgPools.evaluate, + '2000-01-01', + ['0x1', '0x2', '0x3'] + ) + + const res = await fetch( + new URL('/participants/summary', baseUrl), { + redirect: 'manual' + } + ) + await assertResponseStatus(res, 200) + const summary = await res.json() + assert.deepStrictEqual(summary, { participant_count: 3 }) + assert.strictEqual( + res.headers.get('cache-control'), + 'public, max-age=86400' + ) + }) + }) }) const givenDailyMeasurementsSummary = async (pgPoolEvaluate, summaryData) => {