mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 05:31:09 -05:00
- 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.
11 KiB
11 KiB
ThrillWiki API Integration Guide for Next.js Frontend
Overview
You are building a Next.js frontend for ThrillWiki, a comprehensive theme park and roller coaster database. This document provides the complete API structure and integration patterns for connecting your Next.js application to the ThrillWiki Django REST API.
Base API URL: http://localhost:8000/api/v1/
Authentication System
JWT Authentication
The API uses JWT tokens with refresh token rotation:
// Authentication endpoints
POST /api/v1/auth/login/
POST /api/v1/auth/signup/
POST /api/v1/auth/logout/
POST /api/v1/auth/token/refresh/
GET /api/v1/auth/user/
GET /api/v1/auth/status/
// Password management
POST /api/v1/auth/password/reset/
POST /api/v1/auth/password/change/
// Email verification
POST /api/v1/auth/verify-email/<token>/
POST /api/v1/auth/resend-verification/
// Social authentication
GET /api/v1/auth/social/providers/
GET /api/v1/auth/social/providers/available/
GET /api/v1/auth/social/connected/
POST /api/v1/auth/social/connect/<provider>/
POST /api/v1/auth/social/disconnect/<provider>/
GET /api/v1/auth/social/status/
Authentication Hook Example
// hooks/useAuth.ts
import { useState, useEffect } from 'react';
interface User {
id: number;
username: string;
email: string;
first_name: string;
last_name: string;
}
export const useAuth = () => {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const login = async (credentials: LoginCredentials) => {
const response = await fetch('/api/v1/auth/login/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
});
if (response.ok) {
const data = await response.json();
localStorage.setItem('accessToken', data.access);
localStorage.setItem('refreshToken', data.refresh);
setUser(data.user);
}
return response;
};
const logout = async () => {
await fetch('/api/v1/auth/logout/', { method: 'POST' });
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
setUser(null);
};
return { user, login, logout, loading };
};
Core Domain APIs
Parks API
// Parks endpoints
GET /api/v1/parks/ // List parks with filtering
POST /api/v1/parks/ // Create park (admin)
GET /api/v1/parks/<id>/ // Park detail (supports ID or slug)
PUT /api/v1/parks/<id>/ // Update park (admin)
DELETE /api/v1/parks/<id>/ // Delete park (admin)
// Park search and filtering
GET /api/v1/parks/hybrid/ // Advanced search with filtering
GET /api/v1/parks/hybrid/filter-metadata/ // Get filter options
GET /api/v1/parks/filter-options/ // Simple filter options
GET /api/v1/parks/search/companies/ // Company autocomplete
GET /api/v1/parks/search-suggestions/ // Park name suggestions
// Park media
GET /api/v1/parks/<id>/photos/ // List park photos
POST /api/v1/parks/<id>/photos/ // Upload photo
GET /api/v1/parks/<id>/photos/<photo_id>/ // Photo detail
PUT /api/v1/parks/<id>/photos/<photo_id>/ // Update photo
DELETE /api/v1/parks/<id>/photos/<photo_id>/ // Delete photo
GET /api/v1/parks/<id>/image-settings/ // Image display settings
Rides API
// Rides endpoints
GET /api/v1/rides/ // List rides with filtering
POST /api/v1/rides/ // Create ride (admin)
GET /api/v1/rides/<id>/ // Ride detail
PUT /api/v1/rides/<id>/ // Update ride (admin)
DELETE /api/v1/rides/<id>/ // Delete ride (admin)
// Ride search and filtering
GET /api/v1/rides/hybrid/ // Advanced search with filtering
GET /api/v1/rides/hybrid/filter-metadata/ // Get filter options
GET /api/v1/rides/filter-options/ // Simple filter options
GET /api/v1/rides/search/companies/ // Company autocomplete
GET /api/v1/rides/search/ride-models/ // Ride model search
GET /api/v1/rides/search-suggestions/ // Ride name suggestions
// Ride media
GET /api/v1/rides/<id>/photos/ // List ride photos
POST /api/v1/rides/<id>/photos/ // Upload photo
GET /api/v1/rides/<id>/photos/<photo_id>/ // Photo detail
PUT /api/v1/rides/<id>/photos/<photo_id>/ // Update photo
DELETE /api/v1/rides/<id>/photos/<photo_id>/ // Delete photo
GET /api/v1/rides/<id>/image-settings/ // Image display settings
// Manufacturers and models
GET /api/v1/rides/manufacturers/<slug>/ // Manufacturer-specific endpoints
Data Structures
Park Object
interface Park {
id: number;
name: string;
slug: string;
status: 'OPERATING' | 'CLOSED' | 'SBNO' | 'PLANNED';
description: string;
average_rating: number;
coaster_count: number;
ride_count: number;
location: {
city: string;
state: string;
country: string;
};
operator: {
id: number;
name: string;
slug: string;
};
}
Ride Object
interface Ride {
id: number;
name: string;
slug: string;
status: 'OPERATING' | 'CLOSED' | 'SBNO' | 'PLANNED';
description: string;
average_rating: number;
park: {
id: number;
name: string;
slug: string;
};
ride_model: {
id: number;
name: string;
description: string;
category: string;
manufacturer: {
id: number;
name: string;
slug: string;
};
};
statistics?: {
height_ft: number;
speed_mph: number;
length_ft: number;
inversions: number;
};
}
Advanced Features
Hybrid Search System
The API includes a sophisticated hybrid search system for both parks and rides:
// Hybrid search example
const searchParks = async (filters: SearchFilters) => {
const params = new URLSearchParams({
search: filters.query || '',
park_type: filters.parkType || '',
status: filters.status || '',
country: filters.country || '',
has_coasters: filters.hasCoasters?.toString() || '',
min_coasters: filters.minCoasters?.toString() || '',
view_mode: filters.viewMode || 'card',
page: filters.page?.toString() || '1',
});
const response = await fetch(`/api/v1/parks/hybrid/?${params}`);
return response.json();
};
Filter Metadata
Get dynamic filter options for building search UIs:
const getFilterMetadata = async () => {
const response = await fetch('/api/v1/parks/hybrid/filter-metadata/');
const data = await response.json();
// Returns available options for dropdowns:
// { countries: [...], states: [...], park_types: [...] }
return data;
};
Additional Endpoints
Statistics & Analytics
GET /api/v1/stats/ // Platform statistics
POST /api/v1/stats/recalculate/ // Trigger stats recalculation
Trending & Discovery
GET /api/v1/trending/ // Trending content
GET /api/v1/new-content/ // Recently added content
POST /api/v1/trending/calculate/ // Trigger trending calculation
Reviews & Rankings
GET /api/v1/reviews/latest/ // Latest reviews
GET /api/v1/rankings/ // Ride rankings
POST /api/v1/rankings/calculate/ // Trigger ranking calculation
Health & Monitoring
GET /api/v1/health/ // Detailed health check
GET /api/v1/health/simple/ // Simple health status
GET /api/v1/health/performance/ // Performance metrics
Implementation Patterns
API Client Setup
// lib/api.ts
class ThrillWikiAPI {
private baseURL = 'http://localhost:8000/api/v1';
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const token = localStorage.getItem('accessToken');
const response = await fetch(`${this.baseURL}${endpoint}`, {
...options,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// Parks methods
async getParks(params?: URLSearchParams) {
return this.request<Park[]>(`/parks/?${params || ''}`);
}
async getPark(id: string | number) {
return this.request<Park>(`/parks/${id}/`);
}
// Rides methods
async getRides(params?: URLSearchParams) {
return this.request<Ride[]>(`/rides/?${params || ''}`);
}
async getRide(id: number) {
return this.request<Ride>(`/rides/${id}/`);
}
}
export const api = new ThrillWikiAPI();
Error Handling
// hooks/useApiError.ts
export const useApiError = () => {
const [error, setError] = useState<string | null>(null);
const handleError = (error: any) => {
if (error.response?.status === 401) {
// Handle unauthorized
window.location.href = '/login';
} else if (error.response?.status === 403) {
setError('You do not have permission to perform this action');
} else {
setError('An unexpected error occurred');
}
};
return { error, handleError, clearError: () => setError(null) };
};
Pagination Hook
// hooks/usePagination.ts
export const usePagination = <T>(
fetchFn: (page: number) => Promise<{ results: T[]; count: number }>
) => {
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const loadMore = async () => {
setLoading(true);
try {
const response = await fetchFn(page);
setData(prev => [...prev, ...response.results]);
setHasMore(response.results.length > 0);
setPage(prev => prev + 1);
} catch (error) {
console.error('Failed to load more:', error);
} finally {
setLoading(false);
}
};
return { data, loading, hasMore, loadMore };
};
Key Implementation Notes
- Authentication: All protected endpoints require JWT tokens in the Authorization header
- Pagination: List endpoints support standard pagination with
pageandpage_sizeparameters - Filtering: Use the hybrid endpoints for advanced filtering and search functionality
- Media: Image uploads are handled through Cloudflare Images with automatic optimization
- Slugs: Both parks and rides support access by ID or slug in detail endpoints
- Real-time: Consider implementing WebSocket connections for live updates on trending content
- Caching: Implement proper caching strategies for filter metadata and statistics
- Error Handling: Handle 401/403 responses appropriately for authentication flows
This API provides comprehensive access to all ThrillWiki functionality. Focus on implementing the core park and ride browsing features first, then add authentication and advanced features like reviews and rankings as needed.