# Troubleshooting Guide Common issues and their solutions for ThrillWiki. --- ## Authentication Issues ### Cannot Sign In **Symptom:** Sign in button does nothing or shows error **Possible Causes:** 1. Invalid credentials 2. Email not verified 3. Account banned/deactivated 4. Supabase connection error **Solutions:** ```typescript // Check browser console for errors // Look for auth errors in console // Verify Supabase connection const { data, error } = await supabase.auth.getSession(); console.log('Session:', data, error); // Check if email verified (if required) SELECT email, email_confirmed_at FROM auth.users WHERE email = '[user-email]'; ``` ### MFA Not Working **Symptom:** MFA code not accepted or not prompted **Possible Causes:** 1. Time sync issue (TOTP codes are time-based) 2. Wrong factor ID 3. Challenge not created 4. AAL level not checked **Solutions:** ```typescript // Verify device time is correct (CRITICAL for TOTP) // Check enrolled factors const { data } = await supabase.auth.mfa.listFactors(); console.log('Enrolled factors:', data); // Create challenge manually const factor = data.totp[0]; const { data: challenge } = await supabase.auth.mfa.challenge({ factorId: factor.id }); // Verify code const { error } = await supabase.auth.mfa.verify({ factorId: factor.id, challengeId: challenge.id, code: '[6-digit-code]' }); ``` ### Session Expired **Symptom:** User logged out unexpectedly **Possible Causes:** 1. Session timeout (default 1 hour) 2. Token refresh failed 3. Multiple tabs/windows 4. AAL2 expired (MFA users) **Solutions:** ```typescript // Check session status const { data: { session } } = await supabase.auth.getSession(); if (!session) { // Redirect to login } // Refresh session manually const { data, error } = await supabase.auth.refreshSession(); // Monitor auth state changes supabase.auth.onAuthStateChange((event, session) => { console.log('Auth event:', event, session); }); ``` --- ## Submission Issues ### Submission Not Appearing in Queue **Symptom:** Created submission doesn't show in moderation queue **Possible Causes:** 1. Database write failed 2. Status filter hiding submission 3. RLS blocking view 4. Realtime subscription not connected **Solutions:** ```sql -- Check if submission created SELECT * FROM content_submissions WHERE user_id = '[user-id]' ORDER BY created_at DESC LIMIT 5; -- Check submission items SELECT * FROM submission_items WHERE submission_id = '[submission-id]'; -- Check RLS policies SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claims" = '{"sub": "[moderator-user-id]"}'; SELECT * FROM content_submissions WHERE status = 'pending'; ``` ### Cannot Approve Submission **Symptom:** Approve button disabled or approval fails **Possible Causes:** 1. Lock expired 2. No active lock 3. Dependencies not approved 4. Edge function error **Solutions:** ```typescript // Check lock status const { data: submission } = await supabase .from('content_submissions') .select('*, locked_until, assigned_to') .eq('id', submissionId) .single(); if (new Date(submission.locked_until) < new Date()) { console.error('Lock expired'); } // Check dependencies const { data: items } = await supabase .from('submission_items') .select('*, depends_on') .eq('submission_id', submissionId); const blocked = items.filter(item => item.depends_on && items.find(dep => dep.id === item.depends_on && dep.status !== 'approved') ); console.log('Blocked items:', blocked); ``` ### Image Upload Fails **Symptom:** Image upload error or stuck at uploading **Possible Causes:** 1. File too large (>10MB) 2. Invalid file type 3. CloudFlare API error 4. Network error 5. CORS issue **Solutions:** ```typescript // Check file size and type const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp']; if (file.size > MAX_FILE_SIZE) { throw new Error('File too large'); } if (!ALLOWED_TYPES.includes(file.type)) { throw new Error('Invalid file type'); } // Check CloudFlare credentials console.log('CF Account Hash:', import.meta.env.VITE_CLOUDFLARE_ACCOUNT_HASH); // Test edge function const { data, error } = await supabase.functions.invoke('upload-image', { body: { action: 'get-upload-url' } }); console.log('Upload URL response:', data, error); // Check network tab for CORS errors ``` --- ## Moderation Queue Issues ### Lock Stuck **Symptom:** Submission locked but moderator not actively reviewing **Possible Causes:** 1. Moderator closed browser without releasing lock 2. Lock extension failed 3. Database lock not expired **Solutions:** ```sql -- Find stuck locks SELECT id, submission_type, assigned_to, assigned_at, locked_until, EXTRACT(EPOCH FROM (NOW() - locked_until))/60 AS minutes_over FROM content_submissions WHERE locked_until IS NOT NULL AND locked_until < NOW() AND status = 'pending'; -- Release stuck lock (as admin) UPDATE content_submissions SET assigned_to = NULL, assigned_at = NULL, locked_until = NULL WHERE id = '[submission-id]'; ``` ### Realtime Not Working **Symptom:** Queue doesn't update with new submissions **Possible Causes:** 1. Realtime subscription not connected 2. RLS blocking realtime 3. Filter mismatch 4. Subscription channel wrong **Solutions:** ```typescript // Check subscription status const subscription = supabase .channel('moderation_queue') .on('postgres_changes', { event: '*', schema: 'public', table: 'content_submissions', filter: 'status=eq.pending' }, payload => { console.log('Realtime update:', payload); }) .subscribe((status) => { console.log('Subscription status:', status); }); // Verify RLS allows realtime SELECT * FROM content_submissions WHERE status = 'pending'; -- If this works, realtime should too ``` --- ## Versioning Issues ### Version Not Created **Symptom:** Entity updated but no version record **Possible Causes:** 1. Trigger not firing 2. Session variables not set 3. Trigger conditions not met 4. Database error **Solutions:** ```sql -- Check if trigger exists SELECT tgname, tgenabled FROM pg_trigger WHERE tgrelid = 'parks'::regclass; -- Check session variables during update SELECT current_setting('app.current_user_id', true) AS user_id, current_setting('app.submission_id', true) AS submission_id; -- Manual version creation (as admin) INSERT INTO park_versions ( park_id, version_number, created_by, change_type, name, slug, description, -- ... all fields ) SELECT id, COALESCE((SELECT MAX(version_number) FROM park_versions WHERE park_id = p.id), 0) + 1, '[user-id]', 'updated', name, slug, description -- ... all fields FROM parks p WHERE id = '[park-id]'; ``` ### Version Comparison Fails **Symptom:** Cannot compare versions or diff empty **Possible Causes:** 1. Version IDs invalid 2. Function error 3. No changes between versions **Solutions:** ```sql -- Verify versions exist SELECT version_id, version_number, change_type FROM park_versions WHERE park_id = '[park-id]' ORDER BY version_number DESC; -- Test diff function SELECT get_version_diff( 'park', '[from-version-id]'::UUID, '[to-version-id]'::UUID ); -- If null, no changes detected -- Check if fields actually different SELECT (SELECT name FROM park_versions WHERE version_id = '[from]') AS old_name, (SELECT name FROM park_versions WHERE version_id = '[to]') AS new_name; ``` --- ## Performance Issues ### Slow Queries **Symptom:** Pages load slowly, timeouts **Possible Causes:** 1. Missing indexes 2. N+1 queries 3. Large dataset 4. Complex joins **Solutions:** ```sql -- Find slow queries SELECT query, calls, total_exec_time, mean_exec_time, max_exec_time FROM pg_stat_statements WHERE mean_exec_time > 100 ORDER BY mean_exec_time DESC LIMIT 20; -- Add indexes CREATE INDEX idx_rides_park_status ON rides(park_id, status); CREATE INDEX idx_submissions_status_type ON content_submissions(status, submission_type); -- Analyze query plan EXPLAIN ANALYZE SELECT * FROM rides WHERE park_id = '[park-id]' AND status = 'operating'; ``` ### React Query Stale Data **Symptom:** UI shows old data after update **Possible Causes:** 1. Cache not invalidated 2. Stale time too long 3. Background refetch disabled **Solutions:** ```typescript // Invalidate specific queries after mutation const mutation = useMutation({ mutationFn: updatePark, onSuccess: () => { queryClient.invalidateQueries(['parks', parkId]); queryClient.invalidateQueries(['parks']); // List } }); // Force refetch await queryClient.refetchQueries(['parks']); // Disable cache for testing const { data } = useQuery({ queryKey: ['parks'], queryFn: fetchParks, staleTime: 0, gcTime: 0, }); ``` --- ## Build/Deploy Issues ### Build Fails **Symptom:** `npm run build` errors **Possible Causes:** 1. TypeScript errors 2. Missing dependencies 3. Environment variables missing 4. Out of memory **Solutions:** ```bash # Type check first npm run typecheck # Clear cache rm -rf node_modules dist .next npm install # Build with verbose logs npm run build -- --verbose # Increase memory limit NODE_OPTIONS=--max_old_space_size=4096 npm run build ``` ### Edge Function Fails **Symptom:** 500 error from edge function **Possible Causes:** 1. Secrets not set 2. Syntax error 3. Timeout 4. Memory limit **Solutions:** ```bash # Test locally supabase functions serve upload-image # Check logs supabase functions logs upload-image # Verify secrets supabase secrets list # Set secrets supabase secrets set CLOUDFLARE_API_TOKEN=[value] ``` --- ## Database Issues ### RLS Blocking Query **Symptom:** Query returns empty when data exists **Possible Causes:** 1. User not authenticated 2. Wrong user role 3. Policy conditions not met 4. AAL level insufficient **Solutions:** ```sql -- Test as authenticated user SET LOCAL ROLE authenticated; SET LOCAL "request.jwt.claims" = '{"sub": "[user-id]", "role": "authenticated"}'; -- Check policies SELECT * FROM pg_policies WHERE tablename = 'content_submissions'; -- Disable RLS temporarily (DEVELOPMENT ONLY) ALTER TABLE content_submissions DISABLE ROW LEVEL SECURITY; -- Remember to re-enable! ``` ### Migration Fails **Symptom:** `supabase db push` fails **Possible Causes:** 1. Conflicting constraints 2. Data violates new schema 3. Circular dependencies 4. Permission issues **Solutions:** ```sql -- Check for constraint violations SELECT * FROM parks WHERE name IS NULL OR name = ''; -- Fix data before migration UPDATE parks SET name = 'Unnamed Park' WHERE name IS NULL; -- Drop constraints temporarily ALTER TABLE parks DROP CONSTRAINT IF EXISTS parks_name_check; -- Recreate after data fixed ALTER TABLE parks ADD CONSTRAINT parks_name_check CHECK (name IS NOT NULL AND name != ''); ``` --- ## User Reports ### "I can't submit a park" **Checklist:** 1. Is user authenticated? 2. Is user banned? 3. Are all required fields filled? 4. Is form validation passing? 5. Check browser console for errors ### "My submission disappeared" **Checklist:** 1. Check submission status in database 2. Was it approved/rejected? 3. Check notification logs 4. Verify submission wasn't deleted ### "I can't see the moderation queue" **Checklist:** 1. Is user a moderator? 2. Is MFA completed (if enrolled)? 3. Check RLS policies 4. Verify user_roles table --- ## Emergency Procedures ### Database Locked Up ```sql -- Find blocking queries SELECT blocked_locks.pid AS blocked_pid, blocked_activity.usename AS blocked_user, blocking_locks.pid AS blocking_pid, blocking_activity.usename AS blocking_user, blocked_activity.query AS blocked_query, blocking_activity.query AS blocking_query FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_stat_activity blocked_activity ON blocked_activity.pid = blocked_locks.pid JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype JOIN pg_catalog.pg_stat_activity blocking_activity ON blocking_activity.pid = blocking_locks.pid WHERE NOT blocked_locks.granted; -- Kill blocking query (CAREFUL!) SELECT pg_terminate_backend([blocking_pid]); ``` ### Edge Function Infinite Loop ```bash # Stop all executions supabase functions delete [function-name] # Fix and redeploy supabase functions deploy [function-name] ``` ### Out of Database Connections ```sql -- Check active connections SELECT count(*) FROM pg_stat_activity; -- Kill idle connections SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE state = 'idle' AND state_change < NOW() - INTERVAL '5 minutes'; ``` --- ## Getting Help If stuck: 1. **Check Documentation** - [docs/](./docs/) folder - [API documentation](./versioning/API.md) 2. **Search Issues** - GitHub Issues - Supabase Discord - Stack Overflow 3. **Ask for Help** - Create detailed issue - Include error messages - Provide reproduction steps - Share relevant code/logs 4. **Contact Support** - For critical production issues - Email: [support email] --- **Last Updated:** 2025-01-20