mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:11:08 -05:00
- 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
1137 lines
33 KiB
Markdown
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)
|