forked from SmartDropLabs/smartdrop-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
157 lines (140 loc) · 4.96 KB
/
Copy pathindex.js
File metadata and controls
157 lines (140 loc) · 4.96 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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
'use strict';
const express = require('express');
const helmet = require('helmet');
const config = require('./config');
const logger = require('./logger');
const cache = require('./services/cache');
const priceOracle = require('./services/priceOracle');
const priceRefreshJob = require('./jobs/priceRefresh');
const webhookRetryWorker = require('./jobs/webhookRetryWorker');
const airdropExpiryJob = require('./jobs/airdropExpiry');
const { warmCache } = require('./startup/cacheWarm');
const buildCorsMiddleware = require('./middleware/cors');
const buildRateLimit = require('./middleware/rateLimit');
const { requestIdMiddleware } = require('./middleware/requestId');
const { requireApiKey } = require('./middleware/auth');
const { errorHandler, notFoundHandler } = require('./middleware/errorHandler');
const pricesRouter = require('./routes/prices');
const alertsRouter = require('./routes/alerts');
const keysRouter = require('./routes/keys');
const webhooksRouter = require('./routes/webhooks');
const airdropsRouter = require('./routes/airdrops');
const apiDocsRouter = require('./routes/apiDocs');
const priceWebSocket = require('./ws/priceWebSocket');
const app = express();
let server = {
close(callback) {
if (callback) callback();
},
};
app.use(requestIdMiddleware);
app.use(helmet());
app.use(buildCorsMiddleware(config.corsAllowedOrigins));
app.use(express.json({ limit: config.airdrops.jsonMaxBytes }));
app.get('/health', (req, res) => {
const redisConnected = cache.isConnected();
const priceRefreshHealth = priceRefreshJob.getHealth();
const webhookWorkerHealth = webhookRetryWorker.getHealth();
// Compute overall status:
// unhealthy – Redis is down, or a job is stalled past its grace period
// degraded – a job has not yet run but is still within its startup grace period
// ok – all dependencies healthy
let status = 'ok';
if (!redisConnected || !priceRefreshHealth.healthy || !webhookWorkerHealth.healthy) {
// Distinguish between "never started" (degraded) vs outright stalled/down (unhealthy)
const jobsDegraded =
(!priceRefreshHealth.healthy && !priceRefreshHealth.stalled) ||
(!webhookWorkerHealth.healthy && !webhookWorkerHealth.stalled);
status = (!redisConnected || priceRefreshHealth.stalled || webhookWorkerHealth.stalled)
? 'unhealthy'
: jobsDegraded ? 'degraded' : 'unhealthy';
}
res.json({
status,
timestamp: new Date().toISOString(),
redis: {
connected: redisConnected,
},
jobs: {
price_refresh: {
healthy: priceRefreshHealth.healthy,
last_success_at: priceRefreshHealth.lastSuccessAt
? new Date(priceRefreshHealth.lastSuccessAt).toISOString()
: null,
last_error: priceRefreshHealth.lastError,
stalled: priceRefreshHealth.stalled,
},
webhook_retry_worker: {
healthy: webhookWorkerHealth.healthy,
last_success_at: webhookWorkerHealth.lastSuccessAt
? new Date(webhookWorkerHealth.lastSuccessAt).toISOString()
: null,
last_error: webhookWorkerHealth.lastError,
stalled: webhookWorkerHealth.stalled,
},
},
database: {
configured: true,
checked: false,
status: 'unused',
},
price_source_circuits: priceOracle.getSourceCircuitStates(),
});
});
const globalApiLimit = buildRateLimit({
windowSeconds: Math.floor(config.rateLimit.windowMs / 1000),
max: config.rateLimit.max,
keyPrefix: 'api',
});
app.use('/api/v1', globalApiLimit);
app.use('/api/v1', pricesRouter);
app.use('/api/v1', keysRouter);
app.use('/api/v1/alerts', requireApiKey());
app.use('/api/v1', alertsRouter);
app.use('/api/v1', webhooksRouter);
app.use('/api/v1', airdropsRouter);
app.use('/api-docs', globalApiLimit);
app.use('/api-docs', apiDocsRouter);
app.use(notFoundHandler);
app.use(errorHandler);
function shutdown(signal) {
return async () => {
logger.info(`${signal} received, shutting down`);
priceRefreshJob.stop();
webhookRetryWorker.stop();
airdropExpiryJob.stop();
require('./ws/PriceSubscriptionManager').stopHeartbeat();
if (server) server.close();
await cache.disconnect();
process.exit(0);
};
}
if (require.main === module) {
startServer().catch((err) => {
logger.error('Startup failed', { error: err.message });
process.exit(1);
});
process.on('SIGTERM', shutdown('SIGTERM'));
process.on('SIGINT', shutdown('SIGINT'));
}
async function startServer() {
await warmCache(config.watchedAssets);
server = app.listen(config.port, () => {
logger.info(`SmartDrop backend running on port ${config.port}`);
priceWebSocket.attach(server);
priceRefreshJob.start();
webhookRetryWorker.start();
airdropExpiryJob.start();
});
module.exports.server = server;
return server;
}
module.exports = { app, server };
module.exports = app;
module.exports.app = app;
module.exports.server = server || {
close(callback) {
if (callback) callback();
},
};
module.exports.startServer = startServer;