Files
thrilltrack-explorer/docs/TROUBLESHOOTING.md
gpt-engineer-app[bot] bcba0a4f0c Add documentation to files
2025-10-21 14:41:42 +00:00

615 lines
13 KiB
Markdown

# 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