diff --git a/src/components/homepage/ContentTabs.tsx b/src/components/homepage/ContentTabs.tsx index badab0b4..db1fea29 100644 --- a/src/components/homepage/ContentTabs.tsx +++ b/src/components/homepage/ContentTabs.tsx @@ -273,20 +273,24 @@ export function ContentTabs() { ) : (
- {recentlyOpened.map((entity: any) => ( + {recentlyOpened.map((entity) => ( entity.entityType === 'park' ? (
- - {new Date(entity.opening_date).getFullYear()} - + {entity.opening_date && ( + + {new Date(entity.opening_date).getFullYear()} + + )}
) : (
- - {new Date(entity.opening_date).getFullYear()} - + {entity.opening_date && ( + + {new Date(entity.opening_date).getFullYear()} + + )}
) ))} diff --git a/src/components/notifications/NotificationCenter.tsx b/src/components/notifications/NotificationCenter.tsx index 56be0bc4..e357f305 100644 --- a/src/components/notifications/NotificationCenter.tsx +++ b/src/components/notifications/NotificationCenter.tsx @@ -19,10 +19,11 @@ export function NotificationCenter() { return null; } - const handleNotificationClick = (notification: any) => { + const handleNotificationClick = (notification: { data?: Record }) => { // Handle navigation based on notification payload - if (notification.data?.url) { - navigate(notification.data.url); + const data = notification.data as { url?: string } | undefined; + if (data?.url) { + navigate(data.url); } }; diff --git a/src/hooks/useAdminSettings.ts b/src/hooks/useAdminSettings.ts index fbcc2e52..c21e4b47 100644 --- a/src/hooks/useAdminSettings.ts +++ b/src/hooks/useAdminSettings.ts @@ -4,11 +4,12 @@ import { useAuth } from './useAuth'; import { useUserRole } from './useUserRole'; import { useToast } from './use-toast'; import { useCallback, useMemo } from 'react'; +import type { Json } from '@/integrations/supabase/types'; interface AdminSetting { id: string; setting_key: string; - setting_value: any; + setting_value: unknown; category: string; description: string; } @@ -46,11 +47,11 @@ export function useAdminSettings() { }, [settings]); const updateSettingMutation = useMutation({ - mutationFn: async ({ key, value }: { key: string; value: any }) => { + mutationFn: async ({ key, value }: { key: string; value: unknown }) => { const { error } = await supabase .from('admin_settings') .update({ - setting_value: value, + setting_value: value as Json, updated_by: user?.id, updated_at: new Date().toISOString() }) @@ -65,7 +66,7 @@ export function useAdminSettings() { description: "The setting has been saved successfully.", }); }, - onError: (error: any) => { + onError: (error: Error) => { toast({ title: "Error", description: error.message || "Failed to update setting", @@ -74,17 +75,17 @@ export function useAdminSettings() { } }); - const getSettingValue = useCallback((key: string, defaultValue: any = null) => { + const getSettingValue = useCallback((key: string, defaultValue: unknown = null) => { return settingsMap[key] ?? defaultValue; }, [settingsMap]); - const updateSetting = async (key: string, value: any) => { + const updateSetting = async (key: string, value: unknown) => { return updateSettingMutation.mutateAsync({ key, value }); }; // Helper functions for common settings (memoized with useCallback for stable references) const getAutoFlagThreshold = useCallback(() => { - return parseInt(getSettingValue('moderation.auto_flag_threshold', '3')); + return parseInt(String(getSettingValue('moderation.auto_flag_threshold', '3'))); }, [getSettingValue]); const getRequireApproval = useCallback(() => { @@ -94,7 +95,7 @@ export function useAdminSettings() { const getBanDurations = useCallback(() => { const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']); - return Array.isArray(value) ? value : JSON.parse(value || '[]'); + return Array.isArray(value) ? value : JSON.parse(String(value || '[]')); }, [getSettingValue]); const getEmailAlertsEnabled = useCallback(() => { @@ -103,11 +104,11 @@ export function useAdminSettings() { }, [getSettingValue]); const getReportThreshold = useCallback(() => { - return parseInt(getSettingValue('notifications.report_threshold', '5')); + return parseInt(String(getSettingValue('notifications.report_threshold', '5'))); }, [getSettingValue]); const getAuditRetentionDays = useCallback(() => { - return parseInt(getSettingValue('system.audit_retention_days', '365')); + return parseInt(String(getSettingValue('system.audit_retention_days', '365'))); }, [getSettingValue]); const getAutoCleanupEnabled = useCallback(() => { @@ -115,10 +116,11 @@ export function useAdminSettings() { return value === true || value === 'true'; }, [getSettingValue]); - const getAdminPanelRefreshMode = useCallback(() => { + const getAdminPanelRefreshMode = useCallback((): 'auto' | 'manual' => { const value = getSettingValue('system.admin_panel_refresh_mode', 'auto'); // Remove quotes if they exist (JSON string stored in DB) - return typeof value === 'string' ? value.replace(/"/g, '') : value; + const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : String(value); + return (cleanValue === 'manual' ? 'manual' : 'auto') as 'auto' | 'manual'; }, [getSettingValue]); const getAdminPanelPollInterval = useCallback(() => { diff --git a/src/lib/submissionItemsService.ts b/src/lib/submissionItemsService.ts index 8ede9b8a..40b42276 100644 --- a/src/lib/submissionItemsService.ts +++ b/src/lib/submissionItemsService.ts @@ -1137,7 +1137,7 @@ export async function rejectSubmissionItems( .eq('id', itemId); if (error) { - console.error(`Error rejecting item ${itemId}:`, error); + logger.error('Error rejecting item', { error, itemId }); throw error; } }); @@ -1171,7 +1171,7 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi .eq('submission_id', submissionId); if (fetchError) { - console.error('Error fetching submission items:', fetchError); + logger.error('Error fetching submission items', { error: fetchError, submissionId }); return; } @@ -1202,7 +1202,7 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi .eq('id', submissionId); if (updateError) { - console.error('Error updating submission status:', updateError); + logger.error('Error updating submission status', { error: updateError, submissionId }); } } diff --git a/supabase/functions/cancel-email-change/index.ts b/supabase/functions/cancel-email-change/index.ts index 3cd1d0ee..4b3a14c4 100644 --- a/supabase/functions/cancel-email-change/index.ts +++ b/supabase/functions/cancel-email-change/index.ts @@ -85,11 +85,15 @@ Deno.serve(async (req) => { .rpc('cancel_user_email_change', { _user_id: userId }); if (cancelError || !cancelled) { - console.error('Error cancelling email change:', cancelError); + edgeLogger.error('Error cancelling email change', { + error: cancelError?.message, + userId, + requestId: tracking.requestId + }); throw new Error('Unable to cancel email change: ' + (cancelError?.message || 'Unknown error')); } - console.log(`Successfully cancelled email change for user ${userId}`); + edgeLogger.info('Successfully cancelled email change', { userId, requestId: tracking.requestId }); // Log the cancellation in admin_audit_log const { error: auditError } = await supabaseAdmin @@ -104,7 +108,10 @@ Deno.serve(async (req) => { }); if (auditError) { - console.error('Error logging audit:', auditError); + edgeLogger.error('Error logging audit', { + error: auditError.message, + requestId: tracking.requestId + }); // Don't fail the request if audit logging fails } diff --git a/supabase/functions/cleanup-old-versions/index.ts b/supabase/functions/cleanup-old-versions/index.ts index ebcf7714..8f7e87b6 100644 --- a/supabase/functions/cleanup-old-versions/index.ts +++ b/supabase/functions/cleanup-old-versions/index.ts @@ -1,4 +1,5 @@ import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; +import { edgeLogger } from '../_shared/logger.ts'; const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -38,7 +39,7 @@ Deno.serve(async (req) => { errors: [], }; - console.log('Starting version cleanup job...'); + edgeLogger.info('Starting version cleanup job'); // Get retention settings from admin_settings const { data: retentionSetting, error: settingsError } = await supabaseClient @@ -55,7 +56,7 @@ Deno.serve(async (req) => { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - retentionDays); - console.log(`Cleanup configuration: ${retentionDays} day retention, cutoff: ${cutoffDate.toISOString()}`); + edgeLogger.info('Cleanup configuration', { retentionDays, cutoff: cutoffDate.toISOString() }); // Step 1: Delete orphaned edit history (where submission_item no longer exists) const { data: orphanedRecords, error: orphanError } = await supabaseClient @@ -63,10 +64,10 @@ Deno.serve(async (req) => { if (orphanError) { stats.errors.push(`Failed to find orphaned records: ${orphanError.message}`); - console.error('Orphan detection error:', orphanError); + edgeLogger.error('Orphan detection error', { error: orphanError.message }); } else if (orphanedRecords && orphanedRecords.length > 0) { - const orphanedIds = orphanedRecords.map((r: any) => r.id); - console.log(`Found ${orphanedIds.length} orphaned edit history records`); + const orphanedIds = orphanedRecords.map((r: { id: string }) => r.id); + edgeLogger.info('Found orphaned edit history records', { count: orphanedIds.length }); const { error: deleteOrphanError } = await supabaseClient .from('item_edit_history') @@ -75,10 +76,10 @@ Deno.serve(async (req) => { if (deleteOrphanError) { stats.errors.push(`Failed to delete orphaned records: ${deleteOrphanError.message}`); - console.error('Orphan deletion error:', deleteOrphanError); + edgeLogger.error('Orphan deletion error', { error: deleteOrphanError.message }); } else { stats.orphaned_records_deleted = orphanedIds.length; - console.log(`Deleted ${orphanedIds.length} orphaned records`); + edgeLogger.info('Deleted orphaned records', { count: orphanedIds.length }); } } @@ -92,7 +93,7 @@ Deno.serve(async (req) => { } if (items && items.length > 0) { - console.log(`Processing ${items.length} submission items for version cleanup`); + edgeLogger.info('Processing submission items for version cleanup', { itemCount: items.length }); for (const item of items) { try { @@ -165,7 +166,7 @@ Deno.serve(async (req) => { stats.processing_time_ms = Date.now() - startTime; - console.log('Cleanup completed successfully:', { + edgeLogger.info('Cleanup completed successfully', { ...stats, errors: stats.errors.length > 0 ? stats.errors : undefined, }); @@ -182,7 +183,7 @@ Deno.serve(async (req) => { } ); } catch (error) { - console.error('Cleanup job failed:', error); + edgeLogger.error('Cleanup job failed', { error: error instanceof Error ? error.message : String(error) }); return new Response( JSON.stringify({ diff --git a/supabase/functions/confirm-account-deletion/index.ts b/supabase/functions/confirm-account-deletion/index.ts index fd2eb7b3..1f7bac64 100644 --- a/supabase/functions/confirm-account-deletion/index.ts +++ b/supabase/functions/confirm-account-deletion/index.ts @@ -96,7 +96,10 @@ serve(async (req) => { throw new Error('Confirmation code has expired. Please request a new deletion code.'); } - console.log('Deactivating account and confirming deletion request...'); + edgeLogger.info('Deactivating account and confirming deletion request', { + userId: user.id, + requestId: tracking.requestId + }); // Deactivate profile const { error: profileError } = await supabaseClient @@ -109,7 +112,11 @@ serve(async (req) => { .eq('user_id', user.id); if (profileError) { - console.error('Error deactivating profile:', profileError); + edgeLogger.error('Error deactivating profile', { + error: profileError.message, + userId: user.id, + requestId: tracking.requestId + }); throw profileError; } @@ -122,7 +129,10 @@ serve(async (req) => { .eq('id', deletionRequest.id); if (updateError) { - console.error('Error updating deletion request:', updateError); + edgeLogger.error('Error updating deletion request', { + error: updateError.message, + requestId: tracking.requestId + }); throw updateError; } @@ -160,9 +170,12 @@ serve(async (req) => { `, }), }); - console.log('Deletion confirmation email sent'); + edgeLogger.info('Deletion confirmation email sent', { requestId: tracking.requestId }); } catch (emailError) { - console.error('Failed to send email:', emailError); + edgeLogger.error('Failed to send email', { + error: emailError instanceof Error ? emailError.message : String(emailError), + requestId: tracking.requestId + }); } } diff --git a/supabase/functions/create-novu-subscriber/index.ts b/supabase/functions/create-novu-subscriber/index.ts index 84d9894b..f20f80bb 100644 --- a/supabase/functions/create-novu-subscriber/index.ts +++ b/supabase/functions/create-novu-subscriber/index.ts @@ -1,5 +1,6 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { Novu } from "npm:@novu/api@1.6.0"; +import { edgeLogger } from '../_shared/logger.ts'; const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -189,7 +190,7 @@ serve(async (req) => { } } - console.log('Creating Novu subscriber:', { subscriberId, email, firstName }); + edgeLogger.info('Creating Novu subscriber', { subscriberId, email: '***', firstName, requestId: tracking.requestId }); const subscriber = await novu.subscribers.identify(subscriberId, { email, @@ -201,18 +202,26 @@ serve(async (req) => { }); const duration = endRequest(tracking); - console.log('Subscriber created successfully:', subscriber.data, { requestId: tracking.requestId, duration }); + edgeLogger.info('Subscriber created successfully', { + subscriberId: subscriber.data._id, + requestId: tracking.requestId, + duration + }); // Add subscriber to "users" topic for global announcements try { - console.log('Adding subscriber to "users" topic...', { subscriberId }); + edgeLogger.info('Adding subscriber to users topic', { subscriberId, requestId: tracking.requestId }); await novu.topics.addSubscribers('users', { subscribers: [subscriberId], }); - console.log('Successfully added subscriber to "users" topic', { subscriberId }); - } catch (topicError: any) { + edgeLogger.info('Successfully added subscriber to users topic', { subscriberId, requestId: tracking.requestId }); + } catch (topicError: unknown) { // Non-blocking - log error but don't fail the request - console.error('Failed to add subscriber to "users" topic:', topicError.message, { subscriberId }); + edgeLogger.error('Failed to add subscriber to users topic', { + error: topicError instanceof Error ? topicError.message : String(topicError), + subscriberId, + requestId: tracking.requestId + }); } return new Response( @@ -226,9 +235,13 @@ serve(async (req) => { status: 200, } ); - } catch (error: any) { + } catch (error: unknown) { const duration = endRequest(tracking); - console.error('Error creating Novu subscriber:', error, { requestId: tracking.requestId, duration }); + edgeLogger.error('Error creating Novu subscriber', { + error: error instanceof Error ? error.message : String(error), + requestId: tracking.requestId, + duration + }); return new Response( JSON.stringify({ diff --git a/supabase/functions/detect-location/index.ts b/supabase/functions/detect-location/index.ts index 7604bc6f..e69a5bdb 100644 --- a/supabase/functions/detect-location/index.ts +++ b/supabase/functions/detect-location/index.ts @@ -1,5 +1,5 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; -import { startRequest, endRequest } from "../_shared/logger.ts"; +import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -36,14 +36,8 @@ function cleanupExpiredEntries() { } } - // Log cleanup activity for monitoring - if (deletedCount > 0) { - console.log(`[Cleanup] Removed ${deletedCount} expired entries. Map size: ${mapSizeBefore} -> ${rateLimitMap.size}`); - } - - // Reset failure count on successful cleanup + // Cleanup runs silently unless there are issues if (cleanupFailureCount > 0) { - console.log(`[Cleanup] Successful cleanup after ${cleanupFailureCount} previous failures. Resetting failure count.`); cleanupFailureCount = 0; } @@ -52,20 +46,20 @@ function cleanupExpiredEntries() { cleanupFailureCount++; const errorMessage = error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : 'No stack trace available'; - console.error('[Cleanup Error]', { + edgeLogger.error('Cleanup error', { attempt: cleanupFailureCount, maxAttempts: MAX_CLEANUP_FAILURES, error: errorMessage, - stack: errorStack, mapSize: rateLimitMap.size }); // FALLBACK MECHANISM: If cleanup fails repeatedly, force clear to prevent memory leak if (cleanupFailureCount >= MAX_CLEANUP_FAILURES) { - console.error(`[Cleanup CRITICAL] Cleanup has failed ${cleanupFailureCount} times consecutively!`); - console.error(`[Cleanup CRITICAL] Forcing emergency cleanup to prevent memory leak...`); + edgeLogger.error('Cleanup critical - forcing emergency cleanup', { + consecutiveFailures: cleanupFailureCount, + mapSize: rateLimitMap.size + }); try { // Emergency: Clear oldest 50% of entries to prevent unbounded growth @@ -79,20 +73,20 @@ function cleanupExpiredEntries() { clearedCount++; } - console.warn(`[Cleanup CRITICAL] Emergency cleanup completed. Cleared ${clearedCount} entries. Map size: ${rateLimitMap.size}`); + edgeLogger.warn('Emergency cleanup completed', { clearedCount, newMapSize: rateLimitMap.size }); // Reset failure count after emergency cleanup cleanupFailureCount = 0; } catch (emergencyError) { // Last resort: If even emergency cleanup fails, clear everything - console.error(`[Cleanup CRITICAL] Emergency cleanup failed! Clearing entire rate limit map.`); - console.error(`[Cleanup CRITICAL] Emergency error: ${emergencyError}`); - const originalSize = rateLimitMap.size; rateLimitMap.clear(); - console.warn(`[Cleanup CRITICAL] Cleared entire rate limit map (${originalSize} entries) to prevent memory leak.`); + edgeLogger.error('Emergency cleanup failed - cleared entire map', { + originalSize, + error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError) + }); cleanupFailureCount = 0; } } @@ -102,7 +96,6 @@ function cleanupExpiredEntries() { // Reset cleanup failure count periodically to avoid permanent emergency state setInterval(() => { if (cleanupFailureCount > 0) { - console.warn(`[Cleanup] Resetting failure count (was ${cleanupFailureCount}) after timeout period.`); cleanupFailureCount = 0; } }, CLEANUP_FAILURE_RESET_INTERVAL); @@ -140,11 +133,17 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } { deletedCount++; } - console.warn(`[Rate Limit] Map reached ${MAX_MAP_SIZE} entries. Cleared ${deletedCount} oldest entries. New size: ${rateLimitMap.size}`); + edgeLogger.warn('Rate limit map at capacity - evicted entries', { + maxSize: MAX_MAP_SIZE, + deletedCount, + newSize: rateLimitMap.size + }); } catch (evictionError) { // CRITICAL: LRU eviction failed - log error and attempt emergency clear - console.error(`[Rate Limit CRITICAL] LRU eviction failed! Error: ${evictionError}`); - console.error(`[Rate Limit CRITICAL] Map size: ${rateLimitMap.size}`); + edgeLogger.error('LRU eviction failed', { + error: evictionError instanceof Error ? evictionError.message : String(evictionError), + mapSize: rateLimitMap.size + }); try { // Emergency: Clear first 30% of entries without sorting @@ -158,9 +157,14 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } { keysToDelete.forEach(key => rateLimitMap.delete(key)); - console.warn(`[Rate Limit CRITICAL] Emergency eviction cleared ${keysToDelete.length} entries. New size: ${rateLimitMap.size}`); + edgeLogger.warn('Emergency eviction completed', { + clearedCount: keysToDelete.length, + newSize: rateLimitMap.size + }); } catch (emergencyError) { - console.error(`[Rate Limit CRITICAL] Emergency eviction also failed! Clearing entire map. Error: ${emergencyError}`); + edgeLogger.error('Emergency eviction failed - clearing entire map', { + error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError) + }); rateLimitMap.clear(); } } @@ -211,7 +215,7 @@ serve(async (req) => { } // PII Note: Do not log full IP addresses in production - console.log('[Location] Detecting location for request'); + edgeLogger.info('Detecting location for request', { requestId: tracking.requestId }); // Use configurable geolocation service with proper error handling // Defaults to ip-api.com if not configured @@ -222,7 +226,10 @@ serve(async (req) => { try { geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`); } catch (fetchError) { - console.error('Network error fetching location data:', fetchError); + edgeLogger.error('Network error fetching location data', { + error: fetchError instanceof Error ? fetchError.message : String(fetchError), + requestId: tracking.requestId + }); throw new Error('Network error: Unable to reach geolocation service'); } @@ -234,7 +241,10 @@ serve(async (req) => { try { geoData = await geoResponse.json(); } catch (parseError) { - console.error('Failed to parse geolocation response:', parseError); + edgeLogger.error('Failed to parse geolocation response', { + error: parseError instanceof Error ? parseError.message : String(parseError), + requestId: tracking.requestId + }); throw new Error('Invalid response format from geolocation service'); } @@ -252,7 +262,7 @@ serve(async (req) => { measurementSystem }; - console.log('[Location] Location detected:', { + edgeLogger.info('Location detected', { country: result.country, countryCode: result.countryCode, measurementSystem: result.measurementSystem, @@ -275,12 +285,9 @@ serve(async (req) => { } catch (error: unknown) { // Enhanced error logging for better visibility and debugging const errorMessage = error instanceof Error ? error.message : String(error); - const errorStack = error instanceof Error ? error.stack : 'No stack trace available'; - console.error('[Location Detection Error]', { + edgeLogger.error('Location detection error', { error: errorMessage, - stack: errorStack, - hasIP: true, // IP removed for PII protection requestId: tracking.requestId });