- B1: Next.js 15 + Payload CMS 3.0 + Postgres 16, ESLint, Prettier, Husky, Vitest - B2: 9 collections, 6 globals, 12 Page Builder blocks, access control, slugify/revalidate hooks - B3: ezy.com.ua payments, Binotel HMAC webhook, leads API, Telegram bot, Resend email, rate limiting - B4: Tariffs collection with ezy API sync (cron + manual), dynamic pricing source-of-truth - B5: 13 test files covering unit libs and all API routes - B6: Dockerfile multi-stage, docker-compose.prod.yml, nginx.conf SSL, GitHub Actions CI/CD, health endpoint - B7: docs/admin-guide-ua.md (marketer guide), docs/deploy.md (VPS instructions), README quickstart Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
34 lines
657 B
TypeScript
34 lines
657 B
TypeScript
type Bucket = {
|
|
tokens: number
|
|
lastRefill: number
|
|
}
|
|
|
|
const buckets = new Map<string, Bucket>()
|
|
|
|
const MAX_TOKENS = 5
|
|
const REFILL_INTERVAL_MS = 15 * 60 * 1000
|
|
const REFILL_AMOUNT = 5
|
|
|
|
export function checkRateLimit(ip: string): boolean {
|
|
const now = Date.now()
|
|
let bucket = buckets.get(ip)
|
|
|
|
if (!bucket) {
|
|
bucket = { tokens: MAX_TOKENS - 1, lastRefill: now }
|
|
buckets.set(ip, bucket)
|
|
return true
|
|
}
|
|
|
|
const elapsed = now - bucket.lastRefill
|
|
if (elapsed >= REFILL_INTERVAL_MS) {
|
|
bucket.tokens = REFILL_AMOUNT
|
|
bucket.lastRefill = now
|
|
}
|
|
|
|
if (bucket.tokens <= 0) {
|
|
return false
|
|
}
|
|
|
|
bucket.tokens -= 1
|
|
return true
|
|
}
|