2086 lines
44 KiB
Markdown

# 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 <div>{value}</div>;
};
// Right:
const Component = () => {
const [value, setValue] = useState<string | null>(null);
useEffect(() => {
setValue(localStorage.getItem('key'));
}, []);
return <div>{value}</div>;
};
```
**Solution 2: Consistent date formatting**
```typescript
// Wrong:
<div>{new Date().toLocaleString()}</div> // Varies by locale
// Right:
import dayjs from 'dayjs';
<div>{dayjs().format('YYYY-MM-DD HH:mm:ss')}</div>
```
**Solution 3: suppressHydrationWarning for known mismatches**
```typescript
// For values that intentionally differ (like timestamps)
<time suppressHydrationWarning>
{new Date().toISOString()}
</time>
```
**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
<!DOCTYPE html>
<html>
<body>
<h1>Hello {{name}}</h1>
<p>{{message}}</p>
</body>
</html>
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
<!-- templates/campaign-email.html -->
<h1>Hello {{firstName}}</h1>
<p>Your campaign "{{campaignName}}" is ready.</p>
<p>Visit: {{campaignUrl}}</p>
```
**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
<!-- In template, provide fallback -->
<h1>Hello {{firstName || 'Friend'}}</h1>
```
**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