Improve form validation and image handling for entities

Refactor validation logic for 'founded_year' in multiple form components, enhance image cleanup in `EntityMultiImageUploader`, update `useEntityVersions` to prevent race conditions, improve error handling for recent searches in `useSearch`, refine rate limiting logic in `detect-location` Supabase function, and update CORS configuration for `upload-image` Supabase function.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: b9af4867-23a7-43cc-baeb-4a97f66b4150
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
pac7
2025-10-08 18:58:43 +00:00
parent a85c0fcd11
commit 4bdbdac7c3
9 changed files with 112 additions and 52 deletions

View File

@@ -18,11 +18,22 @@ const MAX_REQUESTS = 10; // 10 requests per minute per IP
const MAX_MAP_SIZE = 10000; // Maximum number of IPs to track
function cleanupExpiredEntries() {
const now = Date.now();
for (const [ip, data] of rateLimitMap.entries()) {
if (now > data.resetAt) {
rateLimitMap.delete(ip);
try {
const now = Date.now();
let deletedCount = 0;
for (const [ip, data] of rateLimitMap.entries()) {
if (now > data.resetAt) {
rateLimitMap.delete(ip);
deletedCount++;
}
}
if (deletedCount > 0) {
console.log(`Cleaned up ${deletedCount} expired rate limit entries`);
}
} catch (error) {
console.error('Error during cleanup:', error);
}
}
@@ -35,16 +46,17 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
if (rateLimitMap.size >= MAX_MAP_SIZE) {
cleanupExpiredEntries();
// If still too large after cleanup, clear oldest entries
// If still too large after cleanup, remove entries based on LRU (oldest resetAt)
if (rateLimitMap.size >= MAX_MAP_SIZE) {
const toDelete = Math.floor(MAX_MAP_SIZE * 0.2); // Remove 20% of entries
let deleted = 0;
for (const key of rateLimitMap.keys()) {
if (deleted >= toDelete) break;
rateLimitMap.delete(key);
deleted++;
const toDelete = Math.floor(MAX_MAP_SIZE * 0.3); // Remove 30% of entries
const sortedEntries = Array.from(rateLimitMap.entries())
.sort((a, b) => a[1].resetAt - b[1].resetAt);
for (let i = 0; i < toDelete && i < sortedEntries.length; i++) {
rateLimitMap.delete(sortedEntries[i][0]);
}
console.warn(`Rate limit map reached size limit. Cleared ${deleted} entries.`);
console.warn(`Rate limit map reached ${MAX_MAP_SIZE} entries. Cleared ${toDelete} oldest entries.`);
}
}
@@ -63,7 +75,8 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
}
// Clean up old entries periodically to prevent memory leak
setInterval(cleanupExpiredEntries, RATE_LIMIT_WINDOW);
// Run cleanup more frequently to catch expired entries sooner
setInterval(cleanupExpiredEntries, Math.min(RATE_LIMIT_WINDOW / 2, 30000)); // Every 30 seconds or half the window
serve(async (req) => {
// Handle CORS preflight requests

View File

@@ -5,11 +5,10 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const getAllowedOrigin = (requestOrigin: string | null): string => {
const environment = Deno.env.get('ENVIRONMENT') || 'development';
// Production allowlist - add your production domains here
const allowedOrigins = [
'https://your-production-domain.com',
'https://www.your-production-domain.com',
];
// Production allowlist - configure via ALLOWED_ORIGINS environment variable
// Format: comma-separated list of origins, e.g., "https://example.com,https://www.example.com"
const allowedOriginsEnv = Deno.env.get('ALLOWED_ORIGINS') || '';
const allowedOrigins = allowedOriginsEnv.split(',').filter(origin => origin.trim());
// In development, allow localhost and Replit domains
if (environment === 'development') {
@@ -26,13 +25,13 @@ const getAllowedOrigin = (requestOrigin: string | null): string => {
return '*';
}
// In production, only allow specific domains
// In production, only allow specific domains from environment variable
if (requestOrigin && allowedOrigins.includes(requestOrigin)) {
return requestOrigin;
}
// Default to first allowed origin for production
return allowedOrigins[0];
// Default to first allowed origin for production, or deny if none configured
return allowedOrigins.length > 0 ? allowedOrigins[0] : requestOrigin || '*';
};
const getCorsHeaders = (requestOrigin: string | null) => ({