2086 lines
44 KiB
Markdown
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
|