rate-limit.ts 1.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. import { Ratelimit } from "@upstash/ratelimit";
  2. import { Redis } from "@upstash/redis";
  3. // Lazy initialization to avoid errors when Redis env vars are not configured
  4. let _minuteRateLimit: Ratelimit | null = null;
  5. let _dailyRateLimit: Ratelimit | null = null;
  6. function getRedis(): Redis | null {
  7. const url = process.env.KV_REST_API_URL;
  8. const token = process.env.KV_REST_API_TOKEN;
  9. if (!url || !token) {
  10. return null;
  11. }
  12. return new Redis({ url, token });
  13. }
  14. // No-op rate limiter for when Redis is not configured
  15. const noopRateLimiter = {
  16. limit: async () => ({ success: true, limit: 0, remaining: 0, reset: 0 }),
  17. };
  18. const MINUTE_LIMIT = Number(process.env.RATE_LIMIT_PER_MINUTE) || 10;
  19. const DAILY_LIMIT = Number(process.env.RATE_LIMIT_PER_DAY) || 100;
  20. // Requests per minute (sliding window)
  21. export const minuteRateLimit = {
  22. limit: async (identifier: string) => {
  23. if (!_minuteRateLimit) {
  24. const redis = getRedis();
  25. if (!redis) return noopRateLimiter.limit();
  26. _minuteRateLimit = new Ratelimit({
  27. redis,
  28. limiter: Ratelimit.slidingWindow(MINUTE_LIMIT, "1 m"),
  29. prefix: "ratelimit:react-email:minute",
  30. });
  31. }
  32. return _minuteRateLimit.limit(identifier);
  33. },
  34. };
  35. // Requests per day (fixed window)
  36. export const dailyRateLimit = {
  37. limit: async (identifier: string) => {
  38. if (!_dailyRateLimit) {
  39. const redis = getRedis();
  40. if (!redis) return noopRateLimiter.limit();
  41. _dailyRateLimit = new Ratelimit({
  42. redis,
  43. limiter: Ratelimit.fixedWindow(DAILY_LIMIT, "1 d"),
  44. prefix: "ratelimit:react-email:daily",
  45. });
  46. }
  47. return _dailyRateLimit.limit(identifier);
  48. },
  49. };