import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { getClientAddress } from '@/lib/request'; const RATE_LIMIT_WINDOW = 60 * 1000; const MAX_REQUESTS = 100; const CLEANUP_THRESHOLD = 500; const requestMap = new Map(); let nextCleanup = Date.now() + RATE_LIMIT_WINDOW; function cleanupExpired(now: number) { if (requestMap.size <= CLEANUP_THRESHOLD && now < nextCleanup) return; for (const [key, value] of requestMap.entries()) { if (now > value.expires) { requestMap.delete(key); } } nextCleanup = now + RATE_LIMIT_WINDOW; } export function proxy(request: NextRequest) { const now = Date.now(); cleanupExpired(now); const ip = getClientAddress(request.headers); const record = requestMap.get(ip); if (!record || now > record.expires) { requestMap.set(ip, { count: 1, expires: now + RATE_LIMIT_WINDOW }); } else { record.count += 1; if (record.count > MAX_REQUESTS) { return new NextResponse('Too Many Requests', { status: 429, headers: { 'Retry-After': String(Math.ceil((record.expires - now) / 1000)), }, }); } } if (process.env.REQUEST_LOGS === 'true') { console.log(`[${new Date(now).toISOString()}] ${request.nextUrl.pathname} ${ip}`); } return NextResponse.next(); } export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)'], };