Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth. Includes production Dockerfile (multi-stage, port 7600), docker-compose with registry-based image pull, Gitea Actions CI workflow, nginx config for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
60 lines
1.4 KiB
TypeScript
60 lines
1.4 KiB
TypeScript
/**
|
|
* Simple in-memory rate limiter using fixed window approach.
|
|
* Tracks request counts per key (typically IP) within time windows.
|
|
*
|
|
* For production with multiple instances, replace with Redis-based solution.
|
|
*/
|
|
|
|
type RateLimitEntry = {
|
|
count: number
|
|
resetAt: number
|
|
}
|
|
|
|
const store = new Map<string, RateLimitEntry>()
|
|
|
|
export type RateLimitResult = {
|
|
success: boolean
|
|
remaining: number
|
|
resetAt: number
|
|
}
|
|
|
|
/**
|
|
* Check rate limit for a given key.
|
|
* @param key - Identifier (e.g., IP address)
|
|
* @param limit - Max requests per window
|
|
* @param windowMs - Window size in milliseconds
|
|
*/
|
|
export function checkRateLimit(
|
|
key: string,
|
|
limit: number,
|
|
windowMs: number
|
|
): RateLimitResult {
|
|
const now = Date.now()
|
|
const entry = store.get(key)
|
|
|
|
if (!entry || now > entry.resetAt) {
|
|
const resetAt = now + windowMs
|
|
store.set(key, { count: 1, resetAt })
|
|
return { success: true, remaining: limit - 1, resetAt }
|
|
}
|
|
|
|
if (entry.count >= limit) {
|
|
return { success: false, remaining: 0, resetAt: entry.resetAt }
|
|
}
|
|
|
|
entry.count++
|
|
return { success: true, remaining: limit - entry.count, resetAt: entry.resetAt }
|
|
}
|
|
|
|
// Clean up stale entries every 5 minutes to prevent memory leaks
|
|
if (typeof setInterval !== 'undefined') {
|
|
setInterval(() => {
|
|
const now = Date.now()
|
|
for (const [key, entry] of store) {
|
|
if (now > entry.resetAt) {
|
|
store.delete(key)
|
|
}
|
|
}
|
|
}, 5 * 60 * 1000).unref?.()
|
|
}
|