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) => {