# ThrillWiki Frontend Integration Guide **Last Updated:** 2025-01-15 **IMPORTANT:** All tuple-based choice patterns have been migrated to Rich Choice Objects system as of 2025-01-15. This document provides comprehensive information for frontend developers integrating with the ThrillWiki Django backend API. It covers all endpoints, data structures, authentication, and integration patterns. ## Table of Contents 1. [API Overview](#api-overview) 2. [Authentication](#authentication) 3. [Rich Choice Objects](#rich-choice-objects) 4. [Core Endpoints](#core-endpoints) 5. [Data Models](#data-models) 6. [Error Handling](#error-handling) 7. [Integration Examples](#integration-examples) ## API Overview The ThrillWiki API is built using Django REST Framework with comprehensive OpenAPI documentation. All endpoints follow RESTful conventions and return JSON responses. **Base URL:** `https://api.thrillwiki.com/api/v1/` **Documentation:** `https://api.thrillwiki.com/api/schema/swagger-ui/` ### Key Features - **Rich Choice Objects**: Enhanced choice fields with metadata, colors, icons, and descriptions - **Comprehensive Filtering**: Advanced filtering and search capabilities - **Real-time Updates**: WebSocket support for live data updates - **Media Integration**: Cloudflare Images integration for optimized photo delivery - **Geographic Data**: PostGIS integration for location-based features ## Authentication The API uses token-based authentication with support for both session and JWT tokens. ### Authentication Headers ```javascript // For API requests headers: { 'Authorization': 'Token your-api-token-here', 'Content-Type': 'application/json' } // For JWT (if using) headers: { 'Authorization': 'Bearer your-jwt-token-here', 'Content-Type': 'application/json' } ``` ### Login Endpoint ```javascript POST /api/v1/auth/login/ { "username": "your-username", "password": "your-password" } // Response { "key": "your-api-token", "user": { "user_id": "1234", "username": "thrillseeker", "email": "user@example.com", "role": "USER", "theme_preference": "dark" } } ``` ## Rich Choice Objects **CRITICAL:** ThrillWiki uses a Rich Choice Objects system instead of simple string choices. All choice fields return enhanced objects with metadata. ### Choice Field Structure ```typescript interface RichChoice { value: string; // The actual value stored in database label: string; // Human-readable label description: string; // Detailed description deprecated: boolean; // Whether this choice is deprecated sort_order: number; // Display order category: string; // Choice category metadata: { color: string; // UI color (e.g., "blue", "red") icon: string; // Icon name (e.g., "user", "shield-check") css_class: string; // CSS classes for styling [key: string]: any; // Additional metadata }; } ``` ### Using Rich Choices in Frontend ```javascript // When displaying choice values, use the rich choice data const userRole = user.get_role_rich_choice(); console.log(userRole.label); // "Administrator" console.log(userRole.metadata.color); // "purple" console.log(userRole.metadata.icon); // "cog" // For forms, you can get all available choices const roleChoices = await fetch('/api/v1/choices/accounts/user_roles/'); // Returns array of RichChoice objects ``` ### Available Choice Groups #### Accounts Domain - `user_roles`: USER, MODERATOR, ADMIN, SUPERUSER - `theme_preferences`: light, dark - `privacy_levels`: public, friends, private - `top_list_categories`: RC, DR, FR, WR, PK - `notification_types`: submission_approved, review_helpful, etc. - `notification_priorities`: low, normal, high, urgent #### Parks Domain - `statuses`: OPERATING, CLOSED_TEMP, CLOSED_PERM, etc. - `types`: THEME_PARK, AMUSEMENT_PARK, WATER_PARK, etc. - `company_roles`: OPERATOR, PROPERTY_OWNER #### Rides Domain - `statuses`: OPERATING, CLOSED_TEMP, SBNO, etc. - `categories`: RC, DR, FR, WR, TR, OT - `company_roles`: MANUFACTURER, DESIGNER - `track_materials`: STEEL, WOOD, HYBRID ## Core Endpoints ### Parks API #### List Parks ```javascript GET /api/v1/parks/ GET /api/v1/parks/?search=cedar&status=OPERATING&park_type=THEME_PARK // Response { "count": 150, "next": "https://api.thrillwiki.com/api/v1/parks/?page=2", "previous": null, "results": [ { "id": 1, "name": "Cedar Point", "slug": "cedar-point", "description": "Roller coaster capital of the world", "park_type": { "value": "THEME_PARK", "label": "Theme Park", "metadata": { "color": "blue", "icon": "castle" } }, "status": { "value": "OPERATING", "label": "Operating", "metadata": { "color": "green", "icon": "check-circle" } }, "location": { "city": "Sandusky", "state": "OH", "country": "USA", "coordinates": { "latitude": 41.4814, "longitude": -82.6838 } }, "operator": { "name": "Cedar Fair", "slug": "cedar-fair" }, "stats": { "ride_count": 72, "coaster_count": 17, "average_rating": 8.7 } } ] } ``` #### Park Detail ```javascript GET /api/v1/parks/cedar-point/ // Response includes full park details, rides, areas, photos, reviews { "id": 1, "name": "Cedar Point", "slug": "cedar-point", "description": "America's Roller Coast...", "park_type": { /* Rich Choice Object */ }, "status": { /* Rich Choice Object */ }, "opening_date": "1870-05-30", "size_acres": "364.00", "location": { /* Full location data */ }, "operator": { /* Company details */ }, "areas": [ { "id": 1, "name": "Main Midway", "description": "The heart of Cedar Point" } ], "rides": [ { "id": 1, "name": "Steel Vengeance", "category": { /* Rich Choice Object */ }, "status": { /* Rich Choice Object */ } } ], "photos": [ { "id": 1, "photo_type": "banner", "image_url": "https://imagedelivery.net/...", "variants": { "thumbnail": "https://imagedelivery.net/.../thumbnail", "medium": "https://imagedelivery.net/.../medium", "large": "https://imagedelivery.net/.../large" } } ], "recent_reviews": [ /* Latest reviews */ ], "stats": { "total_reviews": 1247, "average_rating": 8.7, "ride_count": 72, "coaster_count": 17 } } ``` ### Rides API #### List Rides ```javascript GET /api/v1/rides/ GET /api/v1/rides/?category=RC&status=OPERATING&manufacturer=bolliger-mabillard // Response { "count": 500, "results": [ { "id": 1, "name": "Steel Vengeance", "slug": "steel-vengeance", "park": { "name": "Cedar Point", "slug": "cedar-point" }, "category": { "value": "RC", "label": "Roller Coaster", "metadata": { "color": "red", "icon": "roller-coaster" } }, "status": { "value": "OPERATING", "label": "Operating", "metadata": { "color": "green", "icon": "check-circle" } }, "manufacturer": { "name": "Rocky Mountain Construction", "slug": "rocky-mountain-construction" }, "stats": { "height_ft": "205.00", "speed_mph": "74.00", "length_ft": "5740.00" } } ] } ``` #### Ride Detail ```javascript GET /api/v1/rides/steel-vengeance/ // Response includes comprehensive ride data { "id": 1, "name": "Steel Vengeance", "slug": "steel-vengeance", "description": "World's first hyper-hybrid coaster...", "park": { /* Park details */ }, "category": { /* Rich Choice Object */ }, "status": { /* Rich Choice Object */ }, "manufacturer": { /* Company details */ }, "designer": { /* Company details */ }, "ride_model": { "name": "I-Box Track", "description": "Steel track on wooden structure" }, "opening_date": "2018-05-05", "stats": { "height_ft": "205.00", "speed_mph": "74.00", "length_ft": "5740.00", "inversions": 4, "ride_time_seconds": 150, "track_type": "Hybrid", "track_material": { "value": "HYBRID", "label": "Hybrid", "metadata": { "color": "orange", "icon": "layers" } } }, "photos": [ /* Photo array */ ], "reviews": [ /* Recent reviews */ ], "rankings": { "global_rank": 1, "category_rank": 1, "park_rank": 1 } } ``` ### User Accounts API #### User Profile ```javascript GET /api/v1/accounts/profile/ // Response { "user_id": "1234", "username": "thrillseeker", "email": "user@example.com", "role": { "value": "USER", "label": "User", "metadata": { "color": "blue", "icon": "user", "permissions": ["create_content", "create_reviews"] } }, "theme_preference": { "value": "dark", "label": "Dark", "metadata": { "color": "gray", "icon": "moon", "preview_colors": { "background": "#1f2937", "text": "#f9fafb" } } }, "profile": { "display_name": "Thrill Seeker", "avatar_url": "https://imagedelivery.net/...", "bio": "Love roller coasters!", "coaster_credits": 150, "stats": { /* User statistics */ } } } ``` #### User Notifications ```javascript GET /api/v1/accounts/notifications/ // Response { "count": 25, "unread_count": 5, "results": [ { "id": 1, "notification_type": { "value": "submission_approved", "label": "Submission Approved", "metadata": { "color": "green", "icon": "check-circle", "category": "submission" } }, "title": "Your submission has been approved!", "message": "Your photo submission for Cedar Point has been approved.", "priority": { "value": "normal", "label": "Normal", "metadata": { "urgency_level": 2 } }, "is_read": false, "created_at": "2024-01-15T10:30:00Z" } ] } ``` ## Data Models ### TypeScript Interfaces ```typescript // Core Models interface Park { id: number; name: string; slug: string; description: string; park_type: RichChoice; status: RichChoice; opening_date: string; size_acres: string; location: ParkLocation; operator: Company; property_owner?: Company; stats: ParkStats; photos: Photo[]; areas: ParkArea[]; } interface Ride { id: number; name: string; slug: string; description: string; park: Park; category: RichChoice; status: RichChoice; manufacturer?: Company; designer?: Company; ride_model?: RideModel; opening_date: string; stats?: RideStats; photos: Photo[]; } interface User { user_id: string; username: string; email: string; role: RichChoice; theme_preference: RichChoice; privacy_level: RichChoice; profile: UserProfile; } interface UserProfile { profile_id: string; display_name: string; avatar_url: string; avatar_variants: { thumbnail: string; avatar: string; large: string; }; bio: string; coaster_credits: number; dark_ride_credits: number; flat_ride_credits: number; water_ride_credits: number; } // Location Models interface ParkLocation { street_address: string; city: string; state: string; country: string; postal_code: string; coordinates: { latitude: number; longitude: number; }; timezone: string; } // Media Models interface Photo { id: number; photo_type: string; image_url: string; variants: { thumbnail: string; medium: string; large: string; [key: string]: string; }; attribution: string; caption: string; is_primary: boolean; } // Company Models interface Company { id: number; name: string; slug: string; roles: RichChoice[]; description: string; website: string; founded_year?: number; headquarters?: CompanyHeadquarters; } ``` ## Error Handling ### Standard Error Response ```javascript // 400 Bad Request { "error": "validation_error", "message": "Invalid input data", "details": { "field_name": ["This field is required."] } } // 401 Unauthorized { "error": "authentication_required", "message": "Authentication credentials were not provided." } // 403 Forbidden { "error": "permission_denied", "message": "You do not have permission to perform this action." } // 404 Not Found { "error": "not_found", "message": "The requested resource was not found." } // 500 Internal Server Error { "error": "server_error", "message": "An unexpected error occurred. Please try again later." } ``` ### Error Handling in Frontend ```javascript async function fetchPark(slug) { try { const response = await fetch(`/api/v1/parks/${slug}/`); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.message || 'Request failed'); } return await response.json(); } catch (error) { console.error('Failed to fetch park:', error); // Handle error appropriately in UI throw error; } } ``` ## Integration Examples ### React Hook for Rich Choices ```javascript import { useState, useEffect } from 'react'; function useRichChoices(domain, choiceGroup) { const [choices, setChoices] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchChoices() { try { const response = await fetch(`/api/v1/choices/${domain}/${choiceGroup}/`); const data = await response.json(); setChoices(data); } catch (error) { console.error('Failed to fetch choices:', error); } finally { setLoading(false); } } fetchChoices(); }, [domain, choiceGroup]); return { choices, loading }; } // Usage function UserRoleSelect({ value, onChange }) { const { choices, loading } = useRichChoices('accounts', 'user_roles'); if (loading) return
Loading...
; return ( ); } ``` ### Choice Display Component ```javascript function ChoiceDisplay({ choice, showIcon = true, showDescription = false }) { const { value, label, description, metadata } = choice; return ( {showIcon && metadata.icon && ( )} {label} {showDescription && description && ( {description} )} ); } ``` ### API Client with Rich Choice Support ```javascript class ThrillWikiAPI { constructor(baseURL, token) { this.baseURL = baseURL; this.token = token; } async request(endpoint, options = {}) { const url = `${this.baseURL}${endpoint}`; const headers = { 'Content-Type': 'application/json', 'Authorization': `Token ${this.token}`, ...options.headers }; const response = await fetch(url, { ...options, headers }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || 'Request failed'); } return response.json(); } // Parks async getParks(filters = {}) { const params = new URLSearchParams(filters); return this.request(`/parks/?${params}`); } async getPark(slug) { return this.request(`/parks/${slug}/`); } // Rides async getRides(filters = {}) { const params = new URLSearchParams(filters); return this.request(`/rides/?${params}`); } async getRide(slug) { return this.request(`/rides/${slug}/`); } // Choices async getChoices(domain, choiceGroup) { return this.request(`/choices/${domain}/${choiceGroup}/`); } // User async getCurrentUser() { return this.request('/accounts/profile/'); } async updateUserPreferences(preferences) { return this.request('/accounts/preferences/', { method: 'PATCH', body: JSON.stringify(preferences) }); } } ``` ## Best Practices ### 1. Always Use Rich Choice Objects - Never hardcode choice values or labels - Always fetch choice metadata for proper UI rendering - Use choice metadata for styling and icons ### 2. Handle Loading States - Show loading indicators while fetching data - Implement proper error boundaries - Cache choice data to avoid repeated requests ### 3. Responsive Design - Use photo variants for different screen sizes - Implement proper image lazy loading - Consider mobile-first design patterns ### 4. Performance Optimization - Use pagination for large datasets - Implement proper caching strategies - Minimize API requests with efficient data fetching ### 5. Error Handling - Always handle API errors gracefully - Provide meaningful error messages to users - Implement retry logic for transient failures ## API Versioning The API uses URL-based versioning (`/api/v1/`). When breaking changes are introduced, a new version will be created (`/api/v2/`). The current version (v1) will be maintained for backward compatibility. ## Rate Limiting API requests are rate-limited to prevent abuse: - **Authenticated users**: 1000 requests per hour - **Anonymous users**: 100 requests per hour - **Bulk operations**: Special limits apply Rate limit headers are included in responses: ``` X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 999 X-RateLimit-Reset: 1642694400 ``` ## Support For API support and questions: - **Documentation**: https://api.thrillwiki.com/api/schema/swagger-ui/ - **GitHub Issues**: https://github.com/thrillwiki/api/issues - **Email**: api-support@thrillwiki.com --- **Note**: This documentation is automatically updated when the API changes. Always refer to the latest version for accurate information.