Refactor user account system and remove moderation integration

- Remove first_name and last_name fields from User model
- Add user deletion and social provider services
- Restructure auth serializers into separate directory
- Update avatar upload functionality and API endpoints
- Remove django-moderation integration documentation
- Add mandatory compliance enforcement rules
- Update frontend documentation with API usage examples
This commit is contained in:
pacnpal
2025-08-30 07:31:58 -04:00
parent bb7da85516
commit 04394b9976
31 changed files with 7200 additions and 1297 deletions

View File

@@ -0,0 +1,107 @@
# Avatar Upload Debugging Guide
Last Updated: 2025-08-29
## Issue Summary
The avatar upload functionality was experiencing file corruption during transmission from the frontend to the backend. PNG files were being corrupted where the PNG header `\x89PNG` was being replaced with UTF-8 replacement characters `\xef\xbf\xbdPNG`.
## Root Cause Analysis
### Next.js Proxy Issue (ACTUAL ROOT CAUSE)
The primary issue was in the Next.js API proxy route that converts binary FormData to text:
**File:** `/Users/talor/dyad-apps/thrillwiki-real/src/app/api/[...path]/route.ts`
1. **Problematic code:**
```typescript
body = await clonedRequest.text(); // This corrupts binary data!
```
2. **Why this causes corruption:**
- FormData containing binary image files gets converted to UTF-8 text
- Binary bytes like `\x89` (PNG signature) become UTF-8 replacement characters `\xef\xbf\xbd`
- This mangles the file data before it even reaches the backend
## Solutions Implemented
### Next.js Proxy Fix (PRIMARY FIX)
**File:** `/Users/talor/dyad-apps/thrillwiki-real/src/app/api/[...path]/route.ts`
Fixed the proxy to preserve binary data instead of converting to text:
```typescript
// BEFORE (problematic)
body = await clonedRequest.text(); // Corrupts binary data!
// AFTER (fixed)
const arrayBuffer = await clonedRequest.arrayBuffer();
body = new Uint8Array(arrayBuffer); // Preserves binary data
```
**Why this works:**
- `arrayBuffer()` preserves the original binary data
- `Uint8Array` maintains the exact byte sequence
- No UTF-8 text conversion that corrupts binary files
### Backend Cleanup
**File:** `/Users/talor/thrillwiki_django_no_react/backend/apps/api/v1/serializers/accounts.py`
Simplified the avatar validation to basic file validation only:
```python
# Removed complex corruption repair logic
# Now just validates file size, format, and dimensions with PIL
def validate_avatar(self, value):
# Basic file validation only
# Let Cloudflare Images handle the rest
```
## Technical Details
### API Flow
1. **Frontend:** `AvatarUploader` → `useAuth.uploadAvatar()` → `accountApi.uploadAvatar(file)`
2. **Next.js Proxy:** Preserves binary data using `arrayBuffer()` instead of `text()`
3. **Backend:** `AvatarUploadView` → `AvatarUploadSerializer.validate_avatar()` → Cloudflare Images API
## Testing
### Manual Testing Steps
1. Select a PNG image file in the avatar uploader
2. Upload the file
3. Verify the upload succeeds without corruption
4. Check that the avatar displays correctly
## Prevention
### Frontend Best Practices
- Always pass `FormData` objects directly to API calls
- Avoid extracting and re-wrapping files unnecessarily
- Use proper file validation on the client side
### Backend Best Practices
- Use Django's proper file upload classes (`InMemoryUploadedFile`, `TemporaryUploadedFile`)
- Implement robust file validation and corruption detection
- Provide detailed logging for debugging file upload issues
## Related Files
### Frontend
- `/Users/talor/dyad-apps/thrillwiki-real/src/components/profile/avatar-uploader.tsx`
- `/Users/talor/dyad-apps/thrillwiki-real/src/hooks/use-auth.tsx`
- `/Users/talor/dyad-apps/thrillwiki-real/src/lib/api.ts`
### Backend
- `/Users/talor/thrillwiki_django_no_react/backend/apps/api/v1/serializers/accounts.py`
- `/Users/talor/thrillwiki_django_no_react/backend/apps/api/v1/accounts/views.py`
- `/Users/talor/thrillwiki_django_no_react/backend/apps/accounts/services.py`
## Status
**RESOLVED** - File corruption issue fixed in Next.js proxy
**CLEANED UP** - Removed unnecessary corruption repair code from backend
**DOCUMENTED** - Comprehensive debugging guide created
The avatar upload functionality should now work correctly without file corruption. The Next.js proxy properly preserves binary data, and the backend performs basic file validation only.

