feat: Enhance Park Detail Endpoint with Media URL Service Integration

- Updated ParkDetailOutputSerializer to utilize MediaURLService for generating Cloudflare URLs and friendly URLs for park photos.
- Added support for multiple lookup methods (ID and slug) in the park detail endpoint.
- Improved documentation for the park detail endpoint, including request properties and response structure.
- Created MediaURLService for generating SEO-friendly URLs and handling Cloudflare image URLs.
- Comprehensive updates to frontend documentation to reflect new endpoint capabilities and usage examples.
- Added detailed park detail endpoint documentation, including request and response structures, field descriptions, and usage examples.
This commit is contained in:
pacnpal
2025-08-31 16:45:47 -04:00
parent 91906e0d57
commit 0fd6dc2560
12 changed files with 1530 additions and 380 deletions

View File

@@ -0,0 +1,510 @@
# Park Detail Endpoint - Complete Documentation
## Endpoint Overview
**URL:** `GET /api/v1/parks/{identifier}/`
**Description:** Retrieve comprehensive park details including location, photos, areas, rides, and company information.
**Authentication:** None required (public endpoint)
**Supports Multiple Lookup Methods:**
- By ID: `/api/v1/parks/123/`
- By current slug: `/api/v1/parks/cedar-point/`
- By historical slug: `/api/v1/parks/old-cedar-point-name/`
## Request Properties
### Path Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `identifier` | string | Yes | Park ID (integer) or slug (string). Supports current and historical slugs. |
### Query Parameters
**None required** - This endpoint returns full park details by default without any query parameters.
### Request Headers
| Header | Required | Description |
|--------|----------|-------------|
| `Accept` | No | `application/json` (default) |
| `Content-Type` | No | Not applicable for GET requests |
## Response Structure
### Success Response (200 OK)
```json
{
"id": 1,
"name": "Cedar Point",
"slug": "cedar-point",
"status": "OPERATING",
"description": "America's Roller Coast",
"park_type": "THEME_PARK",
// Dates and Operations
"opening_date": "1870-01-01",
"closing_date": null,
"operating_season": "May - October",
"size_acres": 364.0,
"website": "https://cedarpoint.com",
// Statistics
"average_rating": 4.5,
"coaster_count": 17,
"ride_count": 70,
// Location Information
"location": {
"id": 1,
"latitude": 41.4793,
"longitude": -82.6833,
"street_address": "1 Cedar Point Dr",
"city": "Sandusky",
"state": "Ohio",
"country": "United States",
"continent": "North America",
"postal_code": "44870",
"formatted_address": "1 Cedar Point Dr, Sandusky, OH 44870, United States"
},
// Company Information
"operator": {
"id": 1,
"name": "Cedar Fair",
"slug": "cedar-fair",
"roles": ["OPERATOR"],
"description": "Leading amusement park operator",
"website": "https://cedarfair.com",
"founded_year": 1983
},
"property_owner": {
"id": 1,
"name": "Cedar Fair",
"slug": "cedar-fair",
"roles": ["OPERATOR", "PROPERTY_OWNER"],
"description": "Leading amusement park operator",
"website": "https://cedarfair.com",
"founded_year": 1983
},
// Park Areas/Themed Sections
"areas": [
{
"id": 1,
"name": "Frontier Town",
"slug": "frontier-town",
"description": "Wild West themed area"
},
{
"id": 2,
"name": "Millennium Island",
"slug": "millennium-island",
"description": "Home to Millennium Force"
}
],
// Photo Information
"photos": [
{
"id": 456,
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
},
"friendly_urls": {
"thumbnail": "/parks/cedar-point/photos/beautiful-park-entrance-456-thumbnail.jpg",
"medium": "/parks/cedar-point/photos/beautiful-park-entrance-456-medium.jpg",
"large": "/parks/cedar-point/photos/beautiful-park-entrance-456-large.jpg",
"public": "/parks/cedar-point/photos/beautiful-park-entrance-456.jpg"
},
"caption": "Beautiful park entrance",
"alt_text": "Cedar Point main entrance with flags",
"is_primary": true
}
],
// Primary Photo (designated main photo)
"primary_photo": {
"id": 456,
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
},
"caption": "Beautiful park entrance",
"alt_text": "Cedar Point main entrance with flags"
},
// Banner Image (for hero sections)
"banner_image": {
"id": 456,
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
},
"caption": "Beautiful park entrance",
"alt_text": "Cedar Point main entrance with flags",
"is_fallback": false
},
// Card Image (for listings/cards)
"card_image": {
"id": 456,
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
},
"caption": "Beautiful park entrance",
"alt_text": "Cedar Point main entrance with flags",
"is_fallback": false
},
// Frontend URL
"url": "https://thrillwiki.com/parks/cedar-point/",
// Metadata
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-15T12:30:00Z"
}
```
### Error Responses
#### 404 Not Found
```json
{
"detail": "Park not found"
}
```
#### 500 Internal Server Error
```json
{
"detail": "Internal server error"
}
```
## Field Descriptions
### Core Park Information
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Unique park identifier |
| `name` | string | Official park name |
| `slug` | string | URL-friendly identifier |
| `status` | string | Operational status (see Status Values) |
| `description` | string | Park description/tagline |
| `park_type` | string | Park category (see Park Type Values) |
### Operational Details
| Field | Type | Description |
|-------|------|-------------|
| `opening_date` | date | Park opening date (YYYY-MM-DD) |
| `closing_date` | date | Park closing date (null if still operating) |
| `operating_season` | string | Seasonal operation description |
| `size_acres` | decimal | Park size in acres |
| `website` | string | Official park website URL |
### Statistics
| Field | Type | Description |
|-------|------|-------------|
| `average_rating` | decimal | Average user rating (1-10 scale) |
| `coaster_count` | integer | Number of roller coasters |
| `ride_count` | integer | Total number of rides |
### Location Object
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Location record ID |
| `latitude` | float | Geographic latitude |
| `longitude` | float | Geographic longitude |
| `street_address` | string | Street address |
| `city` | string | City name |
| `state` | string | State/province |
| `country` | string | Country name |
| `continent` | string | Continent name |
| `postal_code` | string | ZIP/postal code |
| `formatted_address` | string | Complete formatted address |
### Company Objects (Operator/Property Owner)
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Company ID |
| `name` | string | Company name |
| `slug` | string | URL-friendly identifier |
| `roles` | array | Company roles (OPERATOR, PROPERTY_OWNER, etc.) |
| `description` | string | Company description |
| `website` | string | Company website |
| `founded_year` | integer | Year company was founded |
### Area Objects
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Area ID |
| `name` | string | Area/section name |
| `slug` | string | URL-friendly identifier |
| `description` | string | Area description |
### Photo Objects
| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Photo ID |
| `image_url` | string | Base Cloudflare image URL |
| `image_variants` | object | Available image sizes/transformations |
| `caption` | string | Photo caption |
| `alt_text` | string | Accessibility alt text |
| `is_primary` | boolean | Whether this is the primary photo |
| `is_fallback` | boolean | Whether this is a fallback image |
### Image Variants Object
| Field | Type | Description |
|-------|------|-------------|
| `thumbnail` | string | Small thumbnail URL (150x150) |
| `medium` | string | Medium size URL (500x500) |
| `large` | string | Large size URL (1200x1200) |
| `public` | string | Full size public URL |
## Enumerated Values
### Status Values
| Value | Description |
|-------|-------------|
| `OPERATING` | Currently operating |
| `CLOSED_TEMP` | Temporarily closed |
| `CLOSED_PERM` | Permanently closed |
| `UNDER_CONSTRUCTION` | Under construction |
| `DEMOLISHED` | Demolished |
| `RELOCATED` | Relocated |
### Park Type Values
| Value | Description |
|-------|-------------|
| `THEME_PARK` | Theme park |
| `AMUSEMENT_PARK` | Amusement park |
| `WATER_PARK` | Water park |
| `FAMILY_ENTERTAINMENT_CENTER` | Family entertainment center |
| `CARNIVAL` | Carnival |
| `FAIR` | Fair |
| `PIER` | Pier |
| `BOARDWALK` | Boardwalk |
| `SAFARI_PARK` | Safari park |
| `ZOO` | Zoo |
| `OTHER` | Other |
## Usage Examples
### JavaScript/TypeScript
```typescript
// Fetch by ID
const parkById = await fetch('/api/v1/parks/123/');
const parkData = await parkById.json();
// Fetch by current slug
const parkBySlug = await fetch('/api/v1/parks/cedar-point/');
const parkData2 = await parkBySlug.json();
// Fetch by historical slug
const parkByHistoricalSlug = await fetch('/api/v1/parks/old-name/');
const parkData3 = await parkByHistoricalSlug.json();
// Access different image sizes
const thumbnailUrl = parkData.primary_photo?.image_variants.thumbnail;
const fullSizeUrl = parkData.primary_photo?.image_variants.public;
```
### Python
```python
import requests
# Fetch park details
response = requests.get('https://api.thrillwiki.com/api/v1/parks/cedar-point/')
park_data = response.json()
# Access park information
park_name = park_data['name']
location = park_data['location']
operator = park_data['operator']
photos = park_data['photos']
```
### cURL
```bash
# Fetch by slug
curl -X GET "https://api.thrillwiki.com/api/v1/parks/cedar-point/" \
-H "Accept: application/json"
# Fetch by ID
curl -X GET "https://api.thrillwiki.com/api/v1/parks/123/" \
-H "Accept: application/json"
```
## Related Endpoints
- **Park List:** `GET /api/v1/parks/` - List parks with filtering
- **Park Photos:** `GET /api/v1/parks/{id}/photos/` - Manage park photos
- **Park Areas:** `GET /api/v1/parks/{id}/areas/` - Park themed areas
- **Park Image Settings:** `PATCH /api/v1/parks/{id}/image-settings/` - Set banner/card images
## Photo Handling Details
### Photo Upload vs Display Distinction
**Important**: You can upload unlimited photos per park, but the park detail endpoint shows only the 10 most relevant photos for performance optimization.
#### **Photo Upload Capacity**
- **No Upload Limit**: Upload unlimited photos per park via `POST /api/v1/parks/{park_id}/photos/`
- **Storage**: All photos stored in database and Cloudflare Images
- **Approval System**: Each photo goes through moderation (`is_approved` field)
- **Photo Types**: Categorize photos (banner, card, gallery, etc.)
- **Bulk Upload**: Support for multiple photo uploads
#### **Display Limit (Detail Endpoint)**
- **10 Photo Limit**: Only applies to this park detail endpoint response
- **Smart Selection**: Shows 10 most relevant photos using intelligent ordering:
1. **Primary photos first** (`-is_primary`)
2. **Newest photos next** (`-created_at`)
3. **Only approved photos** (`is_approved=True`)
### Complete Photo Access
#### **All Photos Available Via Dedicated Endpoint**
```
GET /api/v1/parks/{park_id}/photos/
```
- **No Limit**: Returns all uploaded photos for the park
- **Pagination**: Supports pagination for large photo collections
- **Filtering**: Filter by photo type, approval status, etc.
- **Full Management**: Complete CRUD operations for all photos
#### **Photo URL Structure Per Park**
**Maximum Possible URLs per park:**
- **General photos**: 10 photos × 4 variants = **40 URLs**
- **Primary photo**: 1 photo × 4 variants = **4 URLs**
- **Banner image**: 1 photo × 4 variants = **4 URLs**
- **Card image**: 1 photo × 4 variants = **4 URLs**
- **Total Maximum**: **52 photo URLs per park**
**Each photo includes 4 Cloudflare transformation URLs:**
1. **`thumbnail`**: Optimized for small previews (150x150)
2. **`medium`**: Medium resolution for general use (500x500)
3. **`large`**: High resolution for detailed viewing (1200x1200)
4. **`public`**: Original/full size image
#### **Practical Example**
A park could have:
- **50 uploaded photos** (all stored in system)
- **30 approved photos** (available for public display)
- **10 photos shown** in park detail endpoint (most relevant)
- **All 30 approved photos** accessible via `/api/v1/parks/{id}/photos/`
#### **Frontend Implementation Strategy**
```javascript
// Get park with essential photos (fast initial load)
const park = await fetch('/api/v1/parks/cedar-point/');
// Get complete photo gallery when needed (e.g., photo gallery page)
const allPhotos = await fetch('/api/v1/parks/123/photos/?page_size=50');
```
### Friendly URLs for Photos
**NEW FEATURE**: Each photo now includes both Cloudflare URLs and SEO-friendly URLs.
#### **URL Structure**
```
/parks/{park-slug}/photos/{caption-slug}-{photo-id}-{variant}.jpg
```
**Examples:**
- `/parks/cedar-point/photos/beautiful-park-entrance-456.jpg` (public/original)
- `/parks/cedar-point/photos/beautiful-park-entrance-456-thumbnail.jpg`
- `/parks/cedar-point/photos/beautiful-park-entrance-456-medium.jpg`
- `/parks/cedar-point/photos/beautiful-park-entrance-456-large.jpg`
#### **Benefits**
- **SEO Optimized**: Descriptive URLs improve search engine ranking
- **User Friendly**: URLs are readable and meaningful
- **Consistent**: Follows predictable pattern across all photos
- **Backwards Compatible**: Original Cloudflare URLs still available
#### **Implementation**
Each photo object now includes both URL types:
```json
{
"id": 456,
"image_url": "https://imagedelivery.net/account-hash/def789ghi012/public",
"image_variants": {
"thumbnail": "https://imagedelivery.net/account-hash/def789ghi012/thumbnail",
"medium": "https://imagedelivery.net/account-hash/def789ghi012/medium",
"large": "https://imagedelivery.net/account-hash/def789ghi012/large",
"public": "https://imagedelivery.net/account-hash/def789ghi012/public"
},
"friendly_urls": {
"thumbnail": "/parks/cedar-point/photos/beautiful-park-entrance-456-thumbnail.jpg",
"medium": "/parks/cedar-point/photos/beautiful-park-entrance-456-medium.jpg",
"large": "/parks/cedar-point/photos/beautiful-park-entrance-456-large.jpg",
"public": "/parks/cedar-point/photos/beautiful-park-entrance-456.jpg"
},
"caption": "Beautiful park entrance",
"alt_text": "Cedar Point main entrance with flags"
}
```
### Photo Management Features
- **Primary Photo**: Designate which photo represents the park
- **Banner/Card Images**: Set specific photos for different UI contexts
- **Fallback Logic**: Banner and card images automatically fallback to latest approved photo if not explicitly set
- **Approval Workflow**: Moderate photos before public display
- **Photo Metadata**: Each photo includes caption, alt text, and categorization
- **Dual URL System**: Both Cloudflare and friendly URLs provided for maximum flexibility
## Performance Notes
- Response includes optimized database queries with `select_related` and `prefetch_related`
- Photos limited to 10 most recent approved photos for optimal response size
- Image variants are pre-computed Cloudflare transformations for fast delivery
- Historical slug lookup may require additional database queries
- Smart photo selection ensures most relevant photos are included
## Caching
- No caching implemented at endpoint level
- Cloudflare images are cached at CDN level
- Consider implementing Redis caching for frequently accessed parks
## Rate Limiting
- No rate limiting currently implemented
- Public endpoint accessible without authentication
- Consider implementing rate limiting for production use