Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
288
docs/ERROR_HANDLING_QUICK_REFERENCE.md
Normal file
288
docs/ERROR_HANDLING_QUICK_REFERENCE.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# Error Handling Quick Reference
|
||||
|
||||
## Backend
|
||||
|
||||
### Import Error Handler
|
||||
```javascript
|
||||
const { ErrorResponses, asyncHandler, sanitizeError } = require('../utils/errorHandler');
|
||||
const logger = require('../utils/logger');
|
||||
```
|
||||
|
||||
### Standard Route Pattern
|
||||
```javascript
|
||||
router.get('/resource/:id', asyncHandler(async (req, res) => {
|
||||
const resource = await findResource(req.params.id);
|
||||
|
||||
if (!resource) {
|
||||
throw ErrorResponses.notFound('Resource not found');
|
||||
}
|
||||
|
||||
res.json(resource);
|
||||
}));
|
||||
```
|
||||
|
||||
### Error Types
|
||||
```javascript
|
||||
ErrorResponses.badRequest('Invalid input') // 400
|
||||
ErrorResponses.unauthorized('Auth required') // 401
|
||||
ErrorResponses.forbidden('No permission') // 403
|
||||
ErrorResponses.notFound('Not found') // 404
|
||||
ErrorResponses.conflict('Already exists') // 409
|
||||
ErrorResponses.unprocessable('Cannot process') // 422
|
||||
ErrorResponses.tooManyRequests('Rate limited') // 429
|
||||
ErrorResponses.internal('Server error') // 500
|
||||
ErrorResponses.serviceUnavailable('Unavailable') // 503
|
||||
```
|
||||
|
||||
### Logging
|
||||
```javascript
|
||||
// Standard logging
|
||||
logger.info('Operation completed', { userId, action });
|
||||
logger.error('Operation failed', { error, context });
|
||||
logger.warn('Unusual activity', { details });
|
||||
|
||||
// Security logging
|
||||
logger.security('login_attempt', { userId, ip, success: true });
|
||||
|
||||
// Performance logging
|
||||
const start = Date.now();
|
||||
await operation();
|
||||
logger.performance('operation_name', Date.now() - start, { details });
|
||||
```
|
||||
|
||||
### DO NOT
|
||||
```javascript
|
||||
// ❌ Bad - Exposes stack trace
|
||||
res.status(500).json({ error: error.message });
|
||||
|
||||
// ❌ Bad - Exposes file path
|
||||
console.error('Failed:', error.stack);
|
||||
|
||||
// ❌ Bad - Exposes database details
|
||||
res.json({ error: `Database error: ${err.code}` });
|
||||
|
||||
// ✅ Good - Sanitized
|
||||
throw ErrorResponses.internal('Operation failed');
|
||||
```
|
||||
|
||||
## Frontend
|
||||
|
||||
### Import Error Handler
|
||||
```javascript
|
||||
import { useErrorNotification } from '../components/ErrorNotificationProvider';
|
||||
import { getErrorMessage, isAuthError } from '../utils/errorHandler';
|
||||
```
|
||||
|
||||
### Component Pattern
|
||||
```javascript
|
||||
function MyComponent() {
|
||||
const { showError, showSuccess } = useErrorNotification();
|
||||
|
||||
const handleAction = async () => {
|
||||
try {
|
||||
const response = await api.post('/endpoint', data);
|
||||
showSuccess('Operation successful');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
// Or with custom message:
|
||||
showError(error, {
|
||||
defaultMessage: 'Custom error message',
|
||||
title: 'Operation Failed'
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Error Checking
|
||||
```javascript
|
||||
import { isAuthError, isPermissionError, isValidationError } from '../utils/errorHandler';
|
||||
|
||||
try {
|
||||
await api.post('/endpoint', data);
|
||||
} catch (error) {
|
||||
if (isAuthError(error)) {
|
||||
// Handle auth error
|
||||
} else if (isPermissionError(error)) {
|
||||
// Handle permission error
|
||||
} else {
|
||||
// Handle other errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retry Pattern
|
||||
```javascript
|
||||
import { retryRequest } from '../utils/errorHandler';
|
||||
|
||||
const data = await retryRequest(
|
||||
() => api.get('/endpoint'),
|
||||
{ maxRetries: 3, retryDelay: 1000 }
|
||||
);
|
||||
```
|
||||
|
||||
### Custom Error Messages
|
||||
```javascript
|
||||
const { showError, showWarning, showInfo } = useErrorNotification();
|
||||
|
||||
// Error notification
|
||||
showError(error, {
|
||||
defaultMessage: 'Failed to load data',
|
||||
duration: 6000
|
||||
});
|
||||
|
||||
// Warning notification
|
||||
showWarning('You are offline', {
|
||||
title: 'Connection Lost',
|
||||
duration: 5000
|
||||
});
|
||||
|
||||
// Info notification
|
||||
showInfo('Saving in progress...', {
|
||||
duration: 3000
|
||||
});
|
||||
```
|
||||
|
||||
## Translation Keys
|
||||
|
||||
### English (en.json)
|
||||
```json
|
||||
"errors": {
|
||||
"general": { "unexpected", "tryAgain", "contactSupport" },
|
||||
"network": { "title", "message", "timeout" },
|
||||
"auth": { "title", "required", "sessionExpired" },
|
||||
"permission": { "title", "message", "adminRequired" },
|
||||
"validation": { "title", "invalidInput" },
|
||||
"server": { "title", "unavailable" }
|
||||
}
|
||||
```
|
||||
|
||||
### Romanian (ro.json)
|
||||
All error keys have Romanian translations
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### File Upload with Error Handling
|
||||
```javascript
|
||||
// Backend
|
||||
router.post('/upload', asyncHandler(async (req, res) => {
|
||||
if (!req.files || !req.files.file) {
|
||||
throw ErrorResponses.badRequest('No file uploaded');
|
||||
}
|
||||
|
||||
const file = req.files.file;
|
||||
|
||||
if (file.size > MAX_SIZE) {
|
||||
throw ErrorResponses.badRequest('File too large');
|
||||
}
|
||||
|
||||
await saveFile(file);
|
||||
res.json({ success: true });
|
||||
}));
|
||||
|
||||
// Frontend
|
||||
const handleUpload = async (file) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
await api.post('/upload', formData);
|
||||
showSuccess('File uploaded successfully');
|
||||
} catch (error) {
|
||||
showError(error, { defaultMessage: 'Failed to upload file' });
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Database Query with Error Handling
|
||||
```javascript
|
||||
// Backend
|
||||
router.get('/users', asyncHandler(async (req, res) => {
|
||||
const users = await new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM users', [], (err, rows) => {
|
||||
if (err) reject(err);
|
||||
else resolve(rows);
|
||||
});
|
||||
});
|
||||
|
||||
res.json(users);
|
||||
}));
|
||||
// Errors are automatically caught and sanitized by asyncHandler
|
||||
```
|
||||
|
||||
### Form Validation
|
||||
```javascript
|
||||
// Backend
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
router.post('/users', [
|
||||
body('email').isEmail(),
|
||||
body('username').isLength({ min: 3 })
|
||||
], asyncHandler(async (req, res) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
throw ErrorResponses.badRequest('Validation failed');
|
||||
}
|
||||
|
||||
// Process valid data
|
||||
}));
|
||||
|
||||
// Frontend
|
||||
const handleSubmit = async (data) => {
|
||||
// Client-side validation
|
||||
if (!data.email) {
|
||||
showError({ response: { status: 400 } }, {
|
||||
defaultMessage: 'Email is required'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await api.post('/users', data);
|
||||
showSuccess('User created');
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Error Responses
|
||||
```bash
|
||||
# Should return sanitized error
|
||||
curl http://localhost:12345/api/nonexistent
|
||||
|
||||
# Should not expose stack traces or file paths
|
||||
curl http://localhost:12345/api/users -H "Authorization: Bearer invalid"
|
||||
|
||||
# Check logs (internal details preserved)
|
||||
docker exec streamflow tail -f /app/logs/error.log
|
||||
```
|
||||
|
||||
### Test Frontend Error Handling
|
||||
```javascript
|
||||
// In browser console
|
||||
try {
|
||||
await fetch('/api/nonexistent');
|
||||
} catch (error) {
|
||||
console.log('Error handled:', error);
|
||||
}
|
||||
// Should show user-friendly message, not technical details
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- ✅ Never send `error.stack` to client
|
||||
- ✅ Never expose file paths
|
||||
- ✅ Never expose database queries
|
||||
- ✅ Always use `asyncHandler` for async routes
|
||||
- ✅ Always use `ErrorResponses` for errors
|
||||
- ✅ Always sanitize user input
|
||||
- ✅ Log full details internally only
|
||||
- ✅ Use translations for user messages
|
||||
- ✅ Test in both development and production modes
|
||||
|
||||
## Need Help?
|
||||
|
||||
See full documentation: `docs/ERROR_HANDLING_SECURITY.md`
|
||||
Loading…
Add table
Add a link
Reference in a new issue