Files
thrillwiki_django_no_react/docs/nextjs-integration-prompt.md
pacnpal c2c26cfd1d Add comprehensive API documentation for ThrillWiki integration and features
- 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.
2025-09-16 11:29:17 -04:00

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
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

  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.