mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 09:31:13 -05:00
615 lines
13 KiB
Markdown
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
|