# Common Errors and Solutions This guide covers the most frequently encountered errors in Changemaker Lite V2 and their solutions. ## Overview ### How to Use This Guide 1. **Find your error** - Use the error code or message to locate the section 2. **Diagnose** - Read the symptoms and causes 3. **Apply solution** - Follow step-by-step instructions 4. **Prevent recurrence** - Implement preventive measures ### Error Severity Levels | Level | Icon | Meaning | Action | |-------|------|---------|--------| | **Critical** | 🔴 | System down or data at risk | Fix immediately | | **High** | 🟠 | Feature unavailable | Fix within hours | | **Medium** | 🟡 | Degraded performance | Fix within days | | **Low** | 🟢 | Minor inconvenience | Fix when convenient | ### Quick Error Lookup | Error Code | Category | Page | |------------|----------|------| | 401 | Authentication | [Link](#401-unauthorized) | | 403 | Authorization | [Link](#403-forbidden) | | 404 | Not Found | [Link](#404-not-found) | | 422 | Validation | [Link](#422-unprocessable-entity) | | 500 | Server Error | [Link](#500-internal-server-error) | | CORS | Frontend | [Link](#cors-errors) | | ECONNREFUSED | Database | [Link](#connection-refused) | --- ## Authentication Errors ### 401 Unauthorized **Severity:** 🟠 High #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid or missing token" } ``` Browser console: ``` Error: Request failed with status code 401 ``` #### Common Causes 1. **Missing token** - No Authorization header sent 2. **Expired token** - Access token older than 15 minutes 3. **Invalid token** - Corrupted or tampered token 4. **Wrong environment** - Token from dev used in production #### Solutions **Solution 1: Check if logged in** ```typescript // In browser console console.log(localStorage.getItem('auth-storage')); ``` If null or missing `accessToken`, you need to log in again. **Solution 2: Refresh token** The frontend automatically refreshes tokens. If this fails: 1. Log out completely 2. Clear localStorage: `localStorage.clear()` 3. Log in again **Solution 3: Verify API configuration** Check `admin/.env`: ```bash VITE_API_URL=http://localhost:4000 # Must match actual API URL ``` **Solution 4: Check token expiration** ```typescript // In browser console const storage = JSON.parse(localStorage.getItem('auth-storage')); const payload = JSON.parse(atob(storage.state.accessToken.split('.')[1])); console.log('Token expires:', new Date(payload.exp * 1000)); console.log('Current time:', new Date()); ``` If expired, the refresh interceptor should handle this. If not working: ```bash # Check API logs docker compose logs api | grep "refresh" ``` #### Prevention - **Auto-refresh works** - Frontend handles token refresh automatically - **Long sessions** - Refresh tokens valid for 7 days - **Activity-based** - Tokens refresh on API calls - **Clear error handling** - Frontend redirects to login on failure !!! warning "Security Note" 401 errors may return generic messages to prevent user enumeration. This is intentional security behavior. --- ### 403 Forbidden **Severity:** 🟠 High #### Symptoms ```json { "error": "Forbidden", "message": "Insufficient permissions" } ``` Or role-specific: ```json { "error": "Forbidden", "message": "Requires one of: SUPER_ADMIN, MAP_ADMIN" } ``` #### Common Causes 1. **Wrong role** - User lacks required role 2. **TEMP user** - Temporary users restricted from most features 3. **Feature disabled** - Feature flag not enabled 4. **Wrong endpoint** - Using admin endpoint as public user #### Solutions **Solution 1: Check user role** ```bash # In database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, role FROM \"User\" WHERE email = 'your@email.com';" ``` **Solution 2: Update user role** ```sql -- Via Prisma Studio (recommended) docker compose exec api npx prisma studio -- Navigate to User table, edit role -- Or via SQL docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "UPDATE \"User\" SET role = 'MAP_ADMIN' WHERE email = 'your@email.com';" ``` **Solution 3: Check feature flags** ```bash # In API logs docker compose logs api | grep "ENABLE_" # Check .env cat .env | grep ENABLE ``` **Solution 4: Verify endpoint permissions** Check `api/src/modules/*/routes.ts`: ```typescript // Admin endpoint router.post('/', authenticate, requireRole('SUPER_ADMIN'), ...); // Public endpoint (no auth) router.get('/public', ...); ``` #### Prevention - **Role-based access control** - Clear role hierarchy - **Explicit permissions** - Each endpoint lists required roles - **Audit trail** - Track permission changes - **Documentation** - Role matrix in [Access Control](../technical/api-reference.md#access-control) --- ### Invalid Token **Severity:** 🟠 High #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid token" } ``` Or in API logs: ``` Error: jwt malformed Error: invalid signature Error: jwt must be provided ``` #### Common Causes 1. **Corrupted token** - LocalStorage corruption 2. **Wrong secret** - JWT_ACCESS_SECRET changed 3. **Modified token** - Attempted tampering 4. **Format error** - Not a valid JWT structure #### Solutions **Solution 1: Clear and re-login** ```javascript // In browser console localStorage.clear(); // Then log in again ``` **Solution 2: Verify JWT structure** Valid JWT has 3 parts separated by dots: ```javascript const token = 'header.payload.signature'; console.log(token.split('.').length); // Should be 3 ``` **Solution 3: Check secret configuration** ```bash # In .env JWT_ACCESS_SECRET=your-secret-here-32-chars-min JWT_REFRESH_SECRET=different-secret-here-32-chars-min # Secrets must: # - Be different from each other # - Be at least 32 characters # - Remain unchanged (changing invalidates all tokens) ``` **Solution 4: Verify token in logs** ```bash # API logs show token validation errors docker compose logs api | tail -100 | grep "jwt" ``` #### Prevention - **Secure secrets** - Use `openssl rand -hex 32` - **Never commit secrets** - Keep in .env (gitignored) - **Rotate carefully** - Changing secrets logs out all users - **Monitor errors** - Alert on spike in invalid token errors --- ### Token Expired **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unauthorized", "message": "Token expired" } ``` Or: ``` Error: jwt expired ``` #### Common Causes 1. **Access token expired** - Normal after 15 minutes of inactivity 2. **Refresh token expired** - Refresh token older than 7 days 3. **System clock skew** - Server/client time mismatch 4. **Refresh failed** - Refresh token invalid or revoked #### Solutions **Solution 1: Automatic refresh** Frontend automatically refreshes tokens on 401. If this fails: ```javascript // Check refresh token in localStorage const storage = JSON.parse(localStorage.getItem('auth-storage')); console.log('Has refresh token:', !!storage?.state?.refreshToken); ``` **Solution 2: Manual login** If refresh token expired (after 7 days): 1. You'll be redirected to login automatically 2. Log in with email/password 3. New tokens issued **Solution 3: Check system time** ```bash # On server date # Sync if incorrect sudo ntpdate -s time.nist.gov ``` **Solution 4: Verify token expiration** ```bash # In API logs docker compose logs api | grep "expired" # Check token age docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, \"createdAt\", \"expiresAt\" FROM \"RefreshToken\" ORDER BY \"createdAt\" DESC LIMIT 10;" ``` #### Prevention - **Sliding sessions** - Tokens auto-refresh on activity - **Long refresh window** - 7-day refresh token validity - **Graceful handling** - Automatic re-login redirect - **Activity tracking** - Monitor token refresh patterns !!! tip "Developer Tip" During development, use longer token expiration in .env: ```bash JWT_ACCESS_EXPIRATION=1d # Instead of 15m JWT_REFRESH_EXPIRATION=30d # Instead of 7d ``` --- ### User Not Found **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unauthorized", "message": "Invalid credentials" } ``` Note: Same message for both "user not found" and "wrong password" (security feature). #### Common Causes 1. **Wrong email** - Typo in email address 2. **User deleted** - Account removed from database 3. **Wrong database** - Connected to wrong environment 4. **Case sensitivity** - Email stored differently #### Solutions **Solution 1: Verify user exists** ```bash # Check database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email, role FROM \"User\" WHERE email ILIKE '%search%';" ``` **Solution 2: Check email format** Emails are stored lowercase: ```sql -- Find user case-insensitive SELECT * FROM "User" WHERE LOWER(email) = LOWER('User@Example.com'); ``` **Solution 3: Create user if missing** ```bash # Via API curl -X POST http://localhost:4000/api/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "SecurePass123!", "name": "User Name" }' # Or via admin UI at /app/users ``` **Solution 4: Check database connection** ```bash # Verify correct database docker compose exec api npx prisma db pull # Check DATABASE_URL in .env cat .env | grep DATABASE_URL ``` #### Prevention - **Email validation** - Enforce valid email format - **Case normalization** - Store emails lowercase - **Soft deletes** - Consider flagging instead of deleting - **Audit trail** - Log user deletions --- ## API Errors ### 500 Internal Server Error **Severity:** 🔴 Critical #### Symptoms ```json { "error": "Internal Server Error", "message": "An unexpected error occurred" } ``` Or frontend error: ``` Error: Request failed with status code 500 ``` #### Common Causes 1. **Unhandled exception** - Code threw unexpected error 2. **Database error** - Query failed 3. **Missing environment variable** - Required config missing 4. **Type error** - Runtime type mismatch #### Solutions **Solution 1: Check API logs** ```bash # View recent logs docker compose logs api --tail=100 # Follow logs in real-time docker compose logs -f api # Search for errors docker compose logs api | grep -i error | tail -20 ``` **Solution 2: Common error patterns** ```javascript // Missing environment variable Error: SMTP_HOST is required // Solution: Add to .env // Database connection error Error: Can't reach database server at `v2-postgres:5432` // Solution: Check database is running // Type error TypeError: Cannot read property 'id' of undefined // Solution: Check code for null checks ``` **Solution 3: Restart API** ```bash # Restart API container docker compose restart api # Or rebuild if code changed docker compose up -d --build api ``` **Solution 4: Enable debug logging** ```bash # In .env LOG_LEVEL=debug # Restart API docker compose restart api # Check detailed logs docker compose logs api ``` #### Prevention - **Error handling** - Try/catch in all routes - **Input validation** - Validate all inputs with Zod - **Type safety** - Use TypeScript strictly - **Health checks** - Monitor API health - **Alerting** - Set up alerts for 500 errors !!! warning "Production Alert" 500 errors indicate bugs. Always investigate and fix root cause. --- ### 400 Bad Request **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Bad Request", "message": "Invalid request format" } ``` Or with validation details: ```json { "error": "Bad Request", "message": "Validation failed: 2 errors" } ``` #### Common Causes 1. **Invalid JSON** - Malformed request body 2. **Wrong Content-Type** - Missing or incorrect header 3. **Missing required field** - Required parameter not sent 4. **Invalid data type** - String sent for number field #### Solutions **Solution 1: Check request format** ```javascript // Correct format const response = await api.post('/api/users', { email: 'user@example.com', password: 'SecurePass123!', name: 'User Name' }, { headers: { 'Content-Type': 'application/json' } }); // Common mistakes: // ❌ Missing Content-Type header // ❌ Sending FormData to JSON endpoint // ❌ Malformed JSON (trailing comma, unquoted keys) ``` **Solution 2: Validate against schema** Check API schema in `api/src/modules/*/schemas.ts`: ```typescript // Example: User creation schema export const createUserSchema = z.object({ email: z.string().email(), password: z.string().min(12), name: z.string().min(1), role: z.enum(['USER', 'MAP_ADMIN', 'INFLUENCE_ADMIN', 'SUPER_ADMIN']).optional() }); ``` **Solution 3: Check API logs for details** ```bash # Logs show validation errors docker compose logs api | grep "Validation failed" # Example output: # Validation failed: { # "email": "Invalid email format", # "password": "Must be at least 12 characters" # } ``` **Solution 4: Test with curl** ```bash # Test request curl -X POST http://localhost:4000/api/users \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "email": "test@example.com", "password": "SecurePass123!", "name": "Test User" }' ``` #### Prevention - **Client-side validation** - Validate before sending - **TypeScript types** - Use generated types from API - **Schema documentation** - Document all endpoints - **Error messages** - Clear validation error messages --- ### 404 Not Found **Severity:** 🟢 Low to 🟡 Medium #### Symptoms ```json { "error": "Not Found", "message": "Resource not found" } ``` Or specific: ```json { "error": "Not Found", "message": "Campaign not found" } ``` #### Common Causes 1. **Wrong ID** - Resource doesn't exist 2. **Wrong URL** - Typo in endpoint path 3. **Deleted resource** - Resource was deleted 4. **Wrong HTTP method** - GET instead of POST #### Solutions **Solution 1: Verify resource exists** ```bash # Check database docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, name FROM \"Campaign\" WHERE id = 'YOUR_ID';" ``` **Solution 2: Check URL format** ```javascript // Correct formats GET /api/campaigns/:id // Single campaign GET /api/campaigns // List campaigns POST /api/campaigns // Create campaign PUT /api/campaigns/:id // Update campaign DELETE /api/campaigns/:id // Delete campaign // Common mistakes: // ❌ /api/campaign/:id (singular, should be plural) // ❌ /api/campaigns/id/:id (extra 'id/' in path) // ❌ /api/campaign (wrong singular/plural) ``` **Solution 3: Check route registration** ```bash # API logs show registered routes on startup docker compose logs api | grep "Registered route" # Or check routes file cat api/src/modules/*/routes.ts ``` **Solution 4: Test endpoint** ```bash # List all campaigns to verify endpoint curl http://localhost:4000/api/campaigns # Test specific ID curl http://localhost:4000/api/campaigns/YOUR_ID ``` #### Prevention - **UUID validation** - Validate ID format before querying - **Soft deletes** - Flag as deleted instead of removing - **Resource existence checks** - Verify before operations - **Clear error messages** - Specify which resource not found --- ### 422 Unprocessable Entity **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Unprocessable Entity", "message": "Validation failed", "details": { "email": "Email already exists", "password": "Must contain uppercase, lowercase, and digit" } } ``` #### Common Causes 1. **Business logic violation** - Email already exists 2. **Data integrity** - Foreign key doesn't exist 3. **Complex validation** - Password requirements not met 4. **State conflict** - Can't delete resource in use #### Solutions **Solution 1: Read validation details** The `details` field shows exactly what's wrong: ```javascript try { await api.post('/api/users', userData); } catch (error) { if (error.response?.status === 422) { console.log('Validation errors:', error.response.data.details); // Show to user field-by-field } } ``` **Solution 2: Check constraints** ```bash # Email uniqueness docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT email FROM \"User\" WHERE email = 'test@example.com';" # Foreign key exists docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id FROM \"Campaign\" WHERE id = 'CAMPAIGN_ID';" ``` **Solution 3: Fix data** Common fixes: ```javascript // Email already exists → Use different email email: 'newuser@example.com' // Password too weak → Meet requirements password: 'SecurePass123!' // 12+ chars, upper, lower, digit // Foreign key missing → Create parent first // Create campaign before creating email // Resource in use → Delete dependents first // Delete locations before deleting cut ``` **Solution 4: Check database schema** ```bash # View constraints docker compose exec api npx prisma studio # Navigate to model, see unique fields and relations ``` #### Prevention - **Client validation** - Check constraints before submitting - **Clear requirements** - Document validation rules - **Helpful messages** - Explain how to fix - **Cascade deletes** - Auto-delete dependents when safe --- ## Database Errors ### Connection Refused **Severity:** 🔴 Critical #### Symptoms ``` Error: connect ECONNREFUSED 127.0.0.1:5433 ``` Or: ``` Error: Can't reach database server at `v2-postgres:5432` ``` #### Common Causes 1. **Database not running** - Container stopped 2. **Wrong connection string** - Incorrect host/port 3. **Network issue** - Container can't reach database 4. **Port conflict** - Port already in use #### Solutions **Solution 1: Check database status** ```bash # List running containers docker compose ps # Database should show as "running" # If not: docker compose up -d v2-postgres ``` **Solution 2: Verify connection string** ```bash # Check .env cat .env | grep DATABASE_URL # Should be (from API container): DATABASE_URL="postgresql://changemaker:PASSWORD@v2-postgres:5432/changemaker_v2" # Or (from host): DATABASE_URL="postgresql://changemaker:PASSWORD@localhost:5433/changemaker_v2" ``` **Solution 3: Check database logs** ```bash # View database logs docker compose logs v2-postgres # Look for: # - "database system is ready to accept connections" (good) # - "FATAL: password authentication failed" (bad - wrong password) # - "port 5432 already in use" (bad - port conflict) ``` **Solution 4: Test connection manually** ```bash # From host docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 -c "SELECT NOW();" # Should return current timestamp # If fails, database isn't running properly ``` **Solution 5: Restart database** ```bash # Restart database container docker compose restart v2-postgres # Or recreate if corrupted docker compose down v2-postgres docker compose up -d v2-postgres # Wait for "ready to accept connections" message docker compose logs -f v2-postgres ``` #### Prevention - **Health checks** - Monitor database availability - **Auto-restart** - Configure restart policy - **Connection pooling** - Handle transient failures - **Alerting** - Alert on connection failures --- ### Too Many Connections **Severity:** 🟠 High #### Symptoms ``` Error: too many connections for database "changemaker_v2" ``` Or: ``` Error: Prepared statement "prisma_xxx" already exists ``` #### Common Causes 1. **Connection leak** - Connections not released 2. **Pool too small** - Not enough connections for load 3. **Long-running queries** - Blocking connections 4. **Multiple clients** - Too many Prisma instances #### Solutions **Solution 1: Check active connections** ```bash # View connections docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'changemaker_v2';" # PostgreSQL default max: 100 connections # Prisma default pool: 10 connections ``` **Solution 2: Kill idle connections** ```sql -- Find idle connections SELECT pid, usename, state, query_start FROM pg_stat_activity WHERE datname = 'changemaker_v2' AND state = 'idle'; -- Kill specific connection SELECT pg_terminate_backend(PID_HERE); -- Kill all idle connections (careful!) SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'changemaker_v2' AND state = 'idle'; ``` **Solution 3: Adjust connection pool** In `api/prisma/schema.prisma`: ```prisma datasource db { provider = "postgresql" url = env("DATABASE_URL") // Add connection pool config // connectionLimit = 10 // Default } ``` Or via DATABASE_URL: ```bash DATABASE_URL="postgresql://user:pass@host:5432/db?connection_limit=20" ``` **Solution 4: Restart API** ```bash # Restart releases all connections docker compose restart api # Check if connections cleared docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT count(*) FROM pg_stat_activity WHERE datname = 'changemaker_v2';" ``` **Solution 5: Increase PostgreSQL max connections** In `docker-compose.yml`: ```yaml v2-postgres: # ... command: postgres -c max_connections=200 ``` Then restart: ```bash docker compose up -d v2-postgres ``` #### Prevention - **Proper cleanup** - Always close Prisma clients - **Connection pooling** - Use appropriate pool size - **Monitor connections** - Alert on high usage - **Query optimization** - Reduce long-running queries --- ### Unique Constraint Violation **Severity:** 🟡 Medium #### Symptoms ``` Error: Unique constraint failed on the fields: (`email`) ``` Or: ``` PrismaClientKnownRequestError: Unique constraint failed on the constraint: `User_email_key` ``` #### Common Causes 1. **Duplicate email** - User already exists 2. **Race condition** - Two creates at same time 3. **Case sensitivity** - Email differs only in case 4. **Retry logic** - Request sent multiple times #### Solutions **Solution 1: Check existing records** ```bash # Find duplicate docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, email, \"createdAt\" FROM \"User\" WHERE email = 'duplicate@example.com';" ``` **Solution 2: Update instead of create** ```javascript // Instead of: await prisma.user.create({ data: { email, ... } }); // Use upsert: await prisma.user.upsert({ where: { email }, update: { name, ... }, create: { email, name, ... } }); ``` **Solution 3: Handle error gracefully** ```javascript try { await prisma.user.create({ data }); } catch (error) { if (error.code === 'P2002') { // Unique constraint violation const field = error.meta?.target?.[0]; throw new Error(`${field} already exists`); } throw error; } ``` **Solution 4: Delete duplicate** ```bash # If truly duplicate, delete one docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "DELETE FROM \"User\" WHERE id = 'ID_TO_DELETE';" ``` #### Prevention - **Check before create** - Query first to check existence - **Use upsert** - Update or create atomically - **Unique indexes** - Database enforces uniqueness - **Case normalization** - Store emails lowercase --- ### Foreign Key Constraint **Severity:** 🟡 Medium #### Symptoms ``` Error: Foreign key constraint failed on the field: `campaignId` ``` Or: ``` Error: An operation failed because it depends on one or more records that were required but not found. Record to update not found. ``` #### Common Causes 1. **Parent doesn't exist** - Referenced record missing 2. **Wrong ID** - Typo in foreign key value 3. **Delete order** - Trying to delete parent before children 4. **Null constraint** - Foreign key required but null provided #### Solutions **Solution 1: Verify parent exists** ```bash # Check campaign exists docker compose exec v2-postgres psql -U changemaker -d changemaker_v2 \ -c "SELECT id, name FROM \"Campaign\" WHERE id = 'CAMPAIGN_ID';" ``` **Solution 2: Create parent first** ```javascript // Create campaign first const campaign = await prisma.campaign.create({ data: { name: 'My Campaign', ... } }); // Then create email with campaignId const email = await prisma.campaignEmail.create({ data: { campaignId: campaign.id, // Use created campaign's ID ... } }); ``` **Solution 3: Delete children first** ```javascript // Delete all emails in campaign await prisma.campaignEmail.deleteMany({ where: { campaignId } }); // Then delete campaign await prisma.campaign.delete({ where: { id: campaignId } }); // Or use cascade delete in schema: // @@relation(onDelete: Cascade) ``` **Solution 4: Use transactions** ```javascript // Ensure atomicity await prisma.$transaction([ prisma.campaignEmail.deleteMany({ where: { campaignId } }), prisma.campaign.delete({ where: { id: campaignId } }) ]); ``` #### Prevention - **Cascade deletes** - Configure in schema where appropriate - **Soft deletes** - Flag as deleted instead of removing - **Validation** - Check foreign keys exist before creating - **Transactions** - Use for multi-step operations --- ## Frontend Errors ### Network Error **Severity:** 🟠 High #### Symptoms Browser console: ``` Error: Network Error ``` Or: ``` AxiosError: Request failed with status code undefined ``` User sees: API request fails, loading spinner never stops. #### Common Causes 1. **API down** - API container not running 2. **Wrong API URL** - VITE_API_URL misconfigured 3. **CORS issue** - Browser blocking request 4. **Network timeout** - Request taking too long #### Solutions **Solution 1: Check API status** ```bash # Is API running? docker compose ps api # Check API logs docker compose logs api --tail=50 # Test API directly curl http://localhost:4000/api/health ``` **Solution 2: Verify API URL** ```bash # Check admin .env cat admin/.env # Should have: VITE_API_URL=http://localhost:4000 # In Docker, use: VITE_API_URL=http://api:4000 ``` **Solution 3: Check browser console** Press F12, check: - **Network tab** - Does request appear? What's the status? - **Console tab** - Any CORS errors? **Solution 4: Test from different client** ```bash # From command line curl http://localhost:4000/api/campaigns # If this works but browser doesn't, it's a CORS issue ``` #### Prevention - **Health checks** - Monitor API availability - **Error boundaries** - Catch and display network errors - **Retry logic** - Auto-retry failed requests - **Offline detection** - Detect and handle offline state --- ### CORS Errors **Severity:** 🟠 High #### Symptoms Browser console: ``` Access to XMLHttpRequest at 'http://localhost:4000/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. ``` #### Common Causes 1. **Missing CORS config** - API not configured for CORS 2. **Wrong origin** - Admin URL not in allowed origins 3. **Credentials flag** - withCredentials set but not allowed 4. **Preflight failure** - OPTIONS request failing #### Solutions **Solution 1: Check API CORS configuration** In `api/src/server.ts`: ```typescript app.use(cors({ origin: process.env.CORS_ORIGINS?.split(',') || ['http://localhost:3000'], credentials: true })); ``` **Solution 2: Verify CORS_ORIGINS** ```bash # Check .env cat .env | grep CORS_ORIGINS # Should include admin URL: CORS_ORIGINS=http://localhost:3000,https://app.cmlite.org ``` **Solution 3: Add origin temporarily** For development: ```bash # In .env CORS_ORIGINS=* # Allow all origins (dev only!) # Restart API docker compose restart api ``` **Solution 4: Check preflight request** In browser Network tab: 1. Find OPTIONS request before actual request 2. Check if it returns 200 OK 3. Check response headers include: - Access-Control-Allow-Origin - Access-Control-Allow-Methods - Access-Control-Allow-Headers #### Prevention - **Explicit origins** - List all allowed origins - **Environment-based** - Different origins per environment - **Credentials support** - Enable if using cookies/auth - **Preflight caching** - Cache OPTIONS responses !!! warning "Security Note" Never use `CORS_ORIGINS=*` in production with credentials enabled. --- ### Module Not Found **Severity:** 🟡 Medium #### Symptoms ``` Error: Cannot find module '@/components/MyComponent' ``` Or: ``` Module not found: Can't resolve 'some-package' ``` #### Common Causes 1. **Missing dependency** - Package not installed 2. **Wrong import path** - Typo in path 3. **Path alias issue** - @ alias not configured 4. **Case sensitivity** - Wrong case in filename #### Solutions **Solution 1: Install missing package** ```bash cd admin # Install package npm install some-package # Or if dev dependency npm install -D some-package # Restart dev server npm run dev ``` **Solution 2: Check import path** ```typescript // Wrong: import MyComponent from '@/Component/MyComponent'; // Right: import MyComponent from '@/components/MyComponent'; // Verify file exists: // admin/src/components/MyComponent.tsx ``` **Solution 3: Verify path alias** In `admin/vite.config.ts`: ```typescript export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, './src') } } }); ``` In `admin/tsconfig.json`: ```json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` **Solution 4: Clear cache and reinstall** ```bash cd admin # Remove node_modules and lock file rm -rf node_modules package-lock.json # Reinstall npm install # Restart npm run dev ``` #### Prevention - **Type checking** - Use TypeScript for import validation - **IDE support** - Configure path aliases in IDE - **Linting** - Use ESLint with import plugin - **Documentation** - Document custom path aliases --- ### Hydration Errors **Severity:** 🟡 Medium #### Symptoms Browser console: ``` Warning: Text content did not match. Server: "..." Client: "..." ``` Or: ``` Error: Hydration failed because the initial UI does not match what was rendered on the server. ``` #### Common Causes 1. **Date formatting** - Server/client timezone difference 2. **Random values** - Using Math.random() or uuid 3. **localStorage** - Reading from localStorage during render 4. **User agent** - Checking window.navigator during SSR 5. **Third-party scripts** - Injected by browser extensions #### Solutions **Solution 1: Use useEffect for client-only code** ```typescript // Wrong: const Component = () => { const value = localStorage.getItem('key'); return
{value}
; }; // Right: const Component = () => { const [value, setValue] = useState(null); useEffect(() => { setValue(localStorage.getItem('key')); }, []); return
{value}
; }; ``` **Solution 2: Consistent date formatting** ```typescript // Wrong:
{new Date().toLocaleString()}
// Varies by locale // Right: import dayjs from 'dayjs';
{dayjs().format('YYYY-MM-DD HH:mm:ss')}
``` **Solution 3: suppressHydrationWarning for known mismatches** ```typescript // For values that intentionally differ (like timestamps) ``` **Solution 4: Check browser extensions** Disable browser extensions temporarily to see if error persists. #### Prevention - **Avoid client-only APIs during render** - Use useEffect - **Consistent formatting** - Same format server and client - **Test without extensions** - Regular testing - **React DevTools** - Use to identify mismatches !!! note "Changemaker Lite V2" Current admin is CSR (Client-Side Rendered) only, so hydration errors shouldn't occur. This section is for future SSR/SSG implementations. --- ## File Upload Errors ### File Too Large **Severity:** 🟡 Medium #### Symptoms ```json { "error": "Payload Too Large", "message": "File size exceeds maximum of 10485760 bytes" } ``` Or browser: ``` Request Entity Too Large ``` #### Common Causes 1. **File exceeds limit** - Video larger than 10GB 2. **Nginx limit** - Reverse proxy blocking 3. **Wrong content type** - Not multipart/form-data 4. **Network timeout** - Upload taking too long #### Solutions **Solution 1: Check file size** ```javascript // Before upload const file = event.target.files[0]; const maxSize = 10 * 1024 * 1024 * 1024; // 10GB if (file.size > maxSize) { alert(`File too large. Max size: ${maxSize / 1024 / 1024 / 1024}GB`); return; } ``` **Solution 2: Increase limits** In `api/src/modules/media/routes/upload.routes.ts`: ```typescript fastify.register(multipart, { limits: { fileSize: 10 * 1024 * 1024 * 1024 // 10GB } }); ``` In `nginx/conf.d/api.conf`: ```nginx client_max_body_size 10G; ``` **Solution 3: Use chunked upload** For very large files, implement resumable upload: ```typescript // TODO: Implement chunked upload in Phase 15 ``` **Solution 4: Compress video** ```bash # Before uploading, compress with ffmpeg ffmpeg -i input.mp4 -c:v libx264 -crf 23 -c:a aac output.mp4 ``` #### Prevention - **Client validation** - Check size before upload - **Progress indicator** - Show upload progress - **Compression** - Compress large videos - **Chunked uploads** - For files > 1GB --- ### Invalid File Type **Severity:** 🟢 Low #### Symptoms ```json { "error": "Bad Request", "message": "Invalid file type. Allowed: mp4, mov, avi, mkv, webm, m4v, flv" } ``` #### Common Causes 1. **Wrong extension** - File has unsupported extension 2. **Missing extension** - Filename has no extension 3. **Mismatched extension** - Extension doesn't match content 4. **MIME type issue** - Browser sends wrong MIME type #### Solutions **Solution 1: Check supported formats** Supported video formats: - MP4 (.mp4) - MOV (.mov) - AVI (.avi) - MKV (.mkv) - WebM (.webm) - M4V (.m4v) - FLV (.flv) **Solution 2: Convert video** ```bash # Convert to MP4 (most compatible) ffmpeg -i input.avi -c:v libx264 -c:a aac output.mp4 ``` **Solution 3: Check file extension** ```javascript const file = event.target.files[0]; const ext = file.name.split('.').pop().toLowerCase(); const allowed = ['mp4', 'mov', 'avi', 'mkv', 'webm', 'm4v', 'flv']; if (!allowed.includes(ext)) { alert(`Invalid file type: .${ext}`); return; } ``` **Solution 4: Verify with file command** ```bash # Check actual file type file video.mp4 # Should show: # video.mp4: ISO Media, MP4 v2 [ISO 14496-14] ``` #### Prevention - **Client validation** - Check extension before upload - **MIME type checking** - Validate content type - **File magic numbers** - Check file signature - **Clear documentation** - List supported formats --- ### Upload Timeout **Severity:** 🟡 Medium #### Symptoms ``` Error: timeout of 30000ms exceeded ``` Or: ``` 504 Gateway Timeout ``` #### Common Causes 1. **Slow network** - Large file, slow connection 2. **Server timeout** - Request timeout too short 3. **Processing delay** - FFprobe taking too long 4. **Network interruption** - Connection dropped #### Solutions **Solution 1: Increase timeout** In `admin/src/lib/media-api.ts`: ```typescript export const mediaApi = axios.create({ baseURL: import.meta.env.VITE_MEDIA_API_URL, timeout: 300000 // 5 minutes instead of 30 seconds }); ``` **Solution 2: Check upload progress** ```javascript await mediaApi.post('/upload', formData, { onUploadProgress: (progressEvent) => { const percent = (progressEvent.loaded / progressEvent.total) * 100; console.log(`Upload: ${percent.toFixed(2)}%`); } }); ``` **Solution 3: Increase nginx timeout** In `nginx/conf.d/api.conf`: ```nginx proxy_read_timeout 300s; proxy_connect_timeout 300s; proxy_send_timeout 300s; ``` **Solution 4: Upload via chunks** ```typescript // TODO: Implement chunked upload for large files ``` #### Prevention - **Progress indicator** - Show upload progress - **Generous timeouts** - Allow enough time for large files - **Retry logic** - Auto-retry on network errors - **Chunked uploads** - For files > 1GB --- ## Email Errors ### SMTP Connection Failed **Severity:** 🔴 Critical #### Symptoms API logs: ``` Error: Connection timeout Error: connect ECONNREFUSED 127.0.0.1:587 ``` Or: ``` Error: Invalid login: 535-5.7.8 Username and Password not accepted ``` #### Common Causes 1. **SMTP server down** - Mail server unreachable 2. **Wrong credentials** - Invalid username/password 3. **Port blocked** - Firewall blocking SMTP port 4. **TLS/SSL issue** - Certificate validation failed #### Solutions **Solution 1: Test SMTP connection** ```bash # Test with telnet telnet smtp.gmail.com 587 # Should connect and show: # 220 smtp.gmail.com ESMTP... ``` **Solution 2: Verify SMTP configuration** ```bash # Check .env cat .env | grep SMTP # Required settings: SMTP_HOST=smtp.gmail.com SMTP_PORT=587 SMTP_SECURE=false SMTP_USER=your-email@gmail.com SMTP_PASS=your-app-password SMTP_FROM=your-email@gmail.com ``` **Solution 3: Use test mode** ```bash # In .env EMAIL_TEST_MODE=true # Restart API docker compose restart api # Emails now sent to MailHog (http://localhost:8025) ``` **Solution 4: Check Gmail app password** For Gmail: 1. Enable 2-factor authentication 2. Generate app password at https://myaccount.google.com/apppasswords 3. Use app password (not regular password) in SMTP_PASS **Solution 5: Test with curl** ```bash # Send test email via API curl -X POST http://localhost:4000/api/test-email \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_TOKEN" \ -d '{ "to": "test@example.com", "subject": "Test Email", "text": "This is a test" }' ``` #### Prevention - **Test mode for dev** - Use MailHog locally - **Monitor SMTP health** - Alert on connection failures - **Fallback providers** - Configure backup SMTP server - **Queue system** - BullMQ retries failed emails --- ### Template Not Found **Severity:** 🟠 High #### Symptoms API logs: ``` Error: Email template not found: campaign-email ``` Or: ``` Error: ENOENT: no such file or directory, open 'templates/campaign-email.html' ``` #### Common Causes 1. **Missing template file** - Template not created 2. **Wrong template name** - Typo in template name 3. **Wrong path** - Looking in wrong directory 4. **Deleted template** - Template was removed #### Solutions **Solution 1: Check template exists** ```bash # List all templates docker compose exec api ls -la templates/ # Should show: # campaign-email.html # shift-confirmation.html # verification-email.html # etc. ``` **Solution 2: Verify template name** In `api/src/services/email.service.ts`: ```typescript // Template names must match filenames (without .html) await emailService.sendEmail({ to: email, subject: 'Campaign Email', template: 'campaign-email', // Looks for templates/campaign-email.html variables: { ... } }); ``` **Solution 3: Create missing template** ```bash # Create template docker compose exec api sh -c 'cat > templates/my-template.html << EOF

Hello {{name}}

{{message}}

EOF' ``` **Solution 4: Use email template system** ```bash # Navigate to admin UI http://localhost:3000/app/email-templates # Create template there (saved to database + file) ``` #### Prevention - **Seed templates** - Include in database seed - **Template management** - Use admin UI to manage - **Version control** - Keep templates in git - **Validation** - Check template exists before sending --- ### Variable Missing **Severity:** 🟡 Medium #### Symptoms Email received with placeholders not replaced: ``` Hello {{name}}, Your campaign {{campaignName}} is ready. ``` Or API logs: ``` Warning: Template variable 'campaignName' not provided ``` #### Common Causes 1. **Variable not passed** - Missing from variables object 2. **Variable name mismatch** - Typo in variable name 3. **Wrong template** - Using wrong template 4. **Case sensitivity** - Variable name case mismatch #### Solutions **Solution 1: Check template variables** In template file: ```html

Hello {{firstName}}

Your campaign "{{campaignName}}" is ready.

Visit: {{campaignUrl}}

``` **Solution 2: Provide all variables** ```typescript await emailService.sendEmail({ to: email, subject: 'Campaign Ready', template: 'campaign-email', variables: { firstName: user.name.split(' ')[0], campaignName: campaign.name, campaignUrl: `${process.env.PUBLIC_URL}/campaigns/${campaign.id}` } }); ``` **Solution 3: Use default values** ```html

