# 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: ```typescript // 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// 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// POST /api/v1/auth/social/disconnect// GET /api/v1/auth/social/status/ ``` ### Authentication Hook Example ```typescript // 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(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 ```typescript // Parks endpoints GET /api/v1/parks/ // List parks with filtering POST /api/v1/parks/ // Create park (admin) GET /api/v1/parks// // Park detail (supports ID or slug) PUT /api/v1/parks// // Update park (admin) DELETE /api/v1/parks// // 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//photos/ // List park photos POST /api/v1/parks//photos/ // Upload photo GET /api/v1/parks//photos// // Photo detail PUT /api/v1/parks//photos// // Update photo DELETE /api/v1/parks//photos// // Delete photo GET /api/v1/parks//image-settings/ // Image display settings ``` ### Rides API ```typescript // Rides endpoints GET /api/v1/rides/ // List rides with filtering POST /api/v1/rides/ // Create ride (admin) GET /api/v1/rides// // Ride detail PUT /api/v1/rides// // Update ride (admin) DELETE /api/v1/rides// // 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//photos/ // List ride photos POST /api/v1/rides//photos/ // Upload photo GET /api/v1/rides//photos// // Photo detail PUT /api/v1/rides//photos// // Update photo DELETE /api/v1/rides//photos// // Delete photo GET /api/v1/rides//image-settings/ // Image display settings // Manufacturers and models GET /api/v1/rides/manufacturers// // Manufacturer-specific endpoints ``` ## Data Structures ### Park Object ```typescript 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 ```typescript 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: ```typescript // 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: ```typescript 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 ```typescript GET /api/v1/stats/ // Platform statistics POST /api/v1/stats/recalculate/ // Trigger stats recalculation ``` ### Trending & Discovery ```typescript GET /api/v1/trending/ // Trending content GET /api/v1/new-content/ // Recently added content POST /api/v1/trending/calculate/ // Trigger trending calculation ``` ### Reviews & Rankings ```typescript GET /api/v1/reviews/latest/ // Latest reviews GET /api/v1/rankings/ // Ride rankings POST /api/v1/rankings/calculate/ // Trigger ranking calculation ``` ### Health & Monitoring ```typescript 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 ```typescript // lib/api.ts class ThrillWikiAPI { private baseURL = 'http://localhost:8000/api/v1'; private async request( endpoint: string, options: RequestInit = {} ): Promise { 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(`/parks/?${params || ''}`); } async getPark(id: string | number) { return this.request(`/parks/${id}/`); } // Rides methods async getRides(params?: URLSearchParams) { return this.request(`/rides/?${params || ''}`); } async getRide(id: number) { return this.request(`/rides/${id}/`); } } export const api = new ThrillWikiAPI(); ``` ### Error Handling ```typescript // hooks/useApiError.ts export const useApiError = () => { const [error, setError] = useState(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 ```typescript // hooks/usePagination.ts export const usePagination = ( fetchFn: (page: number) => Promise<{ results: T[]; count: number }> ) => { const [data, setData] = useState([]); 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 1. **Authentication**: All protected endpoints require JWT tokens in the Authorization header 2. **Pagination**: List endpoints support standard pagination with `page` and `page_size` parameters 3. **Filtering**: Use the hybrid endpoints for advanced filtering and search functionality 4. **Media**: Image uploads are handled through Cloudflare Images with automatic optimization 5. **Slugs**: Both parks and rides support access by ID or slug in detail endpoints 6. **Real-time**: Consider implementing WebSocket connections for live updates on trending content 7. **Caching**: Implement proper caching strategies for filter metadata and statistics 8. **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.