forked from SmartDropLabs/smartdrop-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindexer.js
More file actions
116 lines (102 loc) · 4.26 KB
/
Copy pathindexer.js
File metadata and controls
116 lines (102 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const express = require('express');
const eventStore = require('../indexer/eventStore');
const indexerPoller = require('../indexer/runtime');
const logger = require('../logger');
const AppError = require('../errors/AppError');
const buildRateLimit = require('../middleware/rateLimit');
const { parsePagination, paginateResponse } = require('../utils/paginate');
const router = express.Router();
function isValidId(value) {
return typeof value === 'string' && /^[A-Za-z0-9:_-]{1,128}$/.test(value);
}
function isValidAddress(value) {
return typeof value === 'string' && /^[A-Z0-9]{10,80}$/.test(value);
}
router.get('/airdrops/:id/status', async (req, res, next) => {
try {
if (!isValidId(req.params.id)) {
return next(new AppError('VALIDATION_ERROR', 'Invalid airdrop id', 400, { param: 'id' }));
}
const status = await eventStore.getAirdropStatus(req.params.id);
if (!status) {
return next(new AppError('AIRDROP_NOT_INDEXED', 'Airdrop has not been indexed', 404, {
airdrop_id: req.params.id,
}));
}
return res.json(status);
} catch (err) {
logger.error('Airdrop status lookup failed', { error: err.message });
return next(err);
}
});
// Named distinctly from airdrops.js's own `/airdrops/:id/recipients` (the
// stored/intended recipient list): this returns recipients derived from
// indexed on-chain claim events, a different source of truth. The two
// routers previously registered the exact same path, and since this
// router is mounted first in src/index.js, it silently shadowed the real
// listRecipients handler in airdrops.js on every request.
// getAirdropRecipients/getRecipientClaims below fully materialize their
// list from a single Redis key regardless (see eventStore.js's
// getJsonList) — there's no server-side "fetch only a page" available at
// the storage layer, so pagination here is a slice of the already-fetched
// array plus the canonical envelope, not a more efficient query (#131).
router.get('/airdrops/:id/onchain-recipients', async (req, res, next) => {
try {
if (!isValidId(req.params.id)) {
return next(new AppError('VALIDATION_ERROR', 'Invalid airdrop id', 400, { param: 'id' }));
}
const allRecipients = await eventStore.getAirdropRecipients(req.params.id);
const { page, limit } = parsePagination(req.query);
const start = (page - 1) * limit;
const pageRecipients = allRecipients.slice(start, start + limit);
return res.json(paginateResponse(pageRecipients, allRecipients.length, { page, limit }));
} catch (err) {
logger.error('Airdrop recipients lookup failed', { error: err.message });
return next(err);
}
});
router.get('/recipients/:address/claims', async (req, res, next) => {
try {
if (!isValidAddress(req.params.address)) {
return next(new AppError('VALIDATION_ERROR', 'Invalid recipient address', 400, {
param: 'address',
}));
}
const allClaims = await eventStore.getRecipientClaims(req.params.address);
const { page, limit } = parsePagination(req.query);
const start = (page - 1) * limit;
const pageClaims = allClaims.slice(start, start + limit);
return res.json(paginateResponse(pageClaims, allClaims.length, { page, limit }));
} catch (err) {
logger.error('Recipient claims lookup failed', { error: err.message });
return next(err);
}
});
const indexerStatusLimit = buildRateLimit({
windowSeconds: 1,
max: 1,
keyPrefix: 'indexer-status',
});
router.get('/indexer/status', indexerStatusLimit, async (_req, res, next) => {
try {
const stats = await eventStore.getStats();
const poller = indexerPoller.getStatus();
const hasLatestLedger = poller.latest_ledger !== null && poller.latest_ledger !== undefined;
const latestLedger = Number(poller.latest_ledger);
const lastLedger = Number(stats.last_ledger);
const ledgerLag = hasLatestLedger && Number.isFinite(latestLedger) && Number.isFinite(lastLedger)
? Math.max(0, latestLedger - lastLedger)
: null;
return res.json({
...poller,
last_ledger: stats.last_ledger,
events_count: stats.events_count,
lag: ledgerLag,
ledger_lag: ledgerLag,
});
} catch (err) {
logger.error('Indexer status lookup failed', { error: err.message });
return next(err);
}
});
module.exports = router;