Files
thrillwiki_django_no_react/docs/frontend.md
pacnpal 04394b9976 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
2025-08-30 07:31:58 -04:00

1137 lines
33 KiB
Markdown

# ThrillWiki Frontend API Documentation
Last updated: 2025-08-29
This document provides comprehensive documentation for all ThrillWiki API endpoints that the NextJS frontend should use.
## Authentication
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 ${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
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
The moderation system provides comprehensive content moderation, user management, and administrative tools. All moderation endpoints require moderator-level permissions or above.
### Moderation Reports
#### List Reports
- **GET** `/api/v1/moderation/reports/`
- **Permissions**: Moderators and above can view all reports, regular users can only view their own reports
- **Query Parameters**:
- `status`: Filter by report status (PENDING, UNDER_REVIEW, RESOLVED, DISMISSED)
- `priority`: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
- `report_type`: Filter by report type (SPAM, HARASSMENT, INAPPROPRIATE_CONTENT, etc.)
- `reported_by`: Filter by user ID who made the report
- `assigned_moderator`: Filter by assigned moderator ID
- `created_after`: Filter reports created after date (ISO format)
- `created_before`: Filter reports created before date (ISO format)
- `unassigned`: Boolean filter for unassigned reports
- `overdue`: Boolean filter for overdue reports based on SLA
- `search`: Search in reason and description fields
- `ordering`: Order by fields (created_at, updated_at, priority, status)
#### Create Report
- **POST** `/api/v1/moderation/reports/`
- **Permissions**: Any authenticated user
- **Body**: CreateModerationReportData
#### Get Report Details
- **GET** `/api/v1/moderation/reports/{id}/`
- **Permissions**: Moderators and above, or report creator
#### Update Report
- **PATCH** `/api/v1/moderation/reports/{id}/`
- **Permissions**: Moderators and above
- **Body**: Partial UpdateModerationReportData
#### Assign Report
- **POST** `/api/v1/moderation/reports/{id}/assign/`
- **Permissions**: Moderators and above
- **Body**: `{ "moderator_id": number }`
#### Resolve Report
- **POST** `/api/v1/moderation/reports/{id}/resolve/`
- **Permissions**: Moderators and above
- **Body**: `{ "resolution_action": string, "resolution_notes": string }`
#### Report Statistics
- **GET** `/api/v1/moderation/reports/stats/`
- **Permissions**: Moderators and above
- **Returns**: ModerationStatsData
### Moderation Queue
#### List Queue Items
- **GET** `/api/v1/moderation/queue/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `status`: Filter by status (PENDING, IN_PROGRESS, COMPLETED, CANCELLED)
- `priority`: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
- `item_type`: Filter by item type (CONTENT_REVIEW, USER_REVIEW, BULK_ACTION, etc.)
- `assigned_to`: Filter by assigned moderator ID
- `unassigned`: Boolean filter for unassigned items
- `has_related_report`: Boolean filter for items with related reports
- `search`: Search in title and description fields
#### Get My Queue
- **GET** `/api/v1/moderation/queue/my_queue/`
- **Permissions**: Moderators and above
- **Returns**: Queue items assigned to current user
#### Assign Queue Item
- **POST** `/api/v1/moderation/queue/{id}/assign/`
- **Permissions**: Moderators and above
- **Body**: `{ "moderator_id": number }`
#### Unassign Queue Item
- **POST** `/api/v1/moderation/queue/{id}/unassign/`
- **Permissions**: Moderators and above
#### Complete Queue Item
- **POST** `/api/v1/moderation/queue/{id}/complete/`
- **Permissions**: Moderators and above
- **Body**: CompleteQueueItemData
### Moderation Actions
#### List Actions
- **GET** `/api/v1/moderation/actions/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `action_type`: Filter by action type (WARNING, USER_SUSPENSION, USER_BAN, etc.)
- `moderator`: Filter by moderator ID
- `target_user`: Filter by target user ID
- `is_active`: Boolean filter for active actions
- `expired`: Boolean filter for expired actions
- `expiring_soon`: Boolean filter for actions expiring within 24 hours
- `has_related_report`: Boolean filter for actions with related reports
#### Create Action
- **POST** `/api/v1/moderation/actions/`
- **Permissions**: Moderators and above (with role-based restrictions)
- **Body**: CreateModerationActionData
#### Get Active Actions
- **GET** `/api/v1/moderation/actions/active/`
- **Permissions**: Moderators and above
#### Get Expired Actions
- **GET** `/api/v1/moderation/actions/expired/`
- **Permissions**: Moderators and above
#### Deactivate Action
- **POST** `/api/v1/moderation/actions/{id}/deactivate/`
- **Permissions**: Moderators and above
### Bulk Operations
#### List Bulk Operations
- **GET** `/api/v1/moderation/bulk-operations/`
- **Permissions**: Admins and superusers only
- **Query Parameters**:
- `status`: Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)
- `operation_type`: Filter by operation type
- `priority`: Filter by priority
- `created_by`: Filter by creator ID
- `can_cancel`: Boolean filter for cancellable operations
- `has_failures`: Boolean filter for operations with failures
- `in_progress`: Boolean filter for operations in progress
#### Create Bulk Operation
- **POST** `/api/v1/moderation/bulk-operations/`
- **Permissions**: Admins and superusers only
- **Body**: CreateBulkOperationData
#### Get Running Operations
- **GET** `/api/v1/moderation/bulk-operations/running/`
- **Permissions**: Admins and superusers only
#### Cancel Operation
- **POST** `/api/v1/moderation/bulk-operations/{id}/cancel/`
- **Permissions**: Admins and superusers only
#### Retry Operation
- **POST** `/api/v1/moderation/bulk-operations/{id}/retry/`
- **Permissions**: Admins and superusers only
#### Get Operation Logs
- **GET** `/api/v1/moderation/bulk-operations/{id}/logs/`
- **Permissions**: Admins and superusers only
### User Moderation
#### Get User Moderation Profile
- **GET** `/api/v1/moderation/users/{id}/`
- **Permissions**: Moderators and above
- **Returns**: UserModerationProfileData
#### Take Action Against User
- **POST** `/api/v1/moderation/users/{id}/moderate/`
- **Permissions**: Moderators and above
- **Body**: CreateModerationActionData
#### Search Users
- **GET** `/api/v1/moderation/users/search/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `query`: Search in username and email
- `role`: Filter by user role
- `has_restrictions`: Boolean filter for users with active restrictions
#### User Moderation Statistics
- **GET** `/api/v1/moderation/users/stats/`
- **Permissions**: Moderators and above
## Parks API
### Parks Listing
- **GET** `/api/v1/parks/`
- **Query Parameters**:
- `search`: Search in park names and descriptions
- `country`: Filter by country code
- `state`: Filter by state/province
- `city`: Filter by city
- `status`: Filter by operational status
- `park_type`: Filter by park type
- `has_rides`: Boolean filter for parks with rides
- `ordering`: Order by fields (name, opened_date, ride_count, etc.)
- `page`: Page number for pagination
- `page_size`: Number of results per page
### Park Details
- **GET** `/api/v1/parks/{slug}/`
- **Returns**: Complete park information including rides, photos, and statistics
### Park Rides
- **GET** `/api/v1/parks/{park_slug}/rides/`
- **Query Parameters**: Similar filtering options as global rides endpoint
### Park Photos
- **GET** `/api/v1/parks/{park_slug}/photos/`
- **Query Parameters**:
- `photo_type`: Filter by photo type (banner, card, gallery)
- `ordering`: Order by upload date, likes, etc.
## Rides API
### Rides Listing
- **GET** `/api/v1/rides/`
- **Query Parameters**:
- `search`: Search in ride names and descriptions
- `park`: Filter by park slug
- `manufacturer`: Filter by manufacturer slug
- `ride_type`: Filter by ride type
- `status`: Filter by operational status
- `opened_after`: Filter rides opened after date
- `opened_before`: Filter rides opened before date
- `height_min`: Minimum height requirement
- `height_max`: Maximum height requirement
- `has_photos`: Boolean filter for rides with photos
- `ordering`: Order by fields (name, opened_date, height, etc.)
### Ride Details
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/`
- **Returns**: Complete ride information including specifications, photos, and reviews
### Ride Photos
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/photos/`
### Ride Reviews
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/reviews/`
- **POST** `/api/v1/rides/{park_slug}/{ride_slug}/reviews/`
## Manufacturers API
### Manufacturers Listing
- **GET** `/api/v1/rides/manufacturers/`
- **Query Parameters**:
- `search`: Search in manufacturer names
- `country`: Filter by country
- `has_rides`: Boolean filter for manufacturers with rides
- `ordering`: Order by name, ride_count, etc.
### Manufacturer Details
- **GET** `/api/v1/rides/manufacturers/{slug}/`
### Manufacturer Rides
- **GET** `/api/v1/rides/manufacturers/{slug}/rides/`
## Authentication API
### Login
- **POST** `/api/v1/auth/login/`
- **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**:
```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/`
- **Body**: `{ "email": string }`
### Password Change
- **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
- **GET** `/api/v1/stats/`
- **Returns**: Global platform statistics
### Trending Content
- **GET** `/api/v1/trending/`
- **Query Parameters**:
- `content_type`: Filter by content type (parks, rides, reviews)
- `time_period`: Time period for trending (24h, 7d, 30d)
### Latest Reviews
- **GET** `/api/v1/reviews/latest/`
- **Query Parameters**:
- `limit`: Number of reviews to return
- `park`: Filter by park slug
- `ride`: Filter by ride slug
## Error Handling
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 {
status: "error";
error: {
code: string;
message: string;
details?: any;
request_user?: string;
};
data: null;
}
```
### 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
List endpoints use cursor-based pagination:
```typescript
interface PaginatedResponse<T> {
status: "success";
data: {
results: T[];
count: number;
next: string | null;
previous: string | null;
};
error: null;
}
```
## Rate Limiting
API endpoints are rate limited based on user role:
- Anonymous users: 100 requests/hour
- Authenticated users: 1000 requests/hour
- Moderators: 5000 requests/hour
- Admins: 10000 requests/hour
## WebSocket Connections
Real-time updates are available for:
- Moderation queue updates
- New reports and actions
- Bulk operation progress
- Live statistics updates
Connect to: `ws://localhost:8000/ws/moderation/` (requires authentication)