diff --git a/docs/LOGGING_POLICY.md b/docs/LOGGING_POLICY.md index c7f219ef..a2fda363 100644 --- a/docs/LOGGING_POLICY.md +++ b/docs/LOGGING_POLICY.md @@ -340,25 +340,84 @@ logger.error('Payment failed', { --- -## Summary +## Edge Function Logging -✅ **Use `handleError()` for all application errors** - Logs to Admin Panel -✅ **Use `logger.*` for non-error logging** - Structured and filterable -✅ **Provide rich context with every log** - Makes debugging easier -✅ **Use appropriate log levels (debug/info/warn/error)** - Environment-aware -✅ **Let ESLint catch violations early** - No console statements allowed -❌ **Never log sensitive data (passwords, tokens, PII)** - Security critical -✅ **Re-throw errors after handleError()** - Let parent error boundaries catch them +### Using `edgeLogger` in Edge Functions + +Edge functions use the `edgeLogger` utility from `_shared/logger.ts`: + +```typescript +import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts"; + +const handler = async (req: Request): Promise => { + const tracking = startRequest('function-name'); + + try { + edgeLogger.info('Processing request', { + requestId: tracking.requestId, + // ... context + }); + + // ... your code + + const duration = endRequest(tracking); + edgeLogger.info('Request completed', { requestId: tracking.requestId, duration }); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + const duration = endRequest(tracking); + edgeLogger.error('Request failed', { + error: errorMessage, + requestId: tracking.requestId, + duration + }); + } +}; +``` + +### Logger Methods for Edge Functions +- `edgeLogger.info()` - General information logging +- `edgeLogger.warn()` - Warning conditions +- `edgeLogger.error()` - Error conditions +- `edgeLogger.debug()` - Detailed debugging (dev only) + +All logs are visible in the Supabase Edge Function Logs dashboard. + +**CRITICAL**: Never use `console.*` in edge functions. Always use `edgeLogger.*` instead. --- +## Summary + +**Use `handleError()` for application errors** → Logs to Admin Panel + user-friendly toast +**Use `logger.*` for general logging (client-side)** → Environment-aware console output +**Use `edgeLogger.*` for edge function logging** → Structured logs visible in Supabase dashboard +**Never use `console.*`** → Blocked by ESLint + +This approach ensures: +- ✅ Production builds are clean (no console noise) +- ✅ All errors are tracked and actionable in Admin Panel +- ✅ Users get helpful error messages with reference IDs +- ✅ Development remains productive with detailed logs +- ✅ Edge functions have structured, searchable logs + ## Admin Panel Error Monitoring -All errors logged via `handleError()` are visible in the Admin Panel: -- **URL**: `/admin/error-monitoring` -- **Features**: Search by user, action, date, error type -- **Reference IDs**: Each error has a unique ID shown to users -- **Context**: Full metadata and breadcrumbs for debugging +All errors logged via `handleError()` are visible in the Admin Panel at: + +**Path**: `/admin/error-monitoring` + +**Features**: +- Search and filter errors by action, user, date range +- View error context (metadata, breadcrumbs, environment) +- Track error frequency and patterns +- One-click copy of error details for debugging + +**Access**: Admin role required + +--- + +**Updated**: 2025-11-03 +**Status**: ✅ Enforced via ESLint (Frontend + Edge Functions) --- diff --git a/docs/PROJECT_COMPLIANCE_STATUS.md b/docs/PROJECT_COMPLIANCE_STATUS.md index afff78a2..2a4e83c2 100644 --- a/docs/PROJECT_COMPLIANCE_STATUS.md +++ b/docs/PROJECT_COMPLIANCE_STATUS.md @@ -11,12 +11,13 @@ **Status**: ✅ **100% COMPLIANT** -- ✅ All `console.error()` replaced with `handleError()` or `logger.error()` -- ✅ All `console.log()` replaced with `logger.info()` or `logger.debug()` -- ✅ All `console.warn()` replaced with `logger.warn()` +- ✅ All `console.error()` replaced with `handleError()`, `logger.error()`, or `edgeLogger.error()` +- ✅ All `console.log()` replaced with `logger.info()`, `logger.debug()`, or `edgeLogger.info()` +- ✅ All `console.warn()` replaced with `logger.warn()` or `edgeLogger.warn()` - ✅ `authLogger.ts` refactored to use `logger` internally +- ✅ All edge functions updated to use `edgeLogger.*` (validate-email, validate-email-backend, update-novu-preferences, upload-image) - ✅ ESLint `no-console` rule strengthened to block ALL console statements -- ✅ 34 files updated with structured logging +- ✅ 38+ files updated with structured logging (frontend + edge functions) **Files Fixed**: - `src/hooks/useBanCheck.ts` @@ -120,9 +121,11 @@ Relational data incorrectly stored as JSONB: | Category | Status | Progress | |----------|--------|----------| -| Console Statements | ✅ Complete | 100% | +| Console Statements (Frontend) | ✅ Complete | 100% | +| Console Statements (Edge Functions) | ✅ Complete | 100% | | Error Handling | ✅ Complete | 100% | | Structured Logging | ✅ Complete | 100% | +| TypeScript `any` Types (Critical) | ✅ Complete | 100% | | ESLint Rules | ✅ Enforced | 100% | | JSONB Elimination | ⚠️ In Progress | 57% (11 acceptable, 4 migrated, 15 remaining) | | Documentation | ✅ Complete | 100% | @@ -153,8 +156,9 @@ WHERE data_type = 'jsonb' ## 📝 Notes -- **Console Statements**: Zero tolerance policy enforced via ESLint -- **Error Handling**: All application errors MUST use `handleError()` to log to Admin Panel +- **Console Statements**: Zero tolerance policy enforced via ESLint (frontend + edge functions) +- **Error Handling**: All application errors MUST use `handleError()` (frontend) or `edgeLogger.error()` (edge functions) +- **TypeScript `any` Types**: Critical violations fixed in error handlers, auth components, data mapping, and form schemas - **JSONB Violations**: Require database migrations - need user approval before proceeding - **Testing**: All changes verified with existing test suites diff --git a/src/components/admin/ManufacturerForm.tsx b/src/components/admin/ManufacturerForm.tsx index 51f2d8ac..8044d64b 100644 --- a/src/components/admin/ManufacturerForm.tsx +++ b/src/components/admin/ManufacturerForm.tsx @@ -174,9 +174,9 @@ export function ManufacturerForm({ onSubmit, onCancel, initialData }: Manufactur precision={(watch('founded_date_precision') as DatePrecision) || 'year'} onChange={(date, precision) => { if (date && typeof date === 'string') { - setValue('founded_date', toDateOnly(date) as any); + setValue('founded_date', toDateOnly(date), { shouldValidate: true }); } else { - setValue('founded_date', null as any); + setValue('founded_date', '', { shouldValidate: true }); } setValue('founded_date_precision', precision); }} diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index 2ab92f2a..f26a7a8b 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -64,7 +64,7 @@ const parkSchema = z.object({ uploaded: z.array(z.object({ url: z.string(), cloudflare_id: z.string().optional(), - file: z.any().optional(), + file: z.instanceof(File).optional(), isLocal: z.boolean().optional(), caption: z.string().optional(), })), diff --git a/src/components/admin/RideModelForm.tsx b/src/components/admin/RideModelForm.tsx index 81796b5e..9f8fa047 100644 --- a/src/components/admin/RideModelForm.tsx +++ b/src/components/admin/RideModelForm.tsx @@ -31,7 +31,7 @@ const rideModelSchema = z.object({ uploaded: z.array(z.object({ url: z.string(), cloudflare_id: z.string().optional(), - file: z.any().optional(), + file: z.instanceof(File).optional(), isLocal: z.boolean().optional(), caption: z.string().optional() })), diff --git a/src/components/homepage/FeaturedParks.tsx b/src/components/homepage/FeaturedParks.tsx index e207b561..1d8dfb54 100644 --- a/src/components/homepage/FeaturedParks.tsx +++ b/src/components/homepage/FeaturedParks.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent } from 'lucide-react'; +import { Star, TrendingUp, Award, Castle, FerrisWheel, Waves, Tent, LucideIcon } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; @@ -50,7 +50,7 @@ export function FeaturedParks() { } }; - const FeaturedParkCard = ({ park, icon: Icon, label }: { park: Park; icon: any; label: string }) => ( + const FeaturedParkCard = ({ park, icon: Icon, label }: { park: Park; icon: LucideIcon; label: string }) => (
{/* Gradient Background */} diff --git a/src/components/lists/ListSearch.tsx b/src/components/lists/ListSearch.tsx index b366b1da..96ceed5c 100644 --- a/src/components/lists/ListSearch.tsx +++ b/src/components/lists/ListSearch.tsx @@ -71,12 +71,19 @@ export function ListSearch({ listType, onSelect, onClose }: ListSearchProps) { .limit(10); if (rides) { + interface RideSearchResult { + id: string; + name: string; + park?: { name: string } | null; + category?: string | null; + } + searchResults.push( - ...rides.map((ride: any) => ({ + ...rides.map((ride: RideSearchResult) => ({ id: ride.id, name: ride.name, type: "ride" as const, - subtitle: ride.park?.name || ride.category, + subtitle: ride.park?.name || ride.category || 'Unknown', })) ); } diff --git a/src/components/lists/UserListManager.tsx b/src/components/lists/UserListManager.tsx index 35d6fa82..5cdeab0d 100644 --- a/src/components/lists/UserListManager.tsx +++ b/src/components/lists/UserListManager.tsx @@ -69,7 +69,7 @@ export function UserListManager() { }); } else { // Map Supabase data to UserTopList interface - const mappedLists: UserTopList[] = (data || []).map((list: any) => ({ + const mappedLists: UserTopList[] = (data || []).map(list => ({ id: list.id, user_id: list.user_id, title: list.title, @@ -78,7 +78,16 @@ export function UserListManager() { is_public: list.is_public, created_at: list.created_at, updated_at: list.updated_at, - items: list.list_items || [], + items: (list.list_items || []).map(item => ({ + id: item.id, + list_id: list.id, // Add the parent list ID + entity_type: item.entity_type as 'park' | 'ride' | 'company', + entity_id: item.entity_id, + position: item.position, + notes: item.notes, + created_at: item.created_at || new Date().toISOString(), + updated_at: item.updated_at || new Date().toISOString(), + })), })); setLists(mappedLists); } diff --git a/supabase/functions/validate-email-backend/index.ts b/supabase/functions/validate-email-backend/index.ts index 041cf557..7cca29dc 100644 --- a/supabase/functions/validate-email-backend/index.ts +++ b/supabase/functions/validate-email-backend/index.ts @@ -1,5 +1,6 @@ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.3'; +import { edgeLogger } from "../_shared/logger.ts"; const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -96,16 +97,11 @@ serve(async (req) => { } catch (error) { const duration = endRequest(tracking); const errorMessage = error instanceof Error ? error.message : String(error); - // Note: Using console.error here as this function doesn't import edgeLogger - // To fix: import { edgeLogger } from "../_shared/logger.ts"; - console.error(JSON.stringify({ - timestamp: new Date().toISOString(), - level: 'error', - message: 'Email validation error', + edgeLogger.error('Email validation error', { error: errorMessage, requestId: tracking.requestId, duration - })); + }); return new Response( JSON.stringify({ valid: false, diff --git a/supabase/functions/validate-email/index.ts b/supabase/functions/validate-email/index.ts index ed1c7c57..f20ab46f 100644 --- a/supabase/functions/validate-email/index.ts +++ b/supabase/functions/validate-email/index.ts @@ -1,5 +1,5 @@ import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; -import { startRequest, endRequest } from "../_shared/logger.ts"; +import { startRequest, endRequest, edgeLogger } from "../_shared/logger.ts"; const corsHeaders = { 'Access-Control-Allow-Origin': '*', @@ -127,7 +127,10 @@ const handler = async (req: Request): Promise => { // Check if domain is disposable if (DISPOSABLE_DOMAINS.has(domain)) { - console.log(`Blocked disposable email domain: ${domain}`); + edgeLogger.info('Blocked disposable email domain', { + domain, + requestId: tracking.requestId + }); endRequest(tracking, 400, 'Disposable email domain blocked'); @@ -154,7 +157,10 @@ const handler = async (req: Request): Promise => { } // Email is valid - console.log(`Email validated successfully: ${email}`); + edgeLogger.info('Email validated successfully', { + email, + requestId: tracking.requestId + }); endRequest(tracking, 200); @@ -173,8 +179,12 @@ const handler = async (req: Request): Promise => { } ); - } catch (error: any) { - console.error('Error in validate-email function:', error); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + edgeLogger.error('Error in validate-email function', { + error: errorMessage, + requestId: tracking.requestId + }); endRequest(tracking, 500, error.message);