View File

@@ -6,18 +6,33 @@ This document provides comprehensive documentation for all ThrillWiki API endpoi
## Authentication
All API requests require authentication via JWT tokens. Include the token in the Authorization header:
ThrillWiki uses JWT Bearer token authentication. After successful login or signup, you'll receive access and refresh tokens that must be included in subsequent API requests.
### Authentication Headers
Include the access token in the Authorization header using Bearer format:
```typescript
headers: {
'Authorization': `Bearer ${token}`,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
```
### Token Management
- **Access Token**: Short-lived token (1 hour) used for API requests
- **Refresh Token**: Long-lived token (7 days) used to obtain new access tokens
- **Token Rotation**: Refresh tokens are rotated on each refresh for enhanced security
## Base URL
All API endpoints are prefixed with `/api/v1/`
The frontend uses a Next.js proxy that routes API requests:
- Frontend requests: `/v1/auth/login/`, `/v1/accounts/profile/`, etc.
- Proxy adds `/api/` prefix: `/api/v1/auth/login/`, `/api/v1/accounts/profile/`, etc.
- Backend receives: `/api/v1/auth/login/`, `/api/v1/accounts/profile/`, etc.
**Important**: Frontend code should make requests to `/v1/...` endpoints, not `/api/v1/...`
## Moderation System API
@@ -271,19 +286,389 @@ The moderation system provides comprehensive content moderation, user management
### Login
- **POST** `/api/v1/auth/login/`
- **Body**: `{ "username": string, "password": string }`
- **Body**: `{ "username": string, "password": string, "turnstile_token"?: string }`
- **Returns**: JWT tokens and user data
- **Response**:
```typescript
{
"access": string,
"refresh": string,
"user": {
"id": number,
"username": string,
"email": string,
"display_name": string,
"is_active": boolean,
"date_joined": string
},
"message": string
}
```
### Signup
- **POST** `/api/v1/auth/signup/`
- **Body**: User registration data
- **Body**:
```typescript
{
"username": string,
"email": string,
"password": string,
"password_confirm": string,
"display_name": string, // Required field
"turnstile_token"?: string
}
```
- **Returns**: JWT tokens and user data
- **Response**: Same format as login response (access and refresh tokens)
- **Note**: `display_name` is now required during registration. The system no longer uses separate first_name and last_name fields.
### Token Refresh
- **POST** `/api/v1/auth/token/refresh/`
- **Body**: `{ "refresh": string }`
- **Returns**: New access token and optionally a new refresh token
- **Response**:
```typescript
{
"access": string,
"refresh"?: string // Only returned if refresh token rotation is enabled
}
```
### Social Authentication
#### Google Login
- **POST** `/api/v1/auth/social/google/`
- **Body**: `{ "access_token": string }`
- **Returns**: JWT tokens and user data (same format as regular login)
- **Note**: The access_token should be obtained from Google OAuth flow
#### Discord Login
- **POST** `/api/v1/auth/social/discord/`
- **Body**: `{ "access_token": string }`
- **Returns**: JWT tokens and user data (same format as regular login)
- **Note**: The access_token should be obtained from Discord OAuth flow
#### Connect Social Account
- **POST** `/api/v1/auth/social/{provider}/connect/`
- **Permissions**: Authenticated users only
- **Body**: `{ "access_token": string }`
- **Returns**: `{ "success": boolean, "message": string }`
- **Note**: Links a social account to an existing ThrillWiki account
#### Disconnect Social Account
- **POST** `/api/v1/auth/social/{provider}/disconnect/`
- **Permissions**: Authenticated users only
- **Returns**: `{ "success": boolean, "message": string }`
#### Get Social Connections
- **GET** `/api/v1/auth/social/connections/`
- **Permissions**: Authenticated users only
- **Returns**:
```typescript
{
"google": { "connected": boolean, "email"?: string },
"discord": { "connected": boolean, "username"?: string }
}
```
### Social Provider Management
#### List Available Providers
- **GET** `/api/v1/auth/social/providers/available/`
- **Permissions**: Public access
- **Returns**: List of available social providers for connection
- **Response**:
```typescript
{
"available_providers": [
{
"id": "google",
"name": "Google",
"auth_url": "https://example.com/accounts/google/login/",
"connect_url": "https://example.com/api/v1/auth/social/connect/google/"
}
],
"count": number
}
```
#### List Connected Providers
- **GET** `/api/v1/auth/social/connected/`
- **Permissions**: Authenticated users only
- **Returns**: List of social providers connected to user's account
- **Response**:
```typescript
{
"connected_providers": [
{
"provider": "google",
"provider_name": "Google",
"uid": "user_id_on_provider",
"date_joined": "2025-01-01T00:00:00Z",
"can_disconnect": boolean,
"disconnect_reason": string | null,
"extra_data": object
}
],
"count": number,
"has_password_auth": boolean,
"can_disconnect_any": boolean
}
```
#### Connect Social Provider
- **POST** `/api/v1/auth/social/connect/{provider}/`
- **Permissions**: Authenticated users only
- **Parameters**: `provider` - Provider ID (e.g., 'google', 'discord')
- **Returns**: Connection initiation response with auth URL
- **Response**:
```typescript
{
"success": boolean,
"message": string,
"provider": string,
"auth_url": string
}
```
- **Error Responses**:
- `400`: Provider already connected or invalid provider
- `500`: Connection initiation failed
#### Disconnect Social Provider
- **DELETE** `/api/v1/auth/social/disconnect/{provider}/`
- **Permissions**: Authenticated users only
- **Parameters**: `provider` - Provider ID to disconnect
- **Returns**: Disconnection result with safety information
- **Response**:
```typescript
{
"success": boolean,
"message": string,
"provider": string,
"remaining_providers": string[],
"has_password_auth": boolean,
"suggestions"?: string[]
}
```
- **Error Responses**:
- `400`: Cannot disconnect (safety validation failed)
- `404`: Provider not connected
- `500`: Disconnection failed
#### Get Social Authentication Status
- **GET** `/api/v1/auth/social/status/`
- **Permissions**: Authenticated users only
- **Returns**: Comprehensive social authentication status
- **Response**:
```typescript
{
"user_id": number,
"username": string,
"email": string,
"has_password_auth": boolean,
"connected_providers": ConnectedProvider[],
"total_auth_methods": number,
"can_disconnect_any": boolean,
"requires_password_setup": boolean
}
```
### Social Provider Safety Rules
The social provider management system enforces strict safety rules to prevent users from locking themselves out:
1. **Disconnection Safety**: Users can only disconnect a social provider if they have:
- Another social provider connected, OR
- Email + password authentication set up
2. **Error Scenarios**:
- **Only Provider + No Password**: "Cannot disconnect your only authentication method. Please set up a password or connect another social provider first."
- **No Password Auth**: "Please set up email/password authentication before disconnecting this provider."
3. **Suggested Actions**: When disconnection is blocked, the API provides suggestions:
- "Set up password authentication"
- "Connect another social provider"
### Usage Examples
#### Check Social Provider Status
```typescript
const checkSocialStatus = async () => {
try {
const response = await fetch('/api/v1/auth/social/status/', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.requires_password_setup) {
// Show password setup prompt
showPasswordSetupModal();
}
return data;
} catch (error) {
console.error('Failed to get social status:', error);
}
};
```
#### Connect Social Provider
```typescript
const connectProvider = async (provider: string) => {
try {
const response = await fetch(`/api/v1/auth/social/connect/${provider}/`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
// Redirect to provider auth URL
window.location.href = data.auth_url;
}
} catch (error) {
console.error('Failed to connect provider:', error);
}
};
```
#### Disconnect Social Provider with Safety Check
```typescript
const disconnectProvider = async (provider: string) => {
try {
const response = await fetch(`/api/v1/auth/social/disconnect/${provider}/`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (!response.ok) {
if (response.status === 400) {
// Show safety warning with suggestions
showSafetyWarning(data.error, data.suggestions);
}
return;
}
// Success - update UI
toast.success(data.message);
refreshConnectedProviders();
} catch (error) {
console.error('Failed to disconnect provider:', error);
}
};
```
#### React Component Example
```typescript
import { useState, useEffect } from 'react';
interface SocialProvider {
provider: string;
provider_name: string;
can_disconnect: boolean;
disconnect_reason?: string;
}
const SocialProviderManager: React.FC = () => {
const [connectedProviders, setConnectedProviders] = useState<SocialProvider[]>([]);
const [hasPasswordAuth, setHasPasswordAuth] = useState(false);
useEffect(() => {
loadConnectedProviders();
}, []);
const loadConnectedProviders = async () => {
const response = await fetch('/api/v1/auth/social/connected/', {
headers: { 'Authorization': `Bearer ${accessToken}` }
});
const data = await response.json();
setConnectedProviders(data.connected_providers);
setHasPasswordAuth(data.has_password_auth);
};
const handleDisconnect = async (provider: string) => {
const response = await fetch(`/api/v1/auth/social/disconnect/${provider}/`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${accessToken}` }
});
if (!response.ok) {
const error = await response.json();
alert(error.error + '\n\nSuggestions:\n' + error.suggestions?.join('\n'));
return;
}
loadConnectedProviders(); // Refresh list
};
return (
<div className="space-y-4">
<h3>Connected Social Accounts</h3>
{!hasPasswordAuth && connectedProviders.length === 1 && (
<div className="bg-yellow-50 p-4 rounded-lg">
<p className="text-yellow-800">
⚠️ Set up a password to safely manage your social connections
</p>
</div>
)}
{connectedProviders.map((provider) => (
<div key={provider.provider} className="flex items-center justify-between p-4 border rounded">
<span>{provider.provider_name}</span>
<button
onClick={() => handleDisconnect(provider.provider)}
disabled={!provider.can_disconnect}
className={`px-4 py-2 rounded ${
provider.can_disconnect
? 'bg-red-600 text-white hover:bg-red-700'
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
}`}
title={provider.disconnect_reason}
>
Disconnect
</button>
</div>
))}
</div>
);
};
```
### Logout
- **POST** `/api/v1/auth/logout/`
- **Returns**: `{ "message": string }`
### Current User
- **GET** `/api/v1/auth/user/`
- **Returns**: Current user profile data
- **Response**:
```typescript
{
"id": number,
"username": string,
"email": string,
"display_name": string,
"is_active": boolean,
"date_joined": string
}
```
### Password Reset
- **POST** `/api/v1/auth/password/reset/`
@@ -293,6 +678,121 @@ The moderation system provides comprehensive content moderation, user management
- **POST** `/api/v1/auth/password/change/`
- **Body**: `{ "old_password": string, "new_password": string }`
## User Account Management API
### User Profile
- **GET** `/api/v1/accounts/profile/`
- **Permissions**: Authenticated users only
- **Returns**: Complete user profile including account details, preferences, and statistics
### Update Account
- **PATCH** `/api/v1/accounts/profile/account/`
- **Permissions**: Authenticated users only
- **Body**: `{ "display_name"?: string, "email"?: string }`
### Update Profile
- **PATCH** `/api/v1/accounts/profile/update/`
- **Permissions**: Authenticated users only
- **Body**: `{ "display_name"?: string, "pronouns"?: string, "bio"?: string, "twitter"?: string, "instagram"?: string, "youtube"?: string, "discord"?: string }`
### Avatar Upload
- **POST** `/api/v1/accounts/profile/avatar/upload/`
- **Permissions**: Authenticated users only
- **Content-Type**: `multipart/form-data`
- **Body**: FormData with `avatar` field containing image file (JPEG, PNG, WebP)
- **Returns**:
```typescript
{
"success": boolean,
"message": string,
"avatar_url": string,
"avatar_variants": {
"thumbnail": string, // 64x64
"avatar": string, // 200x200
"large": string // 400x400
}
}
```
**⚠️ CRITICAL AUTHENTICATION REQUIREMENT**:
- This endpoint requires authentication via JWT token in Authorization header
- **Common Issue**: "Authentication credentials were not provided" (401 error)
- **Root Cause**: User not logged in or JWT token not being sent
- **Debug Steps**: See `docs/avatar-upload-debugging.md` for comprehensive troubleshooting guide
**Usage Example**:
```typescript
// Ensure user is logged in first
const { user } = useAuth();
if (!user) {
// Redirect to login
return;
}
// Upload avatar
const file = event.target.files[0];
const response = await accountApi.uploadAvatar(file);
```
**Test Credentials** (for debugging):
- Username: `testuser`
- Password: `testpass123`
- Email: `test@example.com`
**Recent Fix (2025-08-29)**:
- Fixed file corruption issue where PNG headers were being corrupted during upload
- Frontend now passes FormData directly instead of extracting and re-wrapping files
- Backend includes corruption detection and repair mechanism
- See `docs/avatar-upload-debugging.md` for complete technical details
### Avatar Delete
- **DELETE** `/api/v1/accounts/profile/avatar/delete/`
- **Permissions**: Authenticated users only
- **Returns**: `{ "success": boolean, "message": string, "avatar_url": string }`
### User Preferences
- **GET** `/api/v1/accounts/preferences/`
- **PATCH** `/api/v1/accounts/preferences/update/`
- **Permissions**: Authenticated users only
### Notification Settings
- **GET** `/api/v1/accounts/settings/notifications/`
- **PATCH** `/api/v1/accounts/settings/notifications/update/`
- **Permissions**: Authenticated users only
### Privacy Settings
- **GET** `/api/v1/accounts/settings/privacy/`
- **PATCH** `/api/v1/accounts/settings/privacy/update/`
- **Permissions**: Authenticated users only
### Security Settings
- **GET** `/api/v1/accounts/settings/security/`
- **PATCH** `/api/v1/accounts/settings/security/update/`
- **Permissions**: Authenticated users only
### User Statistics
- **GET** `/api/v1/accounts/statistics/`
- **Permissions**: Authenticated users only
- **Returns**: User activity statistics, ride credits, contributions, and achievements
### Top Lists
- **GET** `/api/v1/accounts/top-lists/`
- **POST** `/api/v1/accounts/top-lists/create/`
- **PATCH** `/api/v1/accounts/top-lists/{id}/`
- **DELETE** `/api/v1/accounts/top-lists/{id}/delete/`
- **Permissions**: Authenticated users only
### Notifications
- **GET** `/api/v1/accounts/notifications/`
- **PATCH** `/api/v1/accounts/notifications/mark-read/`
- **Permissions**: Authenticated users only
### Account Deletion
- **POST** `/api/v1/accounts/delete-account/request/`
- **POST** `/api/v1/accounts/delete-account/verify/`
- **POST** `/api/v1/accounts/delete-account/cancel/`
- **Permissions**: Authenticated users only
## Statistics API
### Global Statistics
@@ -314,7 +814,9 @@ The moderation system provides comprehensive content moderation, user management
## Error Handling
All API endpoints return standardized error responses:
All API endpoints return standardized error responses. The system provides enhanced error handling with detailed messages, error codes, and contextual information.
### Standard Error Response Format
```typescript
interface ApiError {
@@ -329,12 +831,274 @@ interface ApiError {
}
```
Common error codes:
### Enhanced Error Response Format
For critical operations like account deletion, the API returns enhanced error responses with additional context:
```typescript
interface EnhancedApiError {
status: "error";
error: {
code: string;
message: string;
error_code: string;
user_info?: {
username: string;
role: string;
is_superuser: boolean;
is_staff: boolean;
};
help_text?: string;
details?: any;
request_user?: string;
};
data: null;
}
```
### Error Handling in React Components
Here's how to handle and display enhanced error messages in your NextJS components:
```typescript
import { useState } from 'react';
import { toast } from 'react-hot-toast';
interface ErrorDisplayProps {
error: EnhancedApiError | null;
onDismiss: () => void;
}
const ErrorDisplay: React.FC<ErrorDisplayProps> = ({ error, onDismiss }) => {
if (!error) return null;
const { error: errorData } = error;
return (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<div className="flex items-start">
<div className="flex-shrink-0">
<ExclamationTriangleIcon className="h-5 w-5 text-red-400" />
</div>
<div className="ml-3 flex-1">
<h3 className="text-sm font-medium text-red-800">
{errorData.message}
</h3>
{errorData.error_code && (
<p className="mt-1 text-xs text-red-600">
Error Code: {errorData.error_code}
</p>
)}
{errorData.user_info && (
<div className="mt-2 text-xs text-red-600">
<p>User: {errorData.user_info.username} ({errorData.user_info.role})</p>
{errorData.user_info.is_superuser && (
<p className="font-medium">⚠️ Superuser Account</p>
)}
</div>
)}
{errorData.help_text && (
<div className="mt-3 p-2 bg-red-100 rounded text-xs text-red-700">
<strong>Help:</strong> {errorData.help_text}
</div>
)}
</div>
<button
onClick={onDismiss}
className="ml-3 flex-shrink-0 text-red-400 hover:text-red-600"
>
<XMarkIcon className="h-4 w-4" />
</button>
</div>
</div>
);
};
// Usage in account deletion component
const AccountDeletionForm: React.FC = () => {
const [error, setError] = useState<EnhancedApiError | null>(null);
const [isLoading, setIsLoading] = useState(false);
const handleDeleteAccount = async () => {
setIsLoading(true);
setError(null);
try {
await api.accounts.requestAccountDeletion();
toast.success('Account deletion request submitted successfully');
} catch (err: any) {
if (err.response?.data) {
const apiError = err.response.data as EnhancedApiError;
setError(apiError);
// Also show toast for immediate feedback
toast.error(apiError.error.message);
// Log security-related errors for monitoring
if (apiError.error.error_code === 'SUPERUSER_DELETION_BLOCKED') {
console.warn('Superuser deletion attempt blocked:', {
user: apiError.error.user_info?.username,
timestamp: new Date().toISOString()
});
}
} else {
setError({
status: "error",
error: {
code: "UNKNOWN_ERROR",
message: "An unexpected error occurred",
error_code: "UNKNOWN_ERROR"
},
data: null
});
}
} finally {
setIsLoading(false);
}
};
return (
<div className="max-w-md mx-auto">
<ErrorDisplay
error={error}
onDismiss={() => setError(null)}
/>
<button
onClick={handleDeleteAccount}
disabled={isLoading}
className="w-full bg-red-600 text-white py-2 px-4 rounded hover:bg-red-700 disabled:opacity-50"
>
{isLoading ? 'Processing...' : 'Delete Account'}
</button>
</div>
);
};
```
### Toast Notifications for Errors
For immediate user feedback, combine detailed error displays with toast notifications:
```typescript
import { toast } from 'react-hot-toast';
const handleApiError = (error: EnhancedApiError) => {
const { error: errorData } = error;
// Show immediate toast
toast.error(errorData.message, {
duration: 5000,
position: 'top-right',
});
// For critical errors, show additional context
if (errorData.error_code === 'SUPERUSER_DELETION_BLOCKED') {
toast.error('Superuser accounts cannot be deleted for security reasons', {
duration: 8000,
icon: '🔒',
});
}
if (errorData.error_code === 'ADMIN_DELETION_BLOCKED') {
toast.error('Admin accounts with staff privileges cannot be deleted', {
duration: 8000,
icon: '⚠️',
});
}
};
```
### Error Boundary for Global Error Handling
Create an error boundary to catch and display API errors globally:
```typescript
import React from 'react';
interface ErrorBoundaryState {
hasError: boolean;
error: EnhancedApiError | null;
}
class ApiErrorBoundary extends React.Component<
React.PropsWithChildren<{}>,
ErrorBoundaryState
> {
constructor(props: React.PropsWithChildren<{}>) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: any): ErrorBoundaryState {
if (error.response?.data?.status === 'error') {
return {
hasError: true,
error: error.response.data as EnhancedApiError
};
}
return { hasError: true, error: null };
}
render() {
if (this.state.hasError && this.state.error) {
return (
<ErrorDisplay
error={this.state.error}
onDismiss={() => this.setState({ hasError: false, error: null })}
/>
);
}
return this.props.children;
}
}
```
### Common Error Codes
The system uses specific error codes for different scenarios:
- `NOT_AUTHENTICATED`: User not logged in
- `PERMISSION_DENIED`: Insufficient permissions
- `NOT_FOUND`: Resource not found
- `VALIDATION_ERROR`: Invalid request data
- `RATE_LIMITED`: Too many requests
- `SUPERUSER_DELETION_BLOCKED`: Superuser account deletion attempt
- `ADMIN_DELETION_BLOCKED`: Admin account with staff privileges deletion attempt
- `ACCOUNT_DELETION_FAILED`: General account deletion failure
- `SECURITY_VIOLATION`: Security policy violation detected
### Error Logging and Monitoring
For production applications, implement error logging:
```typescript
const logError = (error: EnhancedApiError, context: string) => {
// Log to your monitoring service (e.g., Sentry, LogRocket)
console.error(`API Error in ${context}:`, {
code: error.error.code,
message: error.error.message,
errorCode: error.error.error_code,
userInfo: error.error.user_info,
timestamp: new Date().toISOString(),
context
});
// Send to analytics if it's a security-related error
if (error.error.error_code?.includes('DELETION_BLOCKED')) {
// Track security events
analytics.track('Security Event', {
event: 'Account Deletion Blocked',
errorCode: error.error.error_code,
user: error.error.user_info?.username
});
}
};
```
## Pagination

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff