Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,66 @@ Response:
OK
```

### `GET /miner/:minerId/deals/eligible/summary`

Parameters:
- `minerId` - a miner id like `f0814049`

Response:

Number of deals grouped by client IDs.

```json
{
"minerId": "f0814049",
"dealCount": 13878,
"clients": [
{ "clientId": "f02516933", "dealCount": 6880 },
{ "clientId": "f02833886", "dealCount": 3126 }
]
}
```

### `GET /client/:clientId/deals/eligible/summary`

Parameters:
- `clientId` - a client id like `f0215074`

Response:

Number of deals grouped by miner IDs.

```json
{
"clientId": "f0215074",
"dealCount": 38977,
"providers": [
{ "minerId": "f01975316", "dealCount": 6810 },
{ "minerId": "f01975326", "dealCount": 6810 }
]
}
```

### `GET /allocator/:allocatorId/deals/eligible/summary`

Parameters:
- `allocatorId` - an allocator id like `f03015751`

Response:

Number of deals grouped by client IDs.

```json
{
"allocatorId": "f03015751",
"dealCount": 4088,
"clients": [
{ "clientId": "f03144229", "dealCount": 2488 },
{ "clientId": "f03150656", "dealCount": 1600 }
]
}
```

## Development

### Database
Expand Down
85 changes: 85 additions & 0 deletions api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ const handler = async (req, res, client, domain) => {
await getMeridianRoundDetails(req, res, client, segs[2], segs[3])
} else if (segs[0] === 'rounds' && req.method === 'GET') {
await getRoundDetails(req, res, client, segs[1])
} else if (segs[0] === 'miner' && segs[1] && segs[2] === 'deals' && segs[3] === 'eligible' && segs[4] === 'summary' && req.method === 'GET') {
await getSummaryOfEligibleDealsForMiner(req, res, client, segs[1])
} else if (segs[0] === 'client' && segs[1] && segs[2] === 'deals' && segs[3] === 'eligible' && segs[4] === 'summary' && req.method === 'GET') {
await getSummaryOfEligibleDealsForClient(req, res, client, segs[1])
} else if (segs[0] === 'allocator' && segs[1] && segs[2] === 'deals' && segs[3] === 'eligible' && segs[4] === 'summary' && req.method === 'GET') {
await getSummaryOfEligibleDealsForAllocator(req, res, client, segs[1])
} else if (segs[0] === 'inspect-request' && req.method === 'GET') {
await inspectRequest(req, res)
} else {
Expand Down Expand Up @@ -304,6 +310,85 @@ const redirect = (res, location) => {
res.end()
}

const getSummaryOfEligibleDealsForMiner = async (_req, res, client, minerId) => {
/** @type {{rows: {client_id: string; deal_count: number}[]}} */
const { rows } = await client.query(`
SELECT client_id, COUNT(cid)::INTEGER as deal_count FROM retrievable_deals
WHERE miner_id = $1 AND expires_at > now()
GROUP BY client_id
ORDER BY deal_count DESC, client_id ASC
`, [
minerId
])

// Cache the response for 6 hours
res.setHeader('cache-control', `max-age=${6 * 3600}`)

const body = {
minerId,
dealCount: rows.reduce((sum, row) => sum + row.deal_count, 0),
clients:
rows.map(
// eslint-disable-next-line camelcase
({ client_id, deal_count }) => ({ clientId: client_id, dealCount: deal_count })
)
}

json(res, body)
}

const getSummaryOfEligibleDealsForClient = async (_req, res, client, clientId) => {
/** @type {{rows: {miner_id: string; deal_count: number}[]}} */
const { rows } = await client.query(`
SELECT miner_id, COUNT(cid)::INTEGER as deal_count FROM retrievable_deals
WHERE client_id = $1 AND expires_at > now()
GROUP BY miner_id
ORDER BY deal_count DESC, miner_id ASC
`, [
clientId
])

// Cache the response for 6 hours
res.setHeader('cache-control', `max-age=${6 * 3600}`)

const body = {
clientId,
dealCount: rows.reduce((sum, row) => sum + row.deal_count, 0),
providers: rows.map(
// eslint-disable-next-line camelcase
({ miner_id, deal_count }) => ({ minerId: miner_id, dealCount: deal_count })
)
}
json(res, body)
}

const getSummaryOfEligibleDealsForAllocator = async (_req, res, client, allocatorId) => {
/** @type {{rows: {client_id: string; deal_count: number}[]}} */
const { rows } = await client.query(`
SELECT ac.client_id, COUNT(cid)::INTEGER as deal_count
FROM allocator_clients ac
LEFT JOIN retrievable_deals rd ON ac.client_id = rd.client_id
WHERE ac.allocator_id = $1 AND expires_at > now()
GROUP BY ac.client_id
ORDER BY deal_count DESC, ac.client_id ASC
`, [
allocatorId
])

// Cache the response for 6 hours
res.setHeader('cache-control', `max-age=${6 * 3600}`)

const body = {
allocatorId,
dealCount: rows.reduce((sum, row) => sum + row.deal_count, 0),
clients: rows.map(
// eslint-disable-next-line camelcase
({ client_id, deal_count }) => ({ clientId: client_id, dealCount: deal_count })
)
}
json(res, body)
}

export const inspectRequest = async (req, res) => {
await json(res, {
remoteAddress: req.socket.remoteAddress,
Expand Down
116 changes: 116 additions & 0 deletions api/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,4 +646,120 @@ describe('Routes', () => {
}
})
})

describe('summary of eligible deals', () => {
before(async () => {
await client.query(`
INSERT INTO retrievable_deals (cid, miner_id, client_id, expires_at)
VALUES
('bafyone', 'f0210', 'f0800', '2100-01-01'),
('bafyone', 'f0220', 'f0800', '2100-01-01'),
('bafytwo', 'f0220', 'f0810', '2100-01-01'),
('bafyone', 'f0230', 'f0800', '2100-01-01'),
('bafytwo', 'f0230', 'f0800', '2100-01-01'),
('bafythree', 'f0230', 'f0810', '2100-01-01'),
('bafyfour', 'f0230', 'f0820', '2100-01-01'),
('bafyexpired', 'f0230', 'f0800', '2020-01-01')
ON CONFLICT DO NOTHING
`)

await client.query(`
INSERT INTO allocator_clients (allocator_id, client_id)
VALUES
('f0500', 'f0800'),
('f0500', 'f0810'),
('f0520', 'f0820')
ON CONFLICT DO NOTHING
`)
})

describe('GET /miner/{id}/deals/eligible/summary', () => {
it('returns deal counts grouped by client id', async () => {
const res = await fetch(`${spark}/miner/f0230/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
minerId: 'f0230',
dealCount: 4,
clients: [
{ clientId: 'f0800', dealCount: 2 },
{ clientId: 'f0810', dealCount: 1 },
{ clientId: 'f0820', dealCount: 1 }
]
})
})

