phase-8g: rate limiting and update readme doc

This commit is contained in:
Sai Kiran Vella
2026-06-05 23:02:21 -04:00
committed by saikiranvella
parent 5af9ded35e
commit d1556f2a67
7 changed files with 320 additions and 184 deletions
+22 -1
View File
@@ -1,5 +1,6 @@
import Fastify from 'fastify';
import Fastify, { type FastifyRequest, type FastifyReply } from 'fastify';
import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit';
import { ScreenerController } from './controllers/screener.controller';
import { FinanceController } from './controllers/finance.controller';
import { CallsController } from './controllers/calls.controller';
@@ -31,6 +32,26 @@ export async function buildApp({ logger = true }: BuildAppOptions = {}) {
origin: process.env.CLIENT_ORIGIN ?? 'http://localhost:5173',
});
// ── Rate limiting — applied globally, tightest on expensive routes ───────
await app.register(rateLimit, {
global: false, // opt-in per route via config.rateLimit
max: 60,
timeWindow: '1 minute',
});
// ── API key auth — only enforced when API_KEY env var is set ─────────────
const API_KEY = process.env.API_KEY;
if (API_KEY) {
app.addHook('onRequest', async (req: FastifyRequest, reply: FastifyReply) => {
// Skip auth for health check and OPTIONS preflight
if (req.url === '/health' || req.method === 'OPTIONS') return;
const header = req.headers['authorization'] ?? '';
if (header !== `Bearer ${API_KEY}`) {
return reply.code(401).send({ error: 'Unauthorized' });
}
});
}
const yahoo = new YahooFinanceClient();
const benchmark = new BenchmarkProvider(yahoo, { logger: noopLogger });
const engine = new ScreenerEngine(yahoo, benchmark, { logger: noopLogger });
+5 -1
View File
@@ -10,7 +10,11 @@ export class AnalyzeController {
) {}
register(app: FastifyInstance): void {
app.post('/api/analyze', { schema: analyzeSchema }, this.analyze.bind(this));
app.post(
'/api/analyze',
{ schema: analyzeSchema, config: { rateLimit: { max: 10, timeWindow: '1 minute' } } },
this.analyze.bind(this),
);
}
private async analyze(req: FastifyRequest, reply: FastifyReply) {
+10 -2
View File
@@ -8,8 +8,16 @@ export class ScreenerController {
constructor(private readonly engine: ScreenerEngine) {}
register(app: FastifyInstance): void {
app.post('/api/screen', { schema: screenSchema }, this.screen.bind(this));
app.get('/api/screen/catalysts', this.catalysts.bind(this));
app.post(
'/api/screen',
{ schema: screenSchema, config: { rateLimit: { max: 10, timeWindow: '1 minute' } } },
this.screen.bind(this),
);
app.get(
'/api/screen/catalysts',
{ config: { rateLimit: { max: 10, timeWindow: '1 minute' } } },
this.catalysts.bind(this),
);
}
private static serializeAssets(arr: LiveAssetResult[]) {