|
1 | | -import { doc, runTransaction } from 'firebase/firestore'; |
| 1 | +import { doc, getDoc, increment, setDoc } from 'firebase/firestore'; |
2 | 2 | import { getDb } from './db'; |
3 | 3 |
|
4 | 4 | interface RateLimitOptions { |
@@ -26,31 +26,26 @@ export async function enforceRateLimit( |
26 | 26 | { windowMs = DEFAULT_WINDOW_MS, maxRequests = DEFAULT_MAX_REQUESTS }: RateLimitOptions = {} |
27 | 27 | ): Promise<RateLimitState> { |
28 | 28 | const db = getDb(); |
29 | | - const ref = doc(db, 'rateLimits', key); |
30 | 29 | const now = Date.now(); |
31 | | - |
32 | | - let state: RateLimitState = { |
33 | | - allowed: true, |
34 | | - remaining: maxRequests, |
35 | | - reset: now + windowMs, |
| 30 | + const windowStart = Math.floor(now / windowMs) * windowMs; |
| 31 | + const ref = doc(db, 'rateLimits', `${key}:${windowStart}`); |
| 32 | + |
| 33 | + await setDoc( |
| 34 | + ref, |
| 35 | + { count: increment(1), windowStart }, |
| 36 | + { merge: true }, |
| 37 | + ); |
| 38 | + |
| 39 | + const snapshot = await getDoc(ref); |
| 40 | + const data = snapshot.data() as RateLimitDocument | undefined; |
| 41 | + const count = data?.count ?? 0; |
| 42 | + const remaining = Math.max(0, maxRequests - count); |
| 43 | + const reset = windowStart + windowMs; |
| 44 | + |
| 45 | + return { |
| 46 | + allowed: count <= maxRequests, |
| 47 | + remaining, |
| 48 | + reset, |
36 | 49 | limit: maxRequests, |
37 | 50 | }; |
38 | | - |
39 | | - await runTransaction(db, async (transaction) => { |
40 | | - const snapshot = await transaction.get(ref); |
41 | | - const data = snapshot.data() as RateLimitDocument | undefined; |
42 | | - const withinWindow = data && now - data.windowStart < windowMs; |
43 | | - |
44 | | - const windowStart = withinWindow ? data.windowStart : now; |
45 | | - const newCount = (withinWindow ? data.count : 0) + 1; |
46 | | - const remaining = Math.max(0, maxRequests - newCount); |
47 | | - const reset = windowStart + windowMs; |
48 | | - const allowed = newCount <= maxRequests; |
49 | | - |
50 | | - transaction.set(ref, { count: newCount, windowStart }); |
51 | | - |
52 | | - state = { allowed, remaining, reset, limit: maxRequests }; |
53 | | - }); |
54 | | - |
55 | | - return state; |
56 | 51 | } |
0 commit comments