mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 02:11:12 -05:00
Improve error handling and display for searches and uploads
Enhance user feedback by displaying search errors, refine photo submission fetching, add rate limiting cleanup logic, improve image upload cleanup, and strengthen moderator permission checks. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2741d09b-80fb-4f0a-bfd6-ababb2ac4bfc Replit-Commit-Checkpoint-Type: intermediate_checkpoint
This commit is contained in:
@@ -41,36 +41,38 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
|
||||
const now = Date.now();
|
||||
const existing = rateLimitMap.get(ip);
|
||||
|
||||
if (!existing || now > existing.resetAt) {
|
||||
// If map is too large, clean up expired entries first
|
||||
if (rateLimitMap.size >= MAX_MAP_SIZE) {
|
||||
cleanupExpiredEntries();
|
||||
|
||||
// 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.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 ${MAX_MAP_SIZE} entries. Cleared ${toDelete} oldest entries.`);
|
||||
}
|
||||
// Handle existing entries (most common case - early return for performance)
|
||||
if (existing && now <= existing.resetAt) {
|
||||
if (existing.count >= MAX_REQUESTS) {
|
||||
const retryAfter = Math.ceil((existing.resetAt - now) / 1000);
|
||||
return { allowed: false, retryAfter };
|
||||
}
|
||||
|
||||
// Create new entry or reset expired entry
|
||||
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
|
||||
existing.count++;
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
if (existing.count >= MAX_REQUESTS) {
|
||||
const retryAfter = Math.ceil((existing.resetAt - now) / 1000);
|
||||
return { allowed: false, retryAfter };
|
||||
// Need to add new entry or reset expired one
|
||||
// Only perform cleanup if we're at capacity AND adding a new IP
|
||||
if (!existing && rateLimitMap.size >= MAX_MAP_SIZE) {
|
||||
// First try cleaning expired entries
|
||||
cleanupExpiredEntries();
|
||||
|
||||
// If still at capacity after cleanup, remove oldest entries (LRU eviction)
|
||||
if (rateLimitMap.size >= MAX_MAP_SIZE) {
|
||||
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 ${MAX_MAP_SIZE} entries. Cleared ${toDelete} oldest entries.`);
|
||||
}
|
||||
}
|
||||
|
||||
existing.count++;
|
||||
|
||||
// Create new entry or reset expired entry
|
||||
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,17 @@ Deno.serve(async (req) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { data: isMod } = await supabase.rpc('is_moderator', { _user_id: user.id });
|
||||
const { data: isMod, error: modError } = await supabase.rpc('is_moderator', { _user_id: user.id });
|
||||
if (modError) {
|
||||
console.error('Failed to check moderator status:', modError);
|
||||
return new Response(JSON.stringify({ error: 'Failed to verify permissions' }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
if (!isMod) {
|
||||
return new Response(JSON.stringify({ error: 'Must be moderator' }), {
|
||||
return new Response(JSON.stringify({ error: 'Insufficient permissions. Moderator role required.' }), {
|
||||
status: 403,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
});
|
||||
@@ -94,6 +102,11 @@ Deno.serve(async (req) => {
|
||||
|
||||
// Helper to create submission
|
||||
async function createSubmission(userId: string, type: string, itemData: any, options: { escalated?: boolean; expiredLock?: boolean } = {}) {
|
||||
// Ensure crypto.randomUUID is available
|
||||
if (typeof crypto === 'undefined' || typeof crypto.randomUUID !== 'function') {
|
||||
throw new Error('crypto.randomUUID is not available in this environment');
|
||||
}
|
||||
|
||||
const submissionId = crypto.randomUUID();
|
||||
const itemId = crypto.randomUUID();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user