Hello {{firstName || 'Friend'}}

``` **Solution 4: Validate before sending** ```typescript // Check all required variables exist const required = ['firstName', 'campaignName', 'campaignUrl']; const missing = required.filter(key => !variables[key]); if (missing.length > 0) { throw new Error(`Missing template variables: ${missing.join(', ')}`); } ``` #### Prevention - **Template validation** - Check variables on save - **TypeScript types** - Type template variables - **Documentation** - Document required variables - **Default values** - Provide sensible defaults --- ## Quick Reference Table | Error Code/Message | Category | Common Cause | Quick Fix | Severity | |-------------------|----------|--------------|-----------|----------| | 401 Unauthorized | Auth | Token expired | Re-login | 🟠 | | 403 Forbidden | Auth | Wrong role | Check user role | 🟠 | | 404 Not Found | API | Wrong URL/ID | Verify resource exists | 🟢 | | 422 Unprocessable | Validation | Constraint violation | Check validation details | 🟡 | | 500 Server Error | API | Code bug | Check API logs | 🔴 | | ECONNREFUSED | Database | DB not running | Start database | 🔴 | | Too many connections | Database | Connection leak | Restart API | 🟠 | | Unique constraint | Database | Duplicate record | Use upsert or different value | 🟡 | | Foreign key constraint | Database | Parent missing | Create parent first | 🟡 | | Network Error | Frontend | API down | Check API status | 🟠 | | CORS Error | Frontend | Origin not allowed | Add to CORS_ORIGINS | 🟠 | | Module not found | Frontend | Missing package | npm install | 🟡 | | File too large | Upload | Exceeds 10GB | Compress or increase limit | 🟡 | | Invalid file type | Upload | Wrong format | Convert to MP4 | 🟢 | | Upload timeout | Upload | Slow network | Increase timeout | 🟡 | | SMTP failed | Email | Wrong credentials | Check SMTP config | 🔴 | | Template not found | Email | Missing file | Create template | 🟠 | | Variable missing | Email | Not provided | Add to variables object | 🟡 | --- ## When to Report Bugs ### Report These ✅ **Unexpected behavior** - System does something wrong - 500 errors (unless caused by your config) - Data corruption - Security vulnerabilities - Performance regressions ✅ **Missing features** - Documented feature doesn't work - API endpoint returns 404 but is documented - UI button does nothing - Feature flag doesn't enable feature ✅ **Unclear documentation** - Can't figure out how to do something - Documentation contradicts actual behavior - Missing setup steps - Confusing error messages ### Don't Report These ❌ **Configuration errors** - Your setup is wrong - Missing .env variables - Wrong database credentials - Port conflicts ❌ **Environment issues** - Your system is incompatible - Old Docker version - Missing dependencies - Network restrictions ❌ **User errors** - Misunderstanding how to use - Wrong API endpoint used - Invalid data format - Permission errors from lack of role ### How to Report 1. **Check this troubleshooting guide first** 2. **Search existing GitHub issues** 3. **If new, create issue with:** - Clear title describing problem - Steps to reproduce - Expected vs actual behavior - Relevant logs (sanitize sensitive data) - System information (Docker version, OS, etc.) --- ## Related Documentation ### General Documentation - [Installation Guide](../user/installation.md) - Setup instructions - [Architecture Overview](../technical/architecture.md) - System design - [API Reference](../technical/api-reference.md) - API endpoints ### Specific Troubleshooting - [Docker Issues](docker-issues.md) - Container problems - [Database Issues](database-issues.md) - PostgreSQL errors - [Auth Issues](auth-issues.md) - Authentication problems - [Geocoding Issues](geocoding-issues.md) - Map and geocoding - [Email Issues](email-issues.md) - SMTP and templates - [Monitoring Issues](monitoring-issues.md) - Prometheus and Grafana ### Support - [FAQ](faq.md) - Frequently asked questions - [Performance Optimization](performance-optimization.md) - Speed improvements - [GitHub Issues](https://github.com/yourusername/changemaker.lite/issues) - Report bugs --- **Last Updated:** February 2026 **Version:** V2.0 **Status:** Complete