mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-27 09:26:58 -05:00
Add documentation to files
This commit is contained in:
614
docs/TROUBLESHOOTING.md
Normal file
614
docs/TROUBLESHOOTING.md
Normal file
@@ -0,0 +1,614 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user