- 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
33 KiB
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:
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 reportassigned_moderator: Filter by assigned moderator IDcreated_after: Filter reports created after date (ISO format)created_before: Filter reports created before date (ISO format)unassigned: Boolean filter for unassigned reportsoverdue: Boolean filter for overdue reports based on SLAsearch: Search in reason and description fieldsordering: 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 IDunassigned: Boolean filter for unassigned itemshas_related_report: Boolean filter for items with related reportssearch: 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 IDtarget_user: Filter by target user IDis_active: Boolean filter for active actionsexpired: Boolean filter for expired actionsexpiring_soon: Boolean filter for actions expiring within 24 hourshas_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 typepriority: Filter by prioritycreated_by: Filter by creator IDcan_cancel: Boolean filter for cancellable operationshas_failures: Boolean filter for operations with failuresin_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 emailrole: Filter by user rolehas_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 descriptionscountry: Filter by country codestate: Filter by state/provincecity: Filter by citystatus: Filter by operational statuspark_type: Filter by park typehas_rides: Boolean filter for parks with ridesordering: Order by fields (name, opened_date, ride_count, etc.)page: Page number for paginationpage_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 descriptionspark: Filter by park slugmanufacturer: Filter by manufacturer slugride_type: Filter by ride typestatus: Filter by operational statusopened_after: Filter rides opened after dateopened_before: Filter rides opened before dateheight_min: Minimum height requirementheight_max: Maximum height requirementhas_photos: Boolean filter for rides with photosordering: 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 namescountry: Filter by countryhas_rides: Boolean filter for manufacturers with ridesordering: 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:
{ "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:
{ "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_nameis 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:
{ "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:
{ "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:
{ "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:
{ "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:
{ "success": boolean, "message": string, "provider": string, "auth_url": string } - Error Responses:
400: Provider already connected or invalid provider500: 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:
{ "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 connected500: Disconnection failed
Get Social Authentication Status
- GET
/api/v1/auth/social/status/ - Permissions: Authenticated users only
- Returns: Comprehensive social authentication status
- Response:
{ "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:
-
Disconnection Safety: Users can only disconnect a social provider if they have:
- Another social provider connected, OR
- Email + password authentication set up
-
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."
-
Suggested Actions: When disconnection is blocked, the API provides suggestions:
- "Set up password authentication"
- "Connect another social provider"
Usage Examples
Check Social Provider Status
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
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
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
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:
{ "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
avatarfield containing image file (JPEG, PNG, WebP) - Returns:
{ "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.mdfor comprehensive troubleshooting guide
Usage Example:
// 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.mdfor 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 returnpark: Filter by park slugride: 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
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:
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:
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:
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:
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 inPERMISSION_DENIED: Insufficient permissionsNOT_FOUND: Resource not foundVALIDATION_ERROR: Invalid request dataRATE_LIMITED: Too many requestsSUPERUSER_DELETION_BLOCKED: Superuser account deletion attemptADMIN_DELETION_BLOCKED: Admin account with staff privileges deletion attemptACCOUNT_DELETION_FAILED: General account deletion failureSECURITY_VIOLATION: Security policy violation detected
Error Logging and Monitoring
For production applications, implement error logging:
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:
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)