- Introduced Next.js integration guide for ThrillWiki API, detailing authentication, core domain APIs, data structures, and implementation patterns. - Documented the migration to Rich Choice Objects, highlighting changes for frontend developers and enhanced metadata availability. - Fixed the missing `get_by_slug` method in the Ride model, ensuring proper functionality of ride detail endpoints. - Created a test script to verify manufacturer syncing with ride models, ensuring data integrity across related models.
18 KiB
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
- API Overview
- Authentication
- Rich Choice Objects
- Core Endpoints
- Data Models
- Error Handling
- 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
// 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
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
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
// 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, SUPERUSERtheme_preferences: light, darkprivacy_levels: public, friends, privatetop_list_categories: RC, DR, FR, WR, PKnotification_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, OTcompany_roles: MANUFACTURER, DESIGNERtrack_materials: STEEL, WOOD, HYBRID
Core Endpoints
Parks API
List Parks
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
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
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
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
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
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
// 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
// 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
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
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 <div>Loading...</div>;
return (
<select value={value} onChange={onChange}>
{choices.map(choice => (
<option key={choice.value} value={choice.value}>
{choice.label}
</option>
))}
</select>
);
}
Choice Display Component
function ChoiceDisplay({ choice, showIcon = true, showDescription = false }) {
const { value, label, description, metadata } = choice;
return (
<span className={`choice-display ${metadata.css_class}`}>
{showIcon && metadata.icon && (
<Icon name={metadata.icon} color={metadata.color} />
)}
<span className="choice-label">{label}</span>
{showDescription && description && (
<span className="choice-description">{description}</span>
)}
</span>
);
}
API Client with Rich Choice Support
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.