it('returns an empty array for miners with no deals in our DB', async () => {
const res = await fetch(`${spark}/miner/f0000/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
minerId: 'f0000',
dealCount: 0,
clients: []
})
})
})

describe('GET /client/{id}/deals/eligible/summary', () => {
it('returns deal counts grouped by miner id', async () => {
const res = await fetch(`${spark}/client/f0800/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
clientId: 'f0800',
dealCount: 4,
providers: [
{ minerId: 'f0230', dealCount: 2 },
{ minerId: 'f0210', dealCount: 1 },
{ minerId: 'f0220', dealCount: 1 }
]
})
})

it('returns an empty array for miners with no deals in our DB', async () => {
const res = await fetch(`${spark}/client/f0000/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
clientId: 'f0000',
dealCount: 0,
providers: []
})
})
})

describe('GET /allocator/{id}/deals/eligible/summary', () => {
it('returns deal counts grouped by client id', async () => {
const res = await fetch(`${spark}/allocator/f0500/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
allocatorId: 'f0500',
dealCount: 6,
clients: [
{ clientId: 'f0800', dealCount: 4 },
{ clientId: 'f0810', dealCount: 2 }
]
})
})

it('returns an empty array for miners with no deals in our DB', async () => {
const res = await fetch(`${spark}/allocator/f0000/deals/eligible/summary`)
await assertResponseStatus(res, 200)
assert.strictEqual(res.headers.get('cache-control'), 'max-age=21600')
const body = await res.json()
assert.deepStrictEqual(body, {
allocatorId: 'f0000',
dealCount: 0,
clients: []
})
})
})
})
})
Loading