feat: full site build — Project/Melody schema (Option A), admin CRUD, public sections, uploads, email+SMTP, internal analytics, legal pages, docs
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
type RateLimitEntry = { count: number; resetAt: number };
|
||||
|
||||
const entries = new Map<string, RateLimitEntry>();
|
||||
const windowMs = 10 * 60 * 1000;
|
||||
const maxRequests = 5;
|
||||
|
||||
export function consumeRateLimit(key: string, limit: number, durationMs: number) {
|
||||
const now = Date.now();
|
||||
const current = entries.get(key);
|
||||
if (!current || current.resetAt <= now) {
|
||||
entries.set(key, { count: 1, resetAt: now + durationMs });
|
||||
return true;
|
||||
}
|
||||
if (current.count >= limit) return false;
|
||||
current.count += 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
export function consumeContactRateLimit(key: string) {
|
||||
return consumeRateLimit(key, maxRequests, windowMs);
|
||||
}
|
||||
|
||||
export function getClientIp(requestHeaders: Headers) {
|
||||
const forwardedFor = requestHeaders.get("x-forwarded-for")?.split(",")[0]?.trim();
|
||||
return forwardedFor || requestHeaders.get("x-real-ip")?.trim() || "unknown";
|
||||
}
|
||||
Reference in New Issue
Block a user