1+ import { Redis } from '@upstash/redis'
2+ import { createClient , RedisClientType } from 'redis'
3+ import { NextResponse } from 'next/server'
4+ import type { NextRequest } from 'next/server'
5+
6+ let redisClient : Redis | RedisClientType | null = null
7+
8+ async function initializeRedisClient ( ) {
9+ if ( redisClient ) return redisClient
10+
11+ const useLocalRedis = process . env . USE_LOCAL_REDIS === 'true'
12+
13+ if ( useLocalRedis ) {
14+ const localRedisUrl = process . env . LOCAL_REDIS_URL || 'redis://localhost:6379'
15+ redisClient = createClient ( { url : localRedisUrl } ) as RedisClientType
16+ await redisClient . connect ( )
17+ } else {
18+ const upstashRedisRestUrl = process . env . UPSTASH_REDIS_REST_URL
19+ const upstashRedisRestToken = process . env . UPSTASH_REDIS_REST_TOKEN
20+
21+ if ( upstashRedisRestUrl && upstashRedisRestToken ) {
22+ redisClient = new Redis ( {
23+ url : upstashRedisRestUrl ,
24+ token : upstashRedisRestToken
25+ } )
26+ }
27+ }
28+
29+ return redisClient
30+ }
31+
32+ export async function checkRateLimits ( apiKey : string , limits : { perMinute : number } ) {
33+ const client = await initializeRedisClient ( )
34+ if ( ! client ) throw new Error ( 'Redis client not initialized' )
35+
36+ const now = Date . now ( )
37+ const windowKey = `ratelimit:${ apiKey } :${ Math . floor ( now / 60000 ) } ` // Key for current minute
38+
39+ let currentCount : number
40+
41+ if ( client instanceof Redis ) {
42+ currentCount = await client . incr ( windowKey )
43+ await client . expire ( windowKey , 60 )
44+ } else {
45+ const multi = client . multi ( )
46+ const results = await multi
47+ . incr ( windowKey )
48+ . expire ( windowKey , 60 )
49+ . exec ( )
50+ currentCount = ( results ?. [ 0 ] as number ) || 0
51+ }
52+
53+ if ( currentCount > limits . perMinute ) {
54+ throw new Error ( `Rate limit exceeded: ${ limits . perMinute } requests per minute` )
55+ }
56+
57+ return currentCount
58+ }
59+
60+ export async function withRateLimit (
61+ request : NextRequest ,
62+ handler : ( request : NextRequest ) => Promise < NextResponse > ,
63+ limits : { perMinute : number }
64+ ) {
65+ const apiKey = request . headers . get ( 'x-api-key' )
66+ if ( ! apiKey ) {
67+ return NextResponse . json ( { error : 'Missing API key' } , { status : 401 } )
68+ }
69+
70+ try {
71+ await checkRateLimits ( apiKey , limits )
72+ return await handler ( request )
73+ } catch ( error ) {
74+ return NextResponse . json ( { error : 'Rate limit exceeded' } , { status : 429 } )
75+ }
76+ }
0 commit comments