import { Redis } from '@upstash/redis' import { headers } from 'next/headers' export interface RateLimitConfig { interval: number // in seconds limit: number } export interface RateLimitResult { success: boolean limit: number remaining: number reset: number // timestamp in seconds } export class RateLimit { private redis: Redis private prefix: string constructor() { this.redis = Redis.fromEnv() this.prefix = 'ratelimit' } private async getIP(): Promise { try { const headersList = await headers() const forwardedFor = headersList.get('x-forwarded-for') const realIP = headersList.get('x-real-ip') return forwardedFor?.split(',')[0] || realIP || 'development' } catch (error) { console.warn('Failed to get IP address, using fallback') return 'development' } } private getKey(identifier: string): string { return `${this.prefix}:${identifier}` } async check(config: RateLimitConfig): Promise { try { const ip = await this.getIP() const key = this.getKey(ip) const now = Math.floor(Date.now() / 1000) const windowStart = now - (now % config.interval) const windowKey = `${key}:${windowStart}` const pipeline = this.redis.pipeline() pipeline.incr(windowKey) pipeline.expire(windowKey, config.interval) const [count] = await pipeline.exec() const remaining = Math.max(0, config.limit - (count as number)) return { success: (count as number) <= config.limit, limit: config.limit, remaining, reset: windowStart + config.interval } } catch (error) { console.warn('Redis unavailable, allowing request (rate limiting disabled):', error) // Fallback: allow the request when Redis is unavailable // TODO: Fix Redis connection or create new instance return { success: true, limit: config.limit, remaining: config.limit - 1, reset: Math.floor(Date.now() / 1000) + config.interval } } } } // Default configuration const DEFAULT_CONFIG: RateLimitConfig = { interval: 60 * 60, // 1 hour limit: 5 // 5 requests per hour } // Helper function for easy usage export async function rateLimit( config: RateLimitConfig = DEFAULT_CONFIG ): Promise { const limiter = new RateLimit() return limiter.check(config) }