spb/llmindex Public
The discriminative, contamination-resistant, fully transparent LLM ranking — updated live.
TypeScript 77.9%
TeX 15.2%
Python 3.7%
SQL 1.4%
JavaScript 1.1%
Shell 0.5%
1/**2 * llmindex.io — Redis fixed-window rate limiting for /api/v13 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * License: Proprietary — © Simon-Pierre Boucher, all rights reserved6 *7 * 60 req/min anonymous, 600 req/min with a valid API key (§8).8 * Fails open if Redis is unavailable (availability over strictness).9 */10import 'server-only';11import Redis from 'ioredis';12import { NextResponse, type NextRequest } from 'next/server';1314const ANON_LIMIT = 60;15const KEYED_LIMIT = 600;16const WINDOW_S = 60;1718let redis: Redis | null | undefined;1920function getRedis(): Redis | null {21 if (redis !== undefined) return redis;22 const url = process.env.REDIS_URL;23 if (!url) {24 redis = null;25 return redis;26 }27 redis = new Redis(url, { maxRetriesPerRequest: 1, lazyConnect: false, enableOfflineQueue: false });28 redis.on('error', () => {29 /* logged by ioredis once; keep the API failing open */30 });31 return redis;32}3334function validApiKey(req: NextRequest): boolean {35 const key = req.headers.get('x-api-key');36 if (!key) return false;37 const configured = (process.env.API_KEYS ?? '').split(',').map((k) => k.trim()).filter(Boolean);38 return configured.includes(key);39}4041/** Returns a 429 response when over limit, otherwise null. */42export async function rateLimit(req: NextRequest): Promise<NextResponse | null> {43 const client = getRedis();44 if (!client) return null;45 const keyed = validApiKey(req);46 const limit = keyed ? KEYED_LIMIT : ANON_LIMIT;47 const ip =48 req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ?? req.headers.get('x-real-ip') ?? 'unknown';49 const identity = keyed ? `key:${req.headers.get('x-api-key')}` : `ip:${ip}`;50 const windowKey = `rl:${identity}:${Math.floor(Date.now() / 1000 / WINDOW_S)}`;51 try {52 const count = await client.incr(windowKey);53 if (count === 1) await client.expire(windowKey, WINDOW_S + 5);54 if (count > limit) {55 const retryAfter = WINDOW_S - (Math.floor(Date.now() / 1000) % WINDOW_S);56 return NextResponse.json(57 { error: 'rate_limited', limit, window_seconds: WINDOW_S },58 { status: 429, headers: { 'Retry-After': String(retryAfter) } },59 );60 }61 } catch {62 return null; // fail open63 }64 return null;65}66