mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 14:51:08 -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.
383 lines
11 KiB
Markdown
383 lines
11 KiB
Markdown
# 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/<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
|
|
```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<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
|
|
|
|
```typescript
|
|
// 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
|
|
|
|
```typescript
|
|
// 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
|
|
```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<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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// 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. |