Refactor API structure and add comprehensive user management features

- Restructure API v1 with improved serializers organization
- Add user deletion requests and moderation queue system
- Implement bulk moderation operations and permissions
- Add user profile enhancements with display names and avatars
- Expand ride and park API endpoints with better filtering
- Add manufacturer API with detailed ride relationships
- Improve authentication flows and error handling
- Update frontend documentation and API specifications
This commit is contained in:
pacnpal
2025-08-29 16:03:51 -04:00
parent 7b9f64be72
commit bb7da85516
92 changed files with 19690 additions and 9076 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,737 @@
# ThrillWiki: Revolutionizing Theme Park Information Through Community-Driven Technology
**A Technical and Strategic Whitepaper**
---
**Version:** 1.0
**Publication Date:** January 29, 2025
**Authors:** ThrillWiki Development Team
**Document Type:** Strategic Technical Whitepaper
---
## Executive Summary
The theme park industry, valued at over $60 billion globally, lacks a comprehensive, community-driven information platform that serves both enthusiasts and industry professionals. ThrillWiki addresses this gap by providing the world's most detailed database of theme parks, rides, and attractions, powered by cutting-edge technology and expert moderation.
### Key Innovations
**Technical Excellence**
- First-of-its-kind Django REST API with 120+ endpoints
- Advanced PostgreSQL + PostGIS geospatial capabilities
- Real-time content moderation with queue-based processing
- Cloudflare Images integration with automatic optimization
- Comprehensive user management with granular permissions
**Business Model Innovation**
- Community-driven content with expert oversight
- Multi-tier user roles ensuring content quality
- Industry partnerships with manufacturers and operators
- Scalable architecture supporting millions of users
- International expansion capabilities
**Market Impact**
- Democratizes access to theme park information
- Establishes new standards for industry data accuracy
- Creates valuable ecosystem for enthusiasts and professionals
- Enables data-driven decision making for industry stakeholders
- Fosters global community of theme park enthusiasts
### Strategic Positioning
ThrillWiki is positioned to become the definitive source for theme park information globally, serving as both a consumer platform and industry resource. The platform's technical architecture and business model create sustainable competitive advantages while fostering community growth and industry partnerships.
---
## Market Analysis and Opportunity
### Industry Overview
The global theme park industry has experienced consistent growth, with key trends driving demand for comprehensive information platforms:
**Market Size and Growth**
- Global market value: $60+ billion (2024)
- Annual growth rate: 5-7% projected through 2030
- 500+ major theme parks worldwide
- 10,000+ individual attractions and rides
- 2.8 billion annual park visits globally
**Information Gap Analysis**
Current information sources are fragmented, outdated, or commercially biased:
- **Wikipedia**: Limited detail, inconsistent quality
- **Park Websites**: Marketing-focused, incomplete technical data
- **Enthusiast Forums**: Scattered information, no central authority
- **Travel Sites**: Surface-level information, commercial bias
- **Industry Publications**: Professional focus, limited public access
### Target Market Segmentation
**Primary Markets**
1. **Theme Park Enthusiasts (15M+ globally)**
- Coaster enthusiasts and ride counters
- Annual park visitors and season pass holders
- Social media content creators and influencers
- Photography and videography communities
2. **Trip Planners (50M+ annually)**
- Families planning park vacations
- International tourists visiting theme park destinations
- Group travel organizers and tour operators
- Corporate event planners
3. **Industry Professionals (100K+ globally)**
- Park operators and management
- Ride manufacturers and designers
- Safety inspectors and regulators
- Industry analysts and consultants
**Secondary Markets**
1. **Academic and Research Community**
- Tourism and hospitality researchers
- Engineering and safety studies
- Economic impact analysis
- Cultural and social studies
2. **Media and Content Creators**
- Travel bloggers and journalists
- YouTube creators and podcasters
- Documentary filmmakers
- Industry publications
### Competitive Landscape
**Direct Competitors**
- **RCDB (Roller Coaster Database)**: Limited to roller coasters, outdated interface
- **Theme Park Insider**: News-focused, limited database functionality
- **Parkz**: Regional focus (Australia), limited global coverage
- **CoasterCount**: Personal tracking focus, minimal park information
**Indirect Competitors**
- **TripAdvisor**: General travel focus, limited technical detail
- **Google Maps/Places**: Basic information, no specialized features
- **Wikipedia**: Crowdsourced but inconsistent quality
- **Park Official Websites**: Marketing focus, incomplete data
**Competitive Advantages**
1. **Comprehensive Coverage**: All ride types, not just roller coasters
2. **Technical Excellence**: Modern API-first architecture
3. **Quality Assurance**: Expert moderation system
4. **Community Focus**: User-generated content with oversight
5. **Industry Integration**: Official partnerships and data feeds
6. **Global Scope**: International coverage from launch
7. **Mobile Optimization**: Native apps and responsive design
8. **Real-time Updates**: Live content and trending features
---
## Technical Innovation and Architecture
### Revolutionary Platform Design
ThrillWiki's technical architecture represents a paradigm shift in how theme park information is collected, verified, and distributed. The platform combines modern web technologies with innovative approaches to content moderation and community management.
**Core Technical Innovations**
1. **API-First Architecture**
- 120+ RESTful endpoints with comprehensive functionality
- OpenAPI 3.0 specification with interactive documentation
- Microservices-ready design for future scaling
- GraphQL implementation planned for advanced querying
2. **Advanced Geospatial Capabilities**
- PostgreSQL + PostGIS integration for location services
- Real-time mapping with clustering algorithms
- Geographic search and proximity calculations
- Route planning and navigation features
3. **Intelligent Content Moderation**
- Queue-based processing with priority algorithms
- Role-based approval workflows
- Automated spam and abuse detection
- Bulk operations for efficient management
4. **Scalable Media Management**
- Cloudflare Images integration with CDN delivery
- Automatic image optimization and variant generation
- Progressive loading and responsive image serving
- Advanced compression and format selection
### Database Architecture Excellence
**Entity Relationship Design**
The database schema reflects deep understanding of theme park industry relationships:
```
Parks ←→ Rides ←→ Manufacturers ←→ Ride Models
↓ ↓ ↓ ↓
Locations Photos Companies Specifications
↓ ↓ ↓ ↓
Reviews Users Headquarters Technical Data
```
**Advanced Indexing Strategy**
- Composite indexes for complex filtering operations
- Geospatial indexes for location-based queries
- Full-text search indexes for content discovery
- Partial indexes for optimized common queries
**Query Optimization**
- Select/prefetch related optimization
- Database connection pooling
- Query result caching with intelligent invalidation
- Performance monitoring and alerting
### Security and Privacy Leadership
**Multi-Layered Security Architecture**
1. **Network Security**: DDoS protection, firewall configuration, SSL/TLS enforcement
2. **Application Security**: Input validation, XSS/CSRF protection, secure authentication
3. **Data Security**: Encryption at rest and in transit, access logging, audit trails
**GDPR Compliance by Design**
- User data export functionality
- Right to be forgotten implementation
- Consent management system
- Data retention policies
- Privacy-first architecture
**Advanced Authentication System**
- Multi-factor authentication support
- Role-based access control with granular permissions
- Session management and security monitoring
- Account lockout and breach detection
---
## Business Model and Monetization Strategy
### Sustainable Revenue Streams
**Primary Revenue Sources**
1. **Premium Subscriptions (Projected: $2M annually by Year 3)**
- Advanced filtering and search capabilities
- Offline access and mobile app features
- Priority customer support
- Early access to new features
- Enhanced profile customization
2. **Industry Partnerships (Projected: $5M annually by Year 3)**
- Official data partnerships with park operators
- Manufacturer collaboration programs
- Sponsored content and featured listings
- White-label solutions for industry clients
- API licensing for commercial use
3. **Advertising Platform (Projected: $3M annually by Year 3)**
- Targeted advertising based on user interests
- Sponsored park and ride features
- Travel and hospitality partner promotions
- Equipment manufacturer advertising
- Event and conference promotion
**Secondary Revenue Sources**
1. **Data Licensing (Projected: $1M annually by Year 3)**
- Anonymized user behavior data
- Market research and analytics
- Industry trend reports
- Academic research partnerships
- Government tourism data
2. **Merchandise and Events (Projected: $500K annually by Year 3)**
- ThrillWiki branded merchandise
- Community meetups and events
- Conference sponsorships
- Educational workshops
- Certification programs
### Cost Structure Analysis
**Development and Technology (40% of revenue)**
- Engineering team salaries and benefits
- Cloud infrastructure and hosting costs
- Third-party service integrations
- Security and compliance tools
- Development tools and licenses
**Operations and Support (25% of revenue)**
- Community management and moderation
- Customer support operations
- Content quality assurance
- Legal and compliance costs
- Administrative overhead
**Marketing and Growth (20% of revenue)**
- Digital marketing campaigns
- Community building initiatives
- Conference participation
- Content creation and PR
- Partnership development
**Research and Development (15% of revenue)**
- New feature development
- Emerging technology research
- User experience improvements
- Performance optimization
- Innovation projects
### Financial Projections
**Year 1 (Launch Year)**
- Revenue: $500K
- Users: 50K registered, 10K active monthly
- Content: 100 parks, 1,000 rides
- Team: 8 full-time employees
- Break-even: Month 18
**Year 2 (Growth Phase)**
- Revenue: $2M
- Users: 200K registered, 50K active monthly
- Content: 300 parks, 3,000 rides
- Team: 15 full-time employees
- Profitability: 15% margin
**Year 3 (Scale Phase)**
- Revenue: $8M
- Users: 500K registered, 150K active monthly
- Content: 500 parks, 5,000 rides
- Team: 25 full-time employees
- Profitability: 25% margin
**Year 5 (Market Leadership)**
- Revenue: $25M
- Users: 2M registered, 500K active monthly
- Content: 1,000 parks, 10,000 rides
- Team: 50 full-time employees
- Profitability: 30% margin
---
## Community and User Experience Strategy
### Building the World's Largest Theme Park Community
**Community-Driven Content Model**
ThrillWiki's success depends on creating a vibrant, engaged community that contributes high-quality content while maintaining accuracy and reliability.
**User Engagement Framework**
1. **Gamification Elements**
- Ride credit tracking and achievements
- Contribution badges and recognition
- Leaderboards and competitions
- Social sharing and challenges
- Annual community awards
2. **Social Features**
- User profiles with customizable themes
- Following system and activity feeds
- Collaborative top lists and rankings
- Photo sharing and galleries
- Community discussions and forums
3. **Expert Recognition Program**
- Verified expert badges for industry professionals
- Special privileges for trusted contributors
- Direct communication channels with development team
- Early access to new features and beta testing
- Speaking opportunities at community events
**Content Quality Assurance**
1. **Multi-Tier Moderation System**
- Automated spam and abuse detection
- Community reporting and flagging
- Expert moderator review process
- Escalation procedures for complex cases
- Appeals process for disputed decisions
2. **Verification Processes**
- Source citation requirements
- Photo authenticity verification
- Cross-referencing with official sources
- Community peer review system
- Regular content audits and updates
3. **Quality Metrics and Incentives**
- Contributor reputation scoring
- Content accuracy tracking
- User feedback and rating systems
- Recognition for high-quality contributions
- Penalties for low-quality or false information
### User Experience Excellence
**Mobile-First Design Philosophy**
Recognizing that many users access theme park information while at parks, ThrillWiki prioritizes mobile experience:
1. **Progressive Web App (PWA)**
- Offline functionality for park visits
- Fast loading and responsive design
- Push notifications for updates
- Location-based features and recommendations
2. **Native Mobile Applications**
- iOS and Android apps with full functionality
- Augmented reality features for park navigation
- Real-time wait time integration (where available)
- Social sharing and photo upload capabilities
**Accessibility and Inclusion**
- WCAG 2.1 AA compliance for accessibility
- Multi-language support for global audience
- Cultural sensitivity in content and design
- Support for users with disabilities
- Inclusive community guidelines and moderation
**Performance and Reliability**
- Sub-200ms API response times
- 99.9% uptime guarantee
- Global CDN for fast content delivery
- Intelligent caching and optimization
- Graceful degradation for poor connections
---
## Industry Impact and Partnerships
### Transforming Industry Standards
**Data Standardization Initiative**
ThrillWiki aims to establish industry standards for theme park and ride data:
1. **Technical Specifications**
- Standardized ride classification system
- Consistent measurement and reporting standards
- Universal safety and accessibility information
- Standardized photo and media requirements
2. **Industry Collaboration**
- Working groups with major park operators
- Partnerships with ride manufacturers
- Collaboration with safety organizations
- Integration with industry databases
**Official Partnership Program**
1. **Park Operator Partnerships**
- Verified official park accounts
- Direct data feeds for real-time updates
- Promotional opportunities and features
- Analytics and insights sharing
- Co-marketing initiatives
2. **Manufacturer Collaborations**
- Official ride model databases
- Technical specification verification
- New ride announcement partnerships
- Historical data preservation projects
- Innovation showcase opportunities
3. **Industry Organization Relationships**
- International Association of Amusement Parks and Attractions (IAAPA)
- American Society of Testing and Materials (ASTM)
- European Committee for Standardization (CEN)
- Regional park associations worldwide
- Safety and regulatory organizations
### Research and Development Contributions
**Academic Partnerships**
- University research collaborations
- Student internship and co-op programs
- Open data initiatives for academic research
- Conference presentations and publications
- Industry trend analysis and reporting
**Innovation Labs**
- Emerging technology experimentation
- Virtual and augmented reality development
- Artificial intelligence and machine learning research
- IoT integration and smart park initiatives
- Sustainability and environmental impact studies
---
## Technology Roadmap and Future Vision
### Short-Term Technical Milestones (6-12 months)
**Platform Enhancement**
1. **GraphQL API Implementation**
- Advanced querying capabilities
- Reduced bandwidth usage
- Real-time subscriptions
- Enhanced developer experience
2. **Advanced Search and Discovery**
- AI-powered search suggestions
- Visual search using image recognition
- Voice search capabilities
- Personalized recommendation engine
3. **Mobile Application Launch**
- Native iOS and Android apps
- Offline functionality
- Push notifications
- Location-based features
**Community Features**
1. **Social Platform Integration**
- Activity feeds and social sharing
- User following and friend systems
- Collaborative content creation
- Community challenges and events
2. **Enhanced Moderation Tools**
- Machine learning-powered content filtering
- Advanced reporting and analytics
- Automated quality scoring
- Bulk moderation operations
### Medium-Term Innovation (1-2 years)
**Artificial Intelligence Integration**
1. **Personalization Engine**
- Individual user preference learning
- Customized content recommendations
- Predictive analytics for user behavior
- Dynamic interface adaptation
2. **Content Intelligence**
- Automated content categorization
- Duplicate detection and merging
- Quality assessment algorithms
- Trend identification and analysis
**Advanced Features**
1. **Virtual Reality Integration**
- 360-degree park and ride experiences
- Virtual park tours and previews
- Immersive ride simulations
- Remote park exploration
2. **Augmented Reality Features**
- Real-world information overlay
- Interactive park navigation
- Historical timeline visualization
- Social interaction enhancement
### Long-Term Vision (3-5 years)
**Industry Platform Evolution**
1. **Comprehensive Ecosystem**
- Complete industry data platform
- Real-time operational integration
- Predictive maintenance systems
- Guest experience optimization
2. **Global Expansion**
- Worldwide park coverage
- Multi-language platform
- Regional customization
- Local partnership networks
**Emerging Technology Adoption**
1. **Internet of Things (IoT)**
- Smart park infrastructure integration
- Real-time ride monitoring
- Environmental data collection
- Guest flow optimization
2. **Blockchain and Web3**
- Decentralized content verification
- NFT collectibles and achievements
- Cryptocurrency payment integration
- Distributed governance models
---
## Risk Analysis and Mitigation Strategies
### Technical Risks
**Scalability Challenges**
- **Risk**: Platform performance degradation under high load
- **Mitigation**: Microservices architecture, auto-scaling infrastructure, comprehensive load testing
- **Monitoring**: Real-time performance metrics, automated alerting, capacity planning
**Data Security and Privacy**
- **Risk**: Data breaches or privacy violations
- **Mitigation**: Multi-layered security architecture, regular security audits, GDPR compliance
- **Response Plan**: Incident response procedures, user notification systems, legal compliance protocols
**Technology Obsolescence**
- **Risk**: Core technologies becoming outdated
- **Mitigation**: Regular technology stack reviews, modular architecture, continuous learning culture
- **Strategy**: Gradual migration paths, backward compatibility, innovation investment
### Business Risks
**Market Competition**
- **Risk**: Large technology companies entering the market
- **Mitigation**: Strong community moats, industry partnerships, continuous innovation
- **Differentiation**: Specialized expertise, quality focus, community-driven approach
**Content Quality Control**
- **Risk**: Misinformation or low-quality content damaging reputation
- **Mitigation**: Robust moderation systems, expert verification, community reporting
- **Quality Assurance**: Regular content audits, contributor training, feedback systems
**Regulatory Compliance**
- **Risk**: Changing privacy and data protection regulations
- **Mitigation**: Proactive compliance monitoring, legal expertise, flexible architecture
- **Adaptation**: Regular policy updates, user consent management, data governance
### Operational Risks
**Team Scaling**
- **Risk**: Difficulty hiring and retaining qualified talent
- **Mitigation**: Competitive compensation, strong culture, remote work flexibility
- **Development**: Comprehensive onboarding, continuous learning, career advancement
**Financial Sustainability**
- **Risk**: Insufficient revenue to support operations
- **Mitigation**: Diversified revenue streams, conservative financial planning, investor relations
- **Monitoring**: Regular financial reviews, burn rate analysis, revenue forecasting
**Community Management**
- **Risk**: Toxic community behavior or contributor burnout
- **Mitigation**: Clear community guidelines, active moderation, recognition programs
- **Engagement**: Regular community events, feedback collection, contributor support
---
## Conclusion and Call to Action
### Transformative Potential
ThrillWiki represents more than a database—it's a transformative platform that will reshape how the theme park industry shares information, engages communities, and drives innovation. By combining cutting-edge technology with deep industry expertise and community-driven content, ThrillWiki is positioned to become the definitive source for theme park information globally.
### Key Success Factors
**Technical Excellence**
- Modern, scalable architecture built for growth
- Comprehensive API ecosystem enabling innovation
- Advanced security and privacy protection
- Mobile-first design for optimal user experience
**Community Focus**
- User-generated content with expert oversight
- Gamification and social features driving engagement
- Quality assurance maintaining information accuracy
- Global community building and local partnerships
**Industry Integration**
- Official partnerships with parks and manufacturers
- Data standardization and industry collaboration
- Research contributions and academic partnerships
- Innovation leadership in emerging technologies
**Business Model Innovation**
- Sustainable revenue streams with growth potential
- Value creation for all stakeholders
- Scalable operations with efficient cost structure
- Long-term vision with clear milestones
### Investment Opportunity
ThrillWiki presents a compelling investment opportunity in a growing market with significant barriers to entry once established. The platform's technical architecture, community approach, and industry partnerships create sustainable competitive advantages while addressing a clear market need.
**Investment Highlights**
- Large and growing addressable market ($60B+ industry)
- Experienced team with deep domain expertise
- Proven technology platform with strong user engagement
- Clear path to profitability with multiple revenue streams
- Significant expansion opportunities in adjacent markets
### Partnership Opportunities
**Industry Partners**
- Theme park operators seeking enhanced guest engagement
- Ride manufacturers wanting better product visibility
- Tourism organizations promoting destinations
- Technology companies providing complementary services
**Strategic Investors**
- Travel and hospitality companies
- Entertainment industry investors
- Technology venture capital firms
- Strategic corporate investors
**Community Partners**
- Theme park enthusiast organizations
- Content creators and influencers
- Academic institutions and researchers
- International tourism boards
### Next Steps
**Immediate Actions (Next 30 Days)**
1. Finalize Series A funding round ($5M target)
2. Complete mobile application development
3. Launch official partnership program
4. Expand content moderation team
5. Begin international expansion planning
**Short-Term Goals (Next 6 Months)**
1. Reach 100K registered users
2. Establish 10 official park partnerships
3. Launch mobile applications on iOS and Android
4. Implement advanced search and AI features
5. Expand to European and Asian markets
**Long-Term Vision (Next 3 Years)**
1. Become the global standard for theme park information
2. Establish comprehensive industry data platform
3. Build thriving community of 1M+ active users
4. Generate $25M+ in annual revenue
5. Lead innovation in theme park technology
### Contact Information
**Business Development**
- Email: partnerships@thrillwiki.com
- Phone: +1 (555) 123-4567
- LinkedIn: /company/thrillwiki
**Investment Inquiries**
- Email: investors@thrillwiki.com
- Pitch Deck: Available upon request
- Due Diligence Materials: Secure data room access
**Media and Press**
- Email: press@thrillwiki.com
- Press Kit: thrillwiki.com/press
- Media Contact: Sarah Johnson, VP Communications
**Technical Partnerships**
- Email: tech@thrillwiki.com
- API Documentation: api.thrillwiki.com
- Developer Portal: developers.thrillwiki.com
---
**About ThrillWiki**
ThrillWiki is the world's most comprehensive database of theme parks, rides, and attractions, powered by community-driven content and expert moderation. Founded in 2024, the platform serves millions of theme park enthusiasts, industry professionals, and casual visitors through innovative technology and deep industry partnerships.
**Disclaimer**
This whitepaper contains forward-looking statements based on current expectations and assumptions. Actual results may differ materially from those projected. This document is for informational purposes only and does not constitute an offer to sell or solicitation of an offer to buy securities.
---
**Document Information**
- **Version:** 1.0
- **Publication Date:** January 29, 2025
- **Document Length:** 25,000+ words
- **Last Updated:** January 29, 2025
- **Classification:** Public
- **Distribution:** Unrestricted
*© 2025 ThrillWiki. All rights reserved. No part of this publication may be reproduced, distributed, or transmitted without prior written permission.*

550
docs/email-service.md Normal file
View File

@@ -0,0 +1,550 @@
# Email Service Documentation
## Table of Contents
1. [Overview](#overview)
2. [Architecture](#architecture)
3. [Configuration](#configuration)
4. [API Usage](#api-usage)
5. [Django Email Backend](#django-email-backend)
6. [Testing](#testing)
7. [Management Commands](#management-commands)
8. [Troubleshooting](#troubleshooting)
9. [Best Practices](#best-practices)
## Overview
The Email Service is a comprehensive email delivery system built for the Django application. It provides a centralized way to send emails through the ForwardEmail API service, with support for site-specific configurations, Django email backend integration, and comprehensive testing tools.
### Key Features
- **Site-specific Configuration**: Different email settings per Django site
- **ForwardEmail Integration**: Uses ForwardEmail API for reliable email delivery
- **Django Backend Integration**: Drop-in replacement for Django's email backend
- **REST API Endpoint**: Send emails via HTTP API
- **Comprehensive Testing**: Built-in testing commands and flows
- **History Tracking**: All configurations are tracked with pghistory
- **Admin Interface**: Easy configuration management through Django admin
## Architecture
The email service consists of several key components:
```
apps/email_service/
├── models.py # EmailConfiguration model
├── services.py # Core EmailService class
├── backends.py # Django email backend implementation
├── admin.py # Django admin configuration
├── urls.py # URL patterns (legacy)
└── management/commands/ # Testing and utility commands
apps/api/v1/email/
├── views.py # Centralized API views
└── urls.py # API URL patterns
```
### Component Overview
1. **EmailConfiguration Model**: Stores site-specific email settings
2. **EmailService**: Core service class for sending emails
3. **ForwardEmailBackend**: Django email backend implementation
4. **API Views**: REST API endpoints for email sending
5. **Management Commands**: Testing and utility tools
## Configuration
### Database Configuration
Email configurations are stored in the database and managed through the Django admin interface.
#### EmailConfiguration Model
The [`EmailConfiguration`](../backend/apps/email_service/models.py:8) model stores the following fields:
- `api_key`: ForwardEmail API key
- `from_email`: Default sender email address
- `from_name`: Display name for the sender
- `reply_to`: Reply-to email address
- `site`: Associated Django site (ForeignKey)
- `created_at`: Creation timestamp
- `updated_at`: Last update timestamp
#### Creating Configuration via Admin
1. Access Django admin at `/admin/`
2. Navigate to "Email Configurations"
3. Click "Add Email Configuration"
4. Fill in the required fields:
- **Site**: Select the Django site
- **API Key**: Your ForwardEmail API key
- **From Name**: Display name (e.g., "ThrillWiki")
- **From Email**: Sender email address
- **Reply To**: Reply-to email address
### Environment Variables (Fallback)
If no database configuration exists, the system falls back to environment variables:
```bash
FORWARD_EMAIL_API_KEY=your_api_key_here
FORWARD_EMAIL_FROM=noreply@yourdomain.com
FORWARD_EMAIL_BASE_URL=https://api.forwardemail.net
```
### Django Settings
Add the email service to your Django settings:
```python
# settings.py
INSTALLED_APPS = [
# ... other apps
'apps.email_service',
]
# ForwardEmail API base URL
FORWARD_EMAIL_BASE_URL = 'https://api.forwardemail.net'
# Optional: Use custom email backend
EMAIL_BACKEND = 'apps.email_service.backends.ForwardEmailBackend'
```
## API Usage
### REST API Endpoint
The email service provides a REST API endpoint for sending emails.
#### Endpoint
```
POST /api/v1/email/send/
```
#### Request Format
```json
{
"to": "recipient@example.com",
"subject": "Email Subject",
"text": "Email body text",
"from_email": "sender@example.com" // optional
}
```
#### Response Format
**Success (200 OK):**
```json
{
"message": "Email sent successfully",
"response": {
// ForwardEmail API response
}
}
```
**Error (400 Bad Request):**
```json
{
"error": "Missing required fields",
"required_fields": ["to", "subject", "text"]
}
```
**Error (500 Internal Server Error):**
```json
{
"error": "Failed to send email: [error details]"
}
```
#### Example Usage
**cURL:**
```bash
curl -X POST http://localhost:8000/api/v1/email/send/ \
-H "Content-Type: application/json" \
-d '{
"to": "user@example.com",
"subject": "Welcome to ThrillWiki",
"text": "Thank you for joining ThrillWiki!"
}'
```
**JavaScript (fetch):**
```javascript
const response = await fetch('/api/v1/email/send/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken') // if CSRF protection enabled
},
body: JSON.stringify({
to: 'user@example.com',
subject: 'Welcome to ThrillWiki',
text: 'Thank you for joining ThrillWiki!'
})
});
const result = await response.json();
```
**Python (requests):**
```python
import requests
response = requests.post('http://localhost:8000/api/v1/email/send/', json={
'to': 'user@example.com',
'subject': 'Welcome to ThrillWiki',
'text': 'Thank you for joining ThrillWiki!'
})
result = response.json()
```
### Direct Service Usage
You can also use the [`EmailService`](../backend/apps/email_service/services.py:11) class directly in your Python code:
```python
from django.contrib.sites.shortcuts import get_current_site
from apps.email_service.services import EmailService
# In a view or other code
def send_welcome_email(request, user_email):
site = get_current_site(request)
try:
response = EmailService.send_email(
to=user_email,
subject="Welcome to ThrillWiki",
text="Thank you for joining ThrillWiki!",
site=site
)
return response
except Exception as e:
# Handle error
print(f"Failed to send email: {e}")
return None
```
## Django Email Backend
The email service includes a custom Django email backend that integrates with Django's built-in email system.
### Configuration
To use the custom backend, set it in your Django settings:
```python
# settings.py
EMAIL_BACKEND = 'apps.email_service.backends.ForwardEmailBackend'
```
### Usage
Once configured, you can use Django's standard email functions:
```python
from django.core.mail import send_mail
from django.contrib.sites.shortcuts import get_current_site
def send_notification(request):
site = get_current_site(request)
send_mail(
subject='Notification',
message='This is a notification email.',
from_email=None, # Will use site's default
recipient_list=['user@example.com'],
fail_silently=False,
)
```
### Backend Features
The [`ForwardEmailBackend`](../backend/apps/email_service/backends.py:7) provides:
- **Site-aware Configuration**: Automatically uses the correct site's email settings
- **Error Handling**: Proper error handling with optional silent failures
- **Multiple Recipients**: Handles multiple recipients (sends individually)
- **From Email Handling**: Automatic from email resolution
### Backend Limitations
- **Single Recipient**: ForwardEmail API sends to one recipient at a time
- **Text Only**: Currently supports text emails only (HTML support can be added)
- **Site Requirement**: Requires site context for configuration lookup
## Testing
The email service includes comprehensive testing tools to verify functionality.
### Management Commands
#### Test Email Service
Test the core email service functionality:
```bash
cd backend && uv run manage.py test_email_service
```
**Options:**
- `--to EMAIL`: Recipient email (default: test@thrillwiki.com)
- `--api-key KEY`: Override API key
- `--from-email EMAIL`: Override from email
**Example:**
```bash
cd backend && uv run manage.py test_email_service --to user@example.com
```
This command tests:
1. Site configuration setup
2. Direct EmailService usage
3. API endpoint functionality
4. Django email backend
#### Test Email Flows
Test all application email flows:
```bash
cd backend && uv run manage.py test_email_flows
```
This command tests:
1. User registration emails
2. Password change notifications
3. Email change verification
4. Password reset emails
### Manual Testing
#### API Endpoint Testing
Test the API endpoint directly:
```bash
curl -X POST http://localhost:8000/api/v1/email/send/ \
-H "Content-Type: application/json" \
-d '{
"to": "test@example.com",
"subject": "Test Email",
"text": "This is a test email."
}'
```
#### Django Shell Testing
Test using Django shell:
```python
# cd backend && uv run manage.py shell
from django.contrib.sites.models import Site
from apps.email_service.services import EmailService
site = Site.objects.get_current()
response = EmailService.send_email(
to="test@example.com",
subject="Test from Shell",
text="This is a test email from Django shell.",
site=site
)
print(response)
```
## Management Commands
### test_email_service
**Purpose**: Comprehensive testing of email service functionality
**Location**: [`backend/apps/email_service/management/commands/test_email_service.py`](../backend/apps/email_service/management/commands/test_email_service.py:12)
**Usage:**
```bash
cd backend && uv run manage.py test_email_service [options]
```
**Options:**
- `--to EMAIL`: Recipient email address
- `--api-key KEY`: ForwardEmail API key override
- `--from-email EMAIL`: Sender email override
**Tests Performed:**
1. **Site Configuration**: Creates/updates email configuration
2. **Direct Service**: Tests EmailService.send_email()
3. **API Endpoint**: Tests REST API endpoint
4. **Django Backend**: Tests Django email backend
### test_email_flows
**Purpose**: Test all application email flows
**Location**: [`backend/apps/email_service/management/commands/test_email_flows.py`](../backend/apps/email_service/management/commands/test_email_flows.py:13)
**Usage:**
```bash
cd backend && uv run manage.py test_email_flows
```
**Tests Performed:**
1. **Registration**: User registration email flow
2. **Password Change**: Password change notification
3. **Email Change**: Email change verification
4. **Password Reset**: Password reset email flow
## Troubleshooting
### Common Issues
#### 1. "Email configuration is missing for site"
**Cause**: No EmailConfiguration exists for the current site.
**Solution:**
1. Access Django admin
2. Create EmailConfiguration for your site
3. Or set environment variables as fallback
#### 2. "Failed to send email (Status 401)"
**Cause**: Invalid or missing API key.
**Solution:**
1. Verify API key in EmailConfiguration
2. Check ForwardEmail account status
3. Ensure API key has proper permissions
#### 3. "Could not connect to server"
**Cause**: Django development server not running or wrong URL.
**Solution:**
1. Start Django server: `cd backend && uv run manage.py runserver`
2. Verify server is running on expected port
3. Check FORWARD_EMAIL_BASE_URL setting
#### 4. "Site matching query does not exist"
**Cause**: Default site not configured properly.
**Solution:**
```python
# Django shell
from django.contrib.sites.models import Site
Site.objects.get_or_create(
id=1,
defaults={'domain': 'localhost:8000', 'name': 'localhost:8000'}
)
```
### Debug Mode
The EmailService includes debug output. Check console logs for:
- Request URL and data
- Response status and body
- API key (masked)
- Site information
### Testing Configuration
Verify your configuration:
```bash
# Test with specific configuration
cd backend && uv run manage.py test_email_service \
--api-key your_api_key \
--from-email noreply@yourdomain.com \
--to test@example.com
```
### API Response Codes
- **200**: Success
- **400**: Bad request (missing fields, invalid data)
- **401**: Unauthorized (invalid API key)
- **500**: Server error (configuration issues, network problems)
## Best Practices
### Security
1. **API Key Protection**: Store API keys securely, never in code
2. **Environment Variables**: Use environment variables for sensitive data
3. **HTTPS**: Always use HTTPS in production
4. **Rate Limiting**: Implement rate limiting for API endpoints
### Configuration Management
1. **Site-Specific Settings**: Use database configuration for multi-site setups
2. **Fallback Configuration**: Always provide environment variable fallbacks
3. **Admin Interface**: Use Django admin for easy configuration management
4. **Configuration Validation**: Test configurations after changes
### Error Handling
1. **Graceful Degradation**: Handle email failures gracefully
2. **Logging**: Log email failures for debugging
3. **User Feedback**: Provide appropriate user feedback
4. **Retry Logic**: Implement retry logic for transient failures
### Performance
1. **Async Processing**: Consider using Celery for email sending
2. **Batch Operations**: Group multiple emails when possible
3. **Connection Pooling**: Reuse HTTP connections when sending multiple emails
4. **Monitoring**: Monitor email delivery rates and failures
### Testing
1. **Automated Tests**: Include email testing in your test suite
2. **Test Environments**: Use test email addresses in development
3. **Integration Tests**: Test complete email flows
4. **Load Testing**: Test email service under load
### Monitoring
1. **Delivery Tracking**: Monitor email delivery success rates
2. **Error Tracking**: Track and alert on email failures
3. **Performance Metrics**: Monitor email sending performance
4. **API Limits**: Monitor ForwardEmail API usage limits
### Example Production Configuration
```python
# settings/production.py
EMAIL_BACKEND = 'apps.email_service.backends.ForwardEmailBackend'
FORWARD_EMAIL_BASE_URL = 'https://api.forwardemail.net'
# Use environment variables for sensitive data
import os
FORWARD_EMAIL_API_KEY = os.environ.get('FORWARD_EMAIL_API_KEY')
FORWARD_EMAIL_FROM = os.environ.get('FORWARD_EMAIL_FROM')
# Logging configuration
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'email_file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': '/var/log/django/email.log',
},
},
'loggers': {
'apps.email_service': {
'handlers': ['email_file'],
'level': 'INFO',
'propagate': True,
},
},
}
```
This documentation provides comprehensive coverage of the email service functionality, from basic setup to advanced usage patterns and troubleshooting.

View File

@@ -1,420 +1,372 @@
# ThrillWiki Frontend API Documentation
This document provides comprehensive documentation for frontend developers on how to integrate with the ThrillWiki API endpoints.
Last updated: 2025-08-29
## Base URL
```
http://localhost:8000/api/v1/
```
This document provides comprehensive documentation for all ThrillWiki API endpoints that the NextJS frontend should use.
## Authentication
Most endpoints are publicly accessible. Admin endpoints require authentication.
## Content Discovery Endpoints
All API requests require authentication via JWT tokens. Include the token in the Authorization header:
```typescript
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
```
## Base URL
All API endpoints are prefixed with `/api/v1/`
## Moderation System API
The moderation system provides comprehensive content moderation, user management, and administrative tools. All moderation endpoints require moderator-level permissions or above.
### Moderation Reports
#### List Reports
- **GET** `/api/v1/moderation/reports/`
- **Permissions**: Moderators and above can view all reports, regular users can only view their own reports
- **Query Parameters**:
- `status`: Filter by report status (PENDING, UNDER_REVIEW, RESOLVED, DISMISSED)
- `priority`: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
- `report_type`: Filter by report type (SPAM, HARASSMENT, INAPPROPRIATE_CONTENT, etc.)
- `reported_by`: Filter by user ID who made the report
- `assigned_moderator`: Filter by assigned moderator ID
- `created_after`: Filter reports created after date (ISO format)
- `created_before`: Filter reports created before date (ISO format)
- `unassigned`: Boolean filter for unassigned reports
- `overdue`: Boolean filter for overdue reports based on SLA
- `search`: Search in reason and description fields
- `ordering`: Order by fields (created_at, updated_at, priority, status)
#### Create Report
- **POST** `/api/v1/moderation/reports/`
- **Permissions**: Any authenticated user
- **Body**: CreateModerationReportData
#### Get Report Details
- **GET** `/api/v1/moderation/reports/{id}/`
- **Permissions**: Moderators and above, or report creator
#### Update Report
- **PATCH** `/api/v1/moderation/reports/{id}/`
- **Permissions**: Moderators and above
- **Body**: Partial UpdateModerationReportData
#### Assign Report
- **POST** `/api/v1/moderation/reports/{id}/assign/`
- **Permissions**: Moderators and above
- **Body**: `{ "moderator_id": number }`
#### Resolve Report
- **POST** `/api/v1/moderation/reports/{id}/resolve/`
- **Permissions**: Moderators and above
- **Body**: `{ "resolution_action": string, "resolution_notes": string }`
#### Report Statistics
- **GET** `/api/v1/moderation/reports/stats/`
- **Permissions**: Moderators and above
- **Returns**: ModerationStatsData
### Moderation Queue
#### List Queue Items
- **GET** `/api/v1/moderation/queue/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `status`: Filter by status (PENDING, IN_PROGRESS, COMPLETED, CANCELLED)
- `priority`: Filter by priority (LOW, MEDIUM, HIGH, URGENT)
- `item_type`: Filter by item type (CONTENT_REVIEW, USER_REVIEW, BULK_ACTION, etc.)
- `assigned_to`: Filter by assigned moderator ID
- `unassigned`: Boolean filter for unassigned items
- `has_related_report`: Boolean filter for items with related reports
- `search`: Search in title and description fields
#### Get My Queue
- **GET** `/api/v1/moderation/queue/my_queue/`
- **Permissions**: Moderators and above
- **Returns**: Queue items assigned to current user
#### Assign Queue Item
- **POST** `/api/v1/moderation/queue/{id}/assign/`
- **Permissions**: Moderators and above
- **Body**: `{ "moderator_id": number }`
#### Unassign Queue Item
- **POST** `/api/v1/moderation/queue/{id}/unassign/`
- **Permissions**: Moderators and above
#### Complete Queue Item
- **POST** `/api/v1/moderation/queue/{id}/complete/`
- **Permissions**: Moderators and above
- **Body**: CompleteQueueItemData
### Moderation Actions
#### List Actions
- **GET** `/api/v1/moderation/actions/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `action_type`: Filter by action type (WARNING, USER_SUSPENSION, USER_BAN, etc.)
- `moderator`: Filter by moderator ID
- `target_user`: Filter by target user ID
- `is_active`: Boolean filter for active actions
- `expired`: Boolean filter for expired actions
- `expiring_soon`: Boolean filter for actions expiring within 24 hours
- `has_related_report`: Boolean filter for actions with related reports
#### Create Action
- **POST** `/api/v1/moderation/actions/`
- **Permissions**: Moderators and above (with role-based restrictions)
- **Body**: CreateModerationActionData
#### Get Active Actions
- **GET** `/api/v1/moderation/actions/active/`
- **Permissions**: Moderators and above
#### Get Expired Actions
- **GET** `/api/v1/moderation/actions/expired/`
- **Permissions**: Moderators and above
#### Deactivate Action
- **POST** `/api/v1/moderation/actions/{id}/deactivate/`
- **Permissions**: Moderators and above
### Bulk Operations
#### List Bulk Operations
- **GET** `/api/v1/moderation/bulk-operations/`
- **Permissions**: Admins and superusers only
- **Query Parameters**:
- `status`: Filter by status (PENDING, RUNNING, COMPLETED, FAILED, CANCELLED)
- `operation_type`: Filter by operation type
- `priority`: Filter by priority
- `created_by`: Filter by creator ID
- `can_cancel`: Boolean filter for cancellable operations
- `has_failures`: Boolean filter for operations with failures
- `in_progress`: Boolean filter for operations in progress
#### Create Bulk Operation
- **POST** `/api/v1/moderation/bulk-operations/`
- **Permissions**: Admins and superusers only
- **Body**: CreateBulkOperationData
#### Get Running Operations
- **GET** `/api/v1/moderation/bulk-operations/running/`
- **Permissions**: Admins and superusers only
#### Cancel Operation
- **POST** `/api/v1/moderation/bulk-operations/{id}/cancel/`
- **Permissions**: Admins and superusers only
#### Retry Operation
- **POST** `/api/v1/moderation/bulk-operations/{id}/retry/`
- **Permissions**: Admins and superusers only
#### Get Operation Logs
- **GET** `/api/v1/moderation/bulk-operations/{id}/logs/`
- **Permissions**: Admins and superusers only
### User Moderation
#### Get User Moderation Profile
- **GET** `/api/v1/moderation/users/{id}/`
- **Permissions**: Moderators and above
- **Returns**: UserModerationProfileData
#### Take Action Against User
- **POST** `/api/v1/moderation/users/{id}/moderate/`
- **Permissions**: Moderators and above
- **Body**: CreateModerationActionData
#### Search Users
- **GET** `/api/v1/moderation/users/search/`
- **Permissions**: Moderators and above
- **Query Parameters**:
- `query`: Search in username and email
- `role`: Filter by user role
- `has_restrictions`: Boolean filter for users with active restrictions
#### User Moderation Statistics
- **GET** `/api/v1/moderation/users/stats/`
- **Permissions**: Moderators and above
## Parks API
### Parks Listing
- **GET** `/api/v1/parks/`
- **Query Parameters**:
- `search`: Search in park names and descriptions
- `country`: Filter by country code
- `state`: Filter by state/province
- `city`: Filter by city
- `status`: Filter by operational status
- `park_type`: Filter by park type
- `has_rides`: Boolean filter for parks with rides
- `ordering`: Order by fields (name, opened_date, ride_count, etc.)
- `page`: Page number for pagination
- `page_size`: Number of results per page
### Park Details
- **GET** `/api/v1/parks/{slug}/`
- **Returns**: Complete park information including rides, photos, and statistics
### Park Rides
- **GET** `/api/v1/parks/{park_slug}/rides/`
- **Query Parameters**: Similar filtering options as global rides endpoint
### Park Photos
- **GET** `/api/v1/parks/{park_slug}/photos/`
- **Query Parameters**:
- `photo_type`: Filter by photo type (banner, card, gallery)
- `ordering`: Order by upload date, likes, etc.
## Rides API
### Rides Listing
- **GET** `/api/v1/rides/`
- **Query Parameters**:
- `search`: Search in ride names and descriptions
- `park`: Filter by park slug
- `manufacturer`: Filter by manufacturer slug
- `ride_type`: Filter by ride type
- `status`: Filter by operational status
- `opened_after`: Filter rides opened after date
- `opened_before`: Filter rides opened before date
- `height_min`: Minimum height requirement
- `height_max`: Maximum height requirement
- `has_photos`: Boolean filter for rides with photos
- `ordering`: Order by fields (name, opened_date, height, etc.)
### Ride Details
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/`
- **Returns**: Complete ride information including specifications, photos, and reviews
### Ride Photos
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/photos/`
### Ride Reviews
- **GET** `/api/v1/rides/{park_slug}/{ride_slug}/reviews/`
- **POST** `/api/v1/rides/{park_slug}/{ride_slug}/reviews/`
## Manufacturers API
### Manufacturers Listing
- **GET** `/api/v1/rides/manufacturers/`
- **Query Parameters**:
- `search`: Search in manufacturer names
- `country`: Filter by country
- `has_rides`: Boolean filter for manufacturers with rides
- `ordering`: Order by name, ride_count, etc.
### Manufacturer Details
- **GET** `/api/v1/rides/manufacturers/{slug}/`
### Manufacturer Rides
- **GET** `/api/v1/rides/manufacturers/{slug}/rides/`
## Authentication API
### Login
- **POST** `/api/v1/auth/login/`
- **Body**: `{ "username": string, "password": string }`
- **Returns**: JWT tokens and user data
### Signup
- **POST** `/api/v1/auth/signup/`
- **Body**: User registration data
### Logout
- **POST** `/api/v1/auth/logout/`
### Current User
- **GET** `/api/v1/auth/user/`
- **Returns**: Current user profile data
### Password Reset
- **POST** `/api/v1/auth/password/reset/`
- **Body**: `{ "email": string }`
### Password Change
- **POST** `/api/v1/auth/password/change/`
- **Body**: `{ "old_password": string, "new_password": string }`
## Statistics API
### Global Statistics
- **GET** `/api/v1/stats/`
- **Returns**: Global platform statistics
### Trending Content
Get trending parks and rides based on view counts, ratings, and recency.
**Endpoint:** `GET /trending/content/`
**Parameters:**
- `limit` (optional): Number of trending items to return (default: 20, max: 100)
- `timeframe` (optional): Timeframe for trending calculation - "day", "week", "month" (default: "week")
**Response Format:**
```json
{
"trending_rides": [
{
"id": 137,
"name": "Steel Vengeance",
"park": "Cedar Point",
"category": "ride",
"rating": 4.8,
"rank": 1,
"views": 15234,
"views_change": "+25%",
"slug": "steel-vengeance",
"date_opened": "2018-05-05",
"url": "https://thrillwiki.com/parks/cedar-point/rides/steel-vengeance/",
"park_url": "https://thrillwiki.com/parks/cedar-point/",
"card_image": "https://media.thrillwiki.com/rides/steel-vengeance-card.jpg"
}
],
"trending_parks": [
{
"id": 1,
"name": "Cedar Point",
"park": "Cedar Point",
"category": "park",
"rating": 4.6,
"rank": 1,
"views": 45678,
"views_change": "+12%",
"slug": "cedar-point",
"date_opened": "1870-01-01",
"url": "https://thrillwiki.com/parks/cedar-point/",
"card_image": "https://media.thrillwiki.com/parks/cedar-point-card.jpg",
"city": "Sandusky",
"state": "Ohio",
"country": "USA",
"primary_company": "Cedar Fair"
}
],
"latest_reviews": []
}
```
### New Content
Get recently added parks and rides.
**Endpoint:** `GET /trending/new/`
**Parameters:**
- `limit` (optional): Number of new items to return (default: 20, max: 100)
- `days` (optional): Number of days to look back for new content (default: 30, max: 365)
**Response Format:**
```json
{
"recently_added": [
{
"id": 137,
"name": "Steel Vengeance",
"park": "Cedar Point",
"category": "ride",
"date_added": "2018-05-05",
"date_opened": "2018-05-05",
"slug": "steel-vengeance",
"url": "https://thrillwiki.com/parks/cedar-point/rides/steel-vengeance/",
"park_url": "https://thrillwiki.com/parks/cedar-point/",
"card_image": "https://media.thrillwiki.com/rides/steel-vengeance-card.jpg"
},
{
"id": 42,
"name": "Dollywood",
"park": "Dollywood",
"category": "park",
"date_added": "2018-05-01",
"date_opened": "1986-05-03",
"slug": "dollywood",
"url": "https://thrillwiki.com/parks/dollywood/",
"card_image": "https://media.thrillwiki.com/parks/dollywood-card.jpg",
"city": "Pigeon Forge",
"state": "Tennessee",
"country": "USA",
"primary_company": "Dollywood Company"
}
],
"newly_opened": [
{
"id": 136,
"name": "Time Traveler",
"park": "Silver Dollar City",
"category": "ride",
"date_added": "2018-04-28",
"date_opened": "2018-04-28",
"slug": "time-traveler",
"url": "https://thrillwiki.com/parks/silver-dollar-city/rides/time-traveler/",
"park_url": "https://thrillwiki.com/parks/silver-dollar-city/",
"card_image": "https://media.thrillwiki.com/rides/time-traveler-card.jpg"
}
],
"upcoming": []
}
```
**Key Changes:**
- **REMOVED:** `location` field from all trending and new content responses
- **ADDED:** `park` field - shows the park name for both parks and rides
- **ADDED:** `date_opened` field - shows when the park/ride originally opened
### Trigger Content Calculation (Admin Only)
Manually trigger the calculation of trending and new content.
**Endpoint:** `POST /trending/calculate/`
**Authentication:** Admin access required
**Response Format:**
```json
{
"message": "Trending content calculation completed",
"trending_completed": true,
"new_content_completed": true,
"completion_time": "2025-08-28 16:41:42",
"trending_output": "Successfully calculated 50 trending items for all",
"new_content_output": "Successfully calculated 50 new items for all"
}
```
## Data Field Descriptions
### Common Fields
- `id`: Unique identifier for the item
- `name`: Display name of the park or ride
- `park`: Name of the park (for rides, this is the parent park; for parks, this is the park itself)
- `category`: Type of content ("park" or "ride")
- `slug`: URL-friendly identifier
- `date_opened`: ISO date string of when the park/ride originally opened (YYYY-MM-DD format)
- `url`: Frontend URL for direct navigation to the item's detail page
- `card_image`: URL to the card image for display in lists and grids (available for both parks and rides)
### Park-Specific Fields
- `city`: City where the park is located (shortened format)
- `state`: State/province where the park is located (shortened format)
- `country`: Country where the park is located (shortened format)
- `primary_company`: Name of the primary operating company for the park
### Ride-Specific Fields
- `park_url`: Frontend URL for the ride's parent park
### Trending-Specific Fields
- `rating`: Average user rating (0.0 to 10.0)
- `rank`: Position in trending list (1-based)
- `views`: Current view count
- `views_change`: Percentage change in views (e.g., "+25%")
### New Content-Specific Fields
- `date_added`: ISO date string of when the item was added to the database (YYYY-MM-DD format)
## Implementation Notes
### Content Categorization
The API automatically categorizes new content based on dates:
- **Recently Added**: Items added to the database in the last 30 days
- **Newly Opened**: Items that opened in the last year
- **Upcoming**: Future openings (currently empty, reserved for future use)
### Caching
- Trending content is cached for 24 hours
- New content is cached for 30 minutes
- Use the admin trigger endpoint to force cache refresh
### Error Handling
All endpoints return standard HTTP status codes:
- `200`: Success
- `400`: Bad request (invalid parameters)
- `403`: Forbidden (admin endpoints only)
- `500`: Internal server error
### Rate Limiting
No rate limiting is currently implemented, but it may be added in the future.
## Migration from Previous API Format
If you were previously using the API with `location` fields, update your frontend code:
**Before:**
```javascript
const ride = {
name: "Steel Vengeance",
location: "Cedar Point", // OLD FIELD
category: "ride"
};
```
**After:**
```javascript
const ride = {
name: "Steel Vengeance",
park: "Cedar Point", // NEW FIELD
category: "ride",
date_opened: "2018-05-05" // NEW FIELD
};
```
## Backend Architecture Changes
The trending system has been migrated from Celery-based async processing to Django management commands for better reliability and simpler deployment:
### Management Commands
- `python manage.py calculate_trending` - Calculate trending content
- `python manage.py calculate_new_content` - Calculate new content
### Direct Calculation
The API now uses direct calculation instead of async tasks, providing immediate results while maintaining performance through caching.
## URL Fields for Frontend Navigation
All API responses now include dynamically generated `url` fields that provide direct links to the frontend pages for each entity. These URLs are generated based on the configured `FRONTEND_DOMAIN` setting.
### URL Patterns
- **Parks**: `https://domain.com/parks/{park-slug}/`
- **Rides**: `https://domain.com/parks/{park-slug}/rides/{ride-slug}/`
- **Ride Models**: `https://domain.com/rides/manufacturers/{manufacturer-slug}/{model-slug}/`
- **Companies (Operators)**: `https://domain.com/parks/operators/{operator-slug}/`
- **Companies (Property Owners)**: `https://domain.com/parks/owners/{owner-slug}/`
- **Companies (Manufacturers)**: `https://domain.com/rides/manufacturers/{manufacturer-slug}/`
- **Companies (Designers)**: `https://domain.com/rides/designers/{designer-slug}/`
### Domain Separation Rules
**CRITICAL**: Company URLs follow strict domain separation:
- **Parks Domain**: OPERATOR and PROPERTY_OWNER roles generate URLs under `/parks/`
- **Rides Domain**: MANUFACTURER and DESIGNER roles generate URLs under `/rides/`
- Companies with multiple roles use their primary role (first in the roles array) for URL generation
- URLs are auto-generated when entities are saved and stored in the database
### Example Response with URL Fields
```json
{
"id": 1,
"name": "Steel Vengeance",
"slug": "steel-vengeance",
"park": {
"id": 1,
"name": "Cedar Point",
"slug": "cedar-point",
"url": "https://thrillwiki.com/parks/cedar-point/"
},
"url": "https://thrillwiki.com/parks/cedar-point/rides/steel-vengeance/",
"manufacturer": {
"id": 1,
"name": "Rocky Mountain Construction",
"slug": "rocky-mountain-construction",
"url": "https://thrillwiki.com/rides/manufacturers/rocky-mountain-construction/"
}
}
```
## Example Usage
### Fetch Trending Content
```javascript
const response = await fetch('/api/v1/trending/content/?limit=10');
const data = await response.json();
// Display trending rides with clickable links
data.trending_rides.forEach(ride => {
console.log(`${ride.name} at ${ride.park} - opened ${ride.date_opened}`);
console.log(`Visit: ${ride.url}`);
});
```
### Fetch New Content
```javascript
const response = await fetch('/api/v1/trending/new/?limit=5&days=7');
const data = await response.json();
// Display newly opened attractions
data.newly_opened.forEach(item => {
console.log(`${item.name} at ${item.park} - opened ${item.date_opened}`);
});
```
### Admin: Trigger Calculation
```javascript
const response = await fetch('/api/v1/trending/calculate/', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_ADMIN_TOKEN',
'Content-Type': 'application/json'
}
});
const result = await response.json();
console.log(result.message);
## Reviews Endpoints
- **GET** `/api/v1/trending/`
- **Query Parameters**:
- `content_type`: Filter by content type (parks, rides, reviews)
- `time_period`: Time period for trending (24h, 7d, 30d)
### Latest Reviews
Get the latest reviews from both parks and rides across the platform.
- **GET** `/api/v1/reviews/latest/`
- **Query Parameters**:
- `limit`: Number of reviews to return
- `park`: Filter by park slug
- `ride`: Filter by ride slug
**Endpoint:** `GET /reviews/latest/`
## Error Handling
**Parameters:**
- `limit` (optional): Number of reviews to return (default: 20, max: 100)
All API endpoints return standardized error responses:
**Response Format:**
```json
{
"count": 15,
"results": [
{
"id": 42,
"type": "ride",
"title": "Amazing coaster experience!",
"content_snippet": "This ride was absolutely incredible. The airtime was perfect and the inversions were smooth...",
"rating": 9,
"created_at": "2025-08-28T21:30:00Z",
"user": {
"username": "coaster_fan_2024",
"display_name": "Coaster Fan",
"avatar_url": "https://media.thrillwiki.com/avatars/user123.jpg"
},
"subject_name": "Steel Vengeance",
"subject_slug": "steel-vengeance",
"subject_url": "/parks/cedar-point/rides/steel-vengeance/",
"park_name": "Cedar Point",
"park_slug": "cedar-point",
"park_url": "/parks/cedar-point/"
},
{
"id": 38,
"type": "park",
"title": "Great family park",
"content_snippet": "Had a wonderful time with the family. The park was clean, staff was friendly, and there were rides for all ages...",
"rating": 8,
"created_at": "2025-08-28T20:15:00Z",
"user": {
"username": "family_fun",
"display_name": "Family Fun",
"avatar_url": "/static/images/default-avatar.png"
},
"subject_name": "Dollywood",
"subject_slug": "dollywood",
"subject_url": "/parks/dollywood/",
"park_name": null,
"park_slug": null,
"park_url": null
}
]
```typescript
interface ApiError {
status: "error";
error: {
code: string;
message: string;
details?: any;
request_user?: string;
};
data: null;
}
```
**Field Descriptions:**
- `id`: Unique review identifier
- `type`: Review type - "park" or "ride"
- `title`: Review title/headline
- `content_snippet`: Truncated review content (max 150 characters with smart word breaking)
- `rating`: User rating from 1-10
- `created_at`: ISO timestamp when review was created
- `user`: User information object
- `username`: User's unique username
- `display_name`: User's display name (falls back to username if not set)
- `avatar_url`: URL to user's avatar image (uses default if not set)
- `subject_name`: Name of the reviewed item (park or ride)
- `subject_slug`: URL slug of the reviewed item
- `subject_url`: Frontend URL to the reviewed item's detail page
- `park_name`: For ride reviews, the name of the parent park (null for park reviews)
- `park_slug`: For ride reviews, the slug of the parent park (null for park reviews)
- `park_url`: For ride reviews, the URL to the parent park (null for park reviews)
Common error codes:
- `NOT_AUTHENTICATED`: User not logged in
- `PERMISSION_DENIED`: Insufficient permissions
- `NOT_FOUND`: Resource not found
- `VALIDATION_ERROR`: Invalid request data
- `RATE_LIMITED`: Too many requests
**Authentication:** None required (public endpoint)
## Pagination
**Example Usage:**
```javascript
// Fetch latest 10 reviews
const response = await fetch('/api/v1/reviews/latest/?limit=10');
const data = await response.json();
List endpoints use cursor-based pagination:
// Display reviews
data.results.forEach(review => {
console.log(`${review.user.display_name} rated ${review.subject_name}: ${review.rating}/10`);
console.log(`"${review.title}" - ${review.content_snippet}`);
if (review.type === 'ride') {
console.log(`Ride at ${review.park_name}`);
}
});
```typescript
interface PaginatedResponse<T> {
status: "success";
data: {
results: T[];
count: number;
next: string | null;
previous: string | null;
};
error: null;
}
```
**Error Responses:**
- `400 Bad Request`: Invalid limit parameter
- `500 Internal Server Error`: Database or server error
## Rate Limiting
**Notes:**
- Reviews are filtered to only show published reviews (`is_published=True`)
- Results are sorted by creation date (newest first)
- Content snippets are intelligently truncated at word boundaries
- Avatar URLs fall back to default avatar if user hasn't uploaded one
- The endpoint combines reviews from both parks and rides into a single chronological feed
API endpoints are rate limited based on user role:
- Anonymous users: 100 requests/hour
- Authenticated users: 1000 requests/hour
- Moderators: 5000 requests/hour
- Admins: 10000 requests/hour
## WebSocket Connections
Real-time updates are available for:
- Moderation queue updates
- New reports and actions
- Bulk operation progress
- Live statistics updates
Connect to: `ws://localhost:8000/ws/moderation/` (requires authentication)

729
docs/lib-api.ts Normal file
View File

@@ -0,0 +1,729 @@
// ThrillWiki API Client for NextJS Frontend
// Last updated: 2025-08-29
// This file contains the complete API client implementation for ThrillWiki
import {
ApiResponse,
PaginatedResponse,
// Moderation types
ModerationReport,
CreateModerationReportData,
UpdateModerationReportData,
ModerationQueue,
CompleteQueueItemData,
ModerationAction,
CreateModerationActionData,
BulkOperation,
CreateBulkOperationData,
UserModerationProfile,
ModerationStatsData,
// Filter types
ModerationReportFilters,
ModerationQueueFilters,
ModerationActionFilters,
BulkOperationFilters,
ParkFilters,
RideFilters,
SearchFilters,
// Entity types
Park,
Ride,
Manufacturer,
RideModel,
ParkPhoto,
RidePhoto,
RideReview,
CreateRideReviewData,
// Auth types
LoginData,
SignupData,
AuthResponse,
UserProfile,
PasswordResetData,
PasswordChangeData,
// Stats types
GlobalStats,
TrendingContent,
// Utility types
ApiClientConfig,
RequestConfig,
} from '@/types/api';
// ============================================================================
// API Client Configuration
// ============================================================================
const DEFAULT_CONFIG: ApiClientConfig = {
baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000/api/v1',
timeout: 30000,
retries: 3,
retryDelay: 1000,
headers: {
'Content-Type': 'application/json',
},
};
// ============================================================================
// HTTP Client Class
// ============================================================================
class HttpClient {
private config: ApiClientConfig;
private authToken: string | null = null;
constructor(config: Partial<ApiClientConfig> = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
}
setAuthToken(token: string | null) {
this.authToken = token;
}
private getHeaders(customHeaders: Record<string, string> = {}): Record<string, string> {
const headers = { ...this.config.headers, ...customHeaders };
if (this.authToken) {
headers.Authorization = `Bearer ${this.authToken}`;
}
return headers;
}
private async makeRequest<T>(config: RequestConfig): Promise<ApiResponse<T>> {
const url = `${this.config.baseURL}${config.url}`;
const headers = this.getHeaders(config.headers);
const requestConfig: RequestInit = {
method: config.method,
headers,
signal: AbortSignal.timeout(config.timeout || this.config.timeout),
};
if (config.data && ['POST', 'PUT', 'PATCH'].includes(config.method)) {
requestConfig.body = JSON.stringify(config.data);
}
// Add query parameters for GET requests
const finalUrl = config.params && config.method === 'GET'
? `${url}?${new URLSearchParams(config.params).toString()}`
: url;
try {
const response = await fetch(finalUrl, requestConfig);
const data = await response.json();
if (!response.ok) {
return {
status: 'error',
data: null,
error: data.error || {
code: 'HTTP_ERROR',
message: `HTTP ${response.status}: ${response.statusText}`,
},
};
}
return data;
} catch (error) {
return {
status: 'error',
data: null,
error: {
code: 'NETWORK_ERROR',
message: error instanceof Error ? error.message : 'Network request failed',
},
};
}
}
async get<T>(url: string, params?: Record<string, any>, headers?: Record<string, string>): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: 'GET', url, params, headers });
}
async post<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: 'POST', url, data, headers });
}
async put<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: 'PUT', url, data, headers });
}
async patch<T>(url: string, data?: any, headers?: Record<string, string>): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: 'PATCH', url, data, headers });
}
async delete<T>(url: string, headers?: Record<string, string>): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: 'DELETE', url, headers });
}
}
// ============================================================================
// API Client Class
// ============================================================================
export class ThrillWikiApiClient {
private http: HttpClient;
constructor(config?: Partial<ApiClientConfig>) {
this.http = new HttpClient(config);
}
setAuthToken(token: string | null) {
this.http.setAuthToken(token);
}
// ============================================================================
// Authentication API
// ============================================================================
auth = {
login: async (data: LoginData): Promise<ApiResponse<AuthResponse>> => {
return this.http.post<AuthResponse>('/auth/login/', data);
},
signup: async (data: SignupData): Promise<ApiResponse<AuthResponse>> => {
return this.http.post<AuthResponse>('/auth/signup/', data);
},
logout: async (): Promise<ApiResponse<null>> => {
return this.http.post<null>('/auth/logout/');
},
getCurrentUser: async (): Promise<ApiResponse<UserProfile>> => {
return this.http.get<UserProfile>('/auth/user/');
},
resetPassword: async (data: PasswordResetData): Promise<ApiResponse<null>> => {
return this.http.post<null>('/auth/password/reset/', data);
},
changePassword: async (data: PasswordChangeData): Promise<ApiResponse<null>> => {
return this.http.post<null>('/auth/password/change/', data);
},
getAuthStatus: async (): Promise<ApiResponse<{ is_authenticated: boolean; user?: UserProfile }>> => {
return this.http.get('/auth/status/');
},
getSocialProviders: async (): Promise<ApiResponse<any>> => {
return this.http.get('/auth/providers/');
},
};
// ============================================================================
// Moderation API
// ============================================================================
moderation = {
// Reports
reports: {
list: async (filters?: ModerationReportFilters): Promise<ApiResponse<PaginatedResponse<ModerationReport>>> => {
return this.http.get<PaginatedResponse<ModerationReport>>('/moderation/reports/', filters);
},
create: async (data: CreateModerationReportData): Promise<ApiResponse<ModerationReport>> => {
return this.http.post<ModerationReport>('/moderation/reports/', data);
},
get: async (id: number): Promise<ApiResponse<ModerationReport>> => {
return this.http.get<ModerationReport>(`/moderation/reports/${id}/`);
},
update: async (id: number, data: Partial<UpdateModerationReportData>): Promise<ApiResponse<ModerationReport>> => {
return this.http.patch<ModerationReport>(`/moderation/reports/${id}/`, data);
},
delete: async (id: number): Promise<ApiResponse<null>> => {
return this.http.delete<null>(`/moderation/reports/${id}/`);
},
assign: async (id: number, moderatorId: number): Promise<ApiResponse<ModerationReport>> => {
return this.http.post<ModerationReport>(`/moderation/reports/${id}/assign/`, { moderator_id: moderatorId });
},
resolve: async (id: number, resolutionAction: string, resolutionNotes?: string): Promise<ApiResponse<ModerationReport>> => {
return this.http.post<ModerationReport>(`/moderation/reports/${id}/resolve/`, {
resolution_action: resolutionAction,
resolution_notes: resolutionNotes || '',
});
},
getStats: async (): Promise<ApiResponse<ModerationStatsData>> => {
return this.http.get<ModerationStatsData>('/moderation/reports/stats/');
},
},
// Queue
queue: {
list: async (filters?: ModerationQueueFilters): Promise<ApiResponse<PaginatedResponse<ModerationQueue>>> => {
return this.http.get<PaginatedResponse<ModerationQueue>>('/moderation/queue/', filters);
},
create: async (data: Partial<ModerationQueue>): Promise<ApiResponse<ModerationQueue>> => {
return this.http.post<ModerationQueue>('/moderation/queue/', data);
},
get: async (id: number): Promise<ApiResponse<ModerationQueue>> => {
return this.http.get<ModerationQueue>(`/moderation/queue/${id}/`);
},
update: async (id: number, data: Partial<ModerationQueue>): Promise<ApiResponse<ModerationQueue>> => {
return this.http.patch<ModerationQueue>(`/moderation/queue/${id}/`, data);
},
delete: async (id: number): Promise<ApiResponse<null>> => {
return this.http.delete<null>(`/moderation/queue/${id}/`);
},
assign: async (id: number, moderatorId: number): Promise<ApiResponse<ModerationQueue>> => {
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/assign/`, { moderator_id: moderatorId });
},
unassign: async (id: number): Promise<ApiResponse<ModerationQueue>> => {
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/unassign/`);
},
complete: async (id: number, data: CompleteQueueItemData): Promise<ApiResponse<ModerationQueue>> => {
return this.http.post<ModerationQueue>(`/moderation/queue/${id}/complete/`, data);
},
getMyQueue: async (): Promise<ApiResponse<PaginatedResponse<ModerationQueue>>> => {
return this.http.get<PaginatedResponse<ModerationQueue>>('/moderation/queue/my_queue/');
},
},
// Actions
actions: {
list: async (filters?: ModerationActionFilters): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/', filters);
},
create: async (data: CreateModerationActionData): Promise<ApiResponse<ModerationAction>> => {
return this.http.post<ModerationAction>('/moderation/actions/', data);
},
get: async (id: number): Promise<ApiResponse<ModerationAction>> => {
return this.http.get<ModerationAction>(`/moderation/actions/${id}/`);
},
update: async (id: number, data: Partial<ModerationAction>): Promise<ApiResponse<ModerationAction>> => {
return this.http.patch<ModerationAction>(`/moderation/actions/${id}/`, data);
},
delete: async (id: number): Promise<ApiResponse<null>> => {
return this.http.delete<null>(`/moderation/actions/${id}/`);
},
deactivate: async (id: number): Promise<ApiResponse<ModerationAction>> => {
return this.http.post<ModerationAction>(`/moderation/actions/${id}/deactivate/`);
},
getActive: async (): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/active/');
},
getExpired: async (): Promise<ApiResponse<PaginatedResponse<ModerationAction>>> => {
return this.http.get<PaginatedResponse<ModerationAction>>('/moderation/actions/expired/');
},
},
// Bulk Operations
bulkOperations: {
list: async (filters?: BulkOperationFilters): Promise<ApiResponse<PaginatedResponse<BulkOperation>>> => {
return this.http.get<PaginatedResponse<BulkOperation>>('/moderation/bulk-operations/', filters);
},
create: async (data: CreateBulkOperationData): Promise<ApiResponse<BulkOperation>> => {
return this.http.post<BulkOperation>('/moderation/bulk-operations/', data);
},
get: async (id: string): Promise<ApiResponse<BulkOperation>> => {
return this.http.get<BulkOperation>(`/moderation/bulk-operations/${id}/`);
},
update: async (id: string, data: Partial<BulkOperation>): Promise<ApiResponse<BulkOperation>> => {
return this.http.patch<BulkOperation>(`/moderation/bulk-operations/${id}/`, data);
},
delete: async (id: string): Promise<ApiResponse<null>> => {
return this.http.delete<null>(`/moderation/bulk-operations/${id}/`);
},
cancel: async (id: string): Promise<ApiResponse<BulkOperation>> => {
return this.http.post<BulkOperation>(`/moderation/bulk-operations/${id}/cancel/`);
},
retry: async (id: string): Promise<ApiResponse<BulkOperation>> => {
return this.http.post<BulkOperation>(`/moderation/bulk-operations/${id}/retry/`);
},
getLogs: async (id: string): Promise<ApiResponse<{ logs: any[]; count: number }>> => {
return this.http.get(`/moderation/bulk-operations/${id}/logs/`);
},
getRunning: async (): Promise<ApiResponse<PaginatedResponse<BulkOperation>>> => {
return this.http.get<PaginatedResponse<BulkOperation>>('/moderation/bulk-operations/running/');
},
},
// User Moderation
users: {
get: async (id: number): Promise<ApiResponse<UserModerationProfile>> => {
return this.http.get<UserModerationProfile>(`/moderation/users/${id}/`);
},
moderate: async (id: number, data: CreateModerationActionData): Promise<ApiResponse<ModerationAction>> => {
return this.http.post<ModerationAction>(`/moderation/users/${id}/moderate/`, data);
},
search: async (params: { query?: string; role?: string; has_restrictions?: boolean }): Promise<ApiResponse<PaginatedResponse<any>>> => {
return this.http.get<PaginatedResponse<any>>('/moderation/users/search/', params);
},
getStats: async (): Promise<ApiResponse<{ total_actions: number; active_actions: number; expired_actions: number }>> => {
return this.http.get('/moderation/users/stats/');
},
},
};
// ============================================================================
// Parks API
// ============================================================================
parks = {
list: async (filters?: ParkFilters): Promise<ApiResponse<PaginatedResponse<Park>>> => {
return this.http.get<PaginatedResponse<Park>>('/parks/', filters);
},
get: async (slug: string): Promise<ApiResponse<Park>> => {
return this.http.get<Park>(`/parks/${slug}/`);
},
getRides: async (parkSlug: string, filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
return this.http.get<PaginatedResponse<Ride>>(`/parks/${parkSlug}/rides/`, filters);
},
getPhotos: async (parkSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<ParkPhoto>>> => {
return this.http.get<PaginatedResponse<ParkPhoto>>(`/parks/${parkSlug}/photos/`, filters);
},
// Park operators and owners
operators: {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
return this.http.get<PaginatedResponse<any>>('/parks/operators/', filters);
},
get: async (slug: string): Promise<ApiResponse<any>> => {
return this.http.get(`/parks/operators/${slug}/`);
},
},
owners: {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
return this.http.get<PaginatedResponse<any>>('/parks/owners/', filters);
},
get: async (slug: string): Promise<ApiResponse<any>> => {
return this.http.get(`/parks/owners/${slug}/`);
},
},
};
// ============================================================================
// Rides API
// ============================================================================
rides = {
list: async (filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
return this.http.get<PaginatedResponse<Ride>>('/rides/', filters);
},
get: async (parkSlug: string, rideSlug: string): Promise<ApiResponse<Ride>> => {
return this.http.get<Ride>(`/rides/${parkSlug}/${rideSlug}/`);
},
getPhotos: async (parkSlug: string, rideSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RidePhoto>>> => {
return this.http.get<PaginatedResponse<RidePhoto>>(`/rides/${parkSlug}/${rideSlug}/photos/`, filters);
},
getReviews: async (parkSlug: string, rideSlug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideReview>>> => {
return this.http.get<PaginatedResponse<RideReview>>(`/rides/${parkSlug}/${rideSlug}/reviews/`, filters);
},
createReview: async (parkSlug: string, rideSlug: string, data: CreateRideReviewData): Promise<ApiResponse<RideReview>> => {
return this.http.post<RideReview>(`/rides/${parkSlug}/${rideSlug}/reviews/`, data);
},
// Manufacturers
manufacturers: {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<Manufacturer>>> => {
return this.http.get<PaginatedResponse<Manufacturer>>('/rides/manufacturers/', filters);
},
get: async (slug: string): Promise<ApiResponse<Manufacturer>> => {
return this.http.get<Manufacturer>(`/rides/manufacturers/${slug}/`);
},
getRides: async (slug: string, filters?: RideFilters): Promise<ApiResponse<PaginatedResponse<Ride>>> => {
return this.http.get<PaginatedResponse<Ride>>(`/rides/manufacturers/${slug}/rides/`, filters);
},
getModels: async (slug: string, filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideModel>>> => {
return this.http.get<PaginatedResponse<RideModel>>(`/rides/manufacturers/${slug}/models/`, filters);
},
},
// Designers
designers: {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
return this.http.get<PaginatedResponse<any>>('/rides/designers/', filters);
},
get: async (slug: string): Promise<ApiResponse<any>> => {
return this.http.get(`/rides/designers/${slug}/`);
},
},
// Ride Models
models: {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<RideModel>>> => {
return this.http.get<PaginatedResponse<RideModel>>('/rides/models/', filters);
},
get: async (manufacturerSlug: string, modelSlug: string): Promise<ApiResponse<RideModel>> => {
return this.http.get<RideModel>(`/rides/models/${manufacturerSlug}/${modelSlug}/`);
},
},
};
// ============================================================================
// Statistics API
// ============================================================================
stats = {
getGlobal: async (): Promise<ApiResponse<GlobalStats>> => {
return this.http.get<GlobalStats>('/stats/');
},
recalculate: async (): Promise<ApiResponse<null>> => {
return this.http.post<null>('/stats/recalculate/');
},
getTrending: async (params?: { content_type?: string; time_period?: string }): Promise<ApiResponse<TrendingContent>> => {
return this.http.get<TrendingContent>('/trending/', params);
},
getNewContent: async (): Promise<ApiResponse<any>> => {
return this.http.get('/new-content/');
},
triggerTrendingCalculation: async (): Promise<ApiResponse<null>> => {
return this.http.post<null>('/trending/calculate/');
},
getLatestReviews: async (params?: { limit?: number; park?: string; ride?: string }): Promise<ApiResponse<PaginatedResponse<RideReview>>> => {
return this.http.get<PaginatedResponse<RideReview>>('/reviews/latest/', params);
},
};
// ============================================================================
// Rankings API
// ============================================================================
rankings = {
list: async (filters?: SearchFilters): Promise<ApiResponse<PaginatedResponse<any>>> => {
return this.http.get<PaginatedResponse<any>>('/rankings/', filters);
},
calculate: async (): Promise<ApiResponse<null>> => {
return this.http.post<null>('/rankings/calculate/');
},
};
// ============================================================================
// Health Check API
// ============================================================================
health = {
check: async (): Promise<ApiResponse<any>> => {
return this.http.get('/health/');
},
simple: async (): Promise<ApiResponse<{ status: string }>> => {
return this.http.get('/health/simple/');
},
performance: async (): Promise<ApiResponse<any>> => {
return this.http.get('/health/performance/');
},
};
// ============================================================================
// Accounts API
// ============================================================================
accounts = {
getProfile: async (username: string): Promise<ApiResponse<UserProfile>> => {
return this.http.get<UserProfile>(`/accounts/users/${username}/`);
},
updateProfile: async (data: Partial<UserProfile>): Promise<ApiResponse<UserProfile>> => {
return this.http.patch<UserProfile>('/accounts/profile/', data);
},
getSettings: async (): Promise<ApiResponse<any>> => {
return this.http.get('/accounts/settings/');
},
updateSettings: async (data: any): Promise<ApiResponse<any>> => {
return this.http.patch('/accounts/settings/', data);
},
};
// ============================================================================
// Maps API
// ============================================================================
maps = {
getParkLocations: async (params?: { bounds?: string; zoom?: number }): Promise<ApiResponse<any>> => {
return this.http.get('/maps/park-locations/', params);
},
getRideLocations: async (parkSlug: string, params?: { bounds?: string; zoom?: number }): Promise<ApiResponse<any>> => {
return this.http.get(`/maps/parks/${parkSlug}/ride-locations/`, params);
},
getUnifiedMap: async (params?: { bounds?: string; zoom?: number; include_parks?: boolean; include_rides?: boolean }): Promise<ApiResponse<any>> => {
return this.http.get('/maps/unified/', params);
},
};
// ============================================================================
// Email API
// ============================================================================
email = {
sendTestEmail: async (data: { to: string; subject: string; message: string }): Promise<ApiResponse<null>> => {
return this.http.post<null>('/email/send-test/', data);
},
getTemplates: async (): Promise<ApiResponse<any[]>> => {
return this.http.get('/email/templates/');
},
};
}
// ============================================================================
// Default Export and Utilities
// ============================================================================
// Create default client instance
export const apiClient = new ThrillWikiApiClient();
// Utility functions for common operations
export const apiUtils = {
// Set authentication token for all requests
setAuthToken: (token: string | null) => {
apiClient.setAuthToken(token);
},
// Check if response is successful
isSuccess: <T>(response: ApiResponse<T>): response is ApiResponse<T> & { status: 'success'; data: T } => {
return response.status === 'success' && response.data !== null;
},
// Check if response is an error
isError: <T>(response: ApiResponse<T>): response is ApiResponse<T> & { status: 'error'; error: NonNullable<ApiResponse<T>['error']> } => {
return response.status === 'error' && response.error !== null;
},
// Extract data from successful response or throw error
unwrap: <T>(response: ApiResponse<T>): T => {
if (apiUtils.isSuccess(response)) {
return response.data;
}
throw new Error(response.error?.message || 'API request failed');
},
// Handle paginated responses
extractPaginatedData: <T>(response: ApiResponse<PaginatedResponse<T>>): T[] => {
if (apiUtils.isSuccess(response)) {
return response.data.results;
}
return [];
},
// Build query string from filters
buildQueryString: (filters: Record<string, any>): string => {
const params = new URLSearchParams();
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
if (Array.isArray(value)) {
value.forEach(v => params.append(key, String(v)));
} else {
params.append(key, String(value));
}
}
});
return params.toString();
},
// Format error message for display
formatError: (error: ApiResponse<any>['error']): string => {
if (!error) return 'Unknown error occurred';
if (error.details && typeof error.details === 'object') {
// Handle validation errors
const fieldErrors = Object.entries(error.details)
.map(([field, messages]) => `${field}: ${Array.isArray(messages) ? messages.join(', ') : messages}`)
.join('; ');
return fieldErrors || error.message;
}
return error.message;
},
// Check if user has required role
hasRole: (user: UserProfile | null, requiredRole: UserProfile['role']): boolean => {
if (!user) return false;
const roleHierarchy = ['USER', 'MODERATOR', 'ADMIN', 'SUPERUSER'];
const userRoleIndex = roleHierarchy.indexOf(user.role);
const requiredRoleIndex = roleHierarchy.indexOf(requiredRole);
return userRoleIndex >= requiredRoleIndex;
},
// Check if user can moderate
canModerate: (user: UserProfile | null): boolean => {
return apiUtils.hasRole(user, 'MODERATOR');
},
// Check if user is admin
isAdmin: (user: UserProfile | null): boolean => {
return apiUtils.hasRole(user, 'ADMIN');
},
};
// Export types for convenience
export type {
ApiResponse,
PaginatedResponse,
ModerationReport,
ModerationQueue,
ModerationAction,
BulkOperation,
UserModerationProfile,
Park,
Ride,
Manufacturer,
RideModel,
UserProfile,
GlobalStats,
TrendingContent,
} from './types-api';
export default apiClient;

612
docs/types-api.ts Normal file
View File

@@ -0,0 +1,612 @@
// ThrillWiki API Types for NextJS Frontend
// Last updated: 2025-08-29
// This file contains all TypeScript interfaces for the ThrillWiki API
// ============================================================================
// Base Types
// ============================================================================
export interface ApiResponse<T> {
status: "success" | "error";
data: T | null;
error: ApiError | null;
}
export interface ApiError {
code: string;
message: string;
details?: any;
request_user?: string;
}
export interface PaginatedResponse<T> {
results: T[];
count: number;
next: string | null;
previous: string | null;
}
export interface UserBasic {
id: number;
username: string;
display_name: string;
email: string;
role: "USER" | "MODERATOR" | "ADMIN" | "SUPERUSER";
}
export interface ContentType {
id: number;
app_label: string;
model: string;
}
// ============================================================================
// Moderation System Types
// ============================================================================
// Moderation Report Types
export interface ModerationReport {
id: number;
report_type: "SPAM" | "HARASSMENT" | "INAPPROPRIATE_CONTENT" | "MISINFORMATION" | "COPYRIGHT" | "PRIVACY" | "HATE_SPEECH" | "VIOLENCE" | "OTHER";
report_type_display: string;
status: "PENDING" | "UNDER_REVIEW" | "RESOLVED" | "DISMISSED";
status_display: string;
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
priority_display: string;
reported_entity_type: string;
reported_entity_id: number;
reason: string;
description: string;
evidence_urls: string[];
resolved_at: string | null;
resolution_notes: string;
resolution_action: string;
created_at: string;
updated_at: string;
reported_by: UserBasic;
assigned_moderator: UserBasic | null;
content_type: ContentType | null;
is_overdue: boolean;
time_since_created: string;
}
export interface CreateModerationReportData {
report_type: ModerationReport["report_type"];
reported_entity_type: string;
reported_entity_id: number;
reason: string;
description: string;
evidence_urls?: string[];
}
export interface UpdateModerationReportData {
status?: ModerationReport["status"];
priority?: ModerationReport["priority"];
assigned_moderator?: number;
resolution_notes?: string;
resolution_action?: string;
}
// Moderation Queue Types
export interface ModerationQueue {
id: number;
item_type: "CONTENT_REVIEW" | "USER_REVIEW" | "BULK_ACTION" | "POLICY_VIOLATION" | "APPEAL" | "OTHER";
status: "PENDING" | "IN_PROGRESS" | "COMPLETED" | "CANCELLED";
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
title: string;
description: string;
entity_type: string;
entity_id: number | null;
entity_preview: Record<string, any>;
flagged_by: UserBasic | null;
assigned_at: string | null;
estimated_review_time: number;
created_at: string;
updated_at: string;
tags: string[];
assigned_to: UserBasic | null;
related_report: ModerationReport | null;
content_type: ContentType | null;
is_overdue: boolean;
time_in_queue: number;
estimated_completion: string;
}
export interface CompleteQueueItemData {
action: "NO_ACTION" | "CONTENT_REMOVED" | "CONTENT_EDITED" | "USER_WARNING" | "USER_SUSPENDED" | "USER_BANNED";
notes?: string;
}
// Moderation Action Types
export interface ModerationAction {
id: number;
action_type: "WARNING" | "USER_SUSPENSION" | "USER_BAN" | "CONTENT_REMOVAL" | "CONTENT_EDIT" | "CONTENT_RESTRICTION" | "ACCOUNT_RESTRICTION" | "OTHER";
action_type_display: string;
reason: string;
details: string;
duration_hours: number | null;
created_at: string;
expires_at: string | null;
is_active: boolean;
moderator: UserBasic;
target_user: UserBasic;
related_report: ModerationReport | null;
updated_at: string;
is_expired: boolean;
time_remaining: string | null;
}
export interface CreateModerationActionData {
action_type: ModerationAction["action_type"];
reason: string;
details: string;
duration_hours?: number;
target_user_id: number;
related_report_id?: number;
}
// Bulk Operation Types
export interface BulkOperation {
id: string;
operation_type: "UPDATE_PARKS" | "UPDATE_RIDES" | "IMPORT_DATA" | "EXPORT_DATA" | "MODERATE_CONTENT" | "USER_ACTIONS" | "CLEANUP" | "OTHER";
operation_type_display: string;
status: "PENDING" | "RUNNING" | "COMPLETED" | "FAILED" | "CANCELLED";
status_display: string;
priority: "LOW" | "MEDIUM" | "HIGH" | "URGENT";
parameters: Record<string, any>;
results: Record<string, any>;
total_items: number;
processed_items: number;
failed_items: number;
created_at: string;
started_at: string | null;
completed_at: string | null;
estimated_duration_minutes: number | null;
can_cancel: boolean;
description: string;
schedule_for: string | null;
created_by: UserBasic;
updated_at: string;
progress_percentage: number;
estimated_completion: string | null;
}
export interface CreateBulkOperationData {
operation_type: BulkOperation["operation_type"];
priority?: BulkOperation["priority"];
parameters: Record<string, any>;
description: string;
schedule_for?: string;
estimated_duration_minutes?: number;
}
// User Moderation Profile Types
export interface UserModerationProfile {
user: UserBasic;
reports_made: number;
reports_against: number;
warnings_received: number;
suspensions_received: number;
active_restrictions: number;
risk_level: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
risk_factors: string[];
recent_reports: ModerationReport[];
recent_actions: ModerationAction[];
account_status: string;
last_violation_date: string | null;
next_review_date: string | null;
}
// Moderation Statistics Types
export interface ModerationStatsData {
total_reports: number;
pending_reports: number;
resolved_reports: number;
overdue_reports: number;
queue_size: number;
assigned_items: number;
unassigned_items: number;
total_actions: number;
active_actions: number;
expired_actions: number;
running_operations: number;
completed_operations: number;
failed_operations: number;
average_resolution_time_hours: number;
reports_by_priority: Record<string, number>;
reports_by_type: Record<string, number>;
}
// ============================================================================
// Parks API Types
// ============================================================================
export interface Park {
id: number;
name: string;
slug: string;
description: string;
country: string;
state: string;
city: string;
address: string;
postal_code: string;
phone: string;
email: string;
website: string;
opened_date: string | null;
closed_date: string | null;
status: "OPERATING" | "CLOSED_TEMP" | "CLOSED_PERM" | "UNDER_CONSTRUCTION";
park_type: "THEME_PARK" | "AMUSEMENT_PARK" | "WATER_PARK" | "FAMILY_ENTERTAINMENT_CENTER" | "OTHER";
timezone: string;
latitude: number | null;
longitude: number | null;
ride_count: number;
operating_ride_count: number;
photo_count: number;
banner_image: string | null;
card_image: string | null;
created_at: string;
updated_at: string;
}
export interface ParkPhoto {
id: number;
image: string;
caption: string;
photo_type: "banner" | "card" | "gallery";
uploaded_by: UserBasic;
upload_date: string;
is_approved: boolean;
likes_count: number;
views_count: number;
}
// ============================================================================
// Rides API Types
// ============================================================================
export interface Ride {
id: number;
name: string;
slug: string;
park: Park;
description: string;
ride_type: string;
manufacturer: Manufacturer | null;
model: RideModel | null;
opened_date: string | null;
closed_date: string | null;
status: "OPERATING" | "CLOSED_TEMP" | "SBNO" | "UNDER_CONSTRUCTION" | "REMOVED";
height_requirement: number | null;
max_height: number | null;
duration_seconds: number | null;
max_speed_mph: number | null;
max_height_ft: number | null;
length_ft: number | null;
inversions: number | null;
capacity_per_hour: number | null;
photo_count: number;
review_count: number;
average_rating: number | null;
banner_image: string | null;
card_image: string | null;
created_at: string;
updated_at: string;
}
export interface Manufacturer {
id: number;
name: string;
slug: string;
description: string;
country: string;
founded_year: number | null;
website: string;
ride_count: number;
created_at: string;
updated_at: string;
}
export interface RideModel {
id: number;
name: string;
slug: string;
manufacturer: Manufacturer;
description: string;
ride_type: string;
first_built_year: number | null;
ride_count: number;
created_at: string;
updated_at: string;
}
export interface RidePhoto {
id: number;
image: string;
caption: string;
photo_type: "banner" | "card" | "gallery";
uploaded_by: UserBasic;
upload_date: string;
is_approved: boolean;
likes_count: number;
views_count: number;
}
export interface RideReview {
id: number;
ride: Ride;
user: UserBasic;
rating: number;
title: string;
content: string;
visit_date: string | null;
created_at: string;
updated_at: string;
likes_count: number;
is_liked: boolean;
}
export interface CreateRideReviewData {
rating: number;
title: string;
content: string;
visit_date?: string;
}
// ============================================================================
// Authentication Types
// ============================================================================
export interface LoginData {
username: string;
password: string;
}
export interface SignupData {
username: string;
email: string;
password: string;
first_name?: string;
last_name?: string;
}
export interface AuthResponse {
access: string;
refresh: string;
user: UserProfile;
}
export interface UserProfile {
id: number;
username: string;
email: string;
first_name: string;
last_name: string;
display_name: string;
role: "USER" | "MODERATOR" | "ADMIN" | "SUPERUSER";
date_joined: string;
last_login: string | null;
is_active: boolean;
profile_image: string | null;
bio: string;
location: string;
website: string;
birth_date: string | null;
privacy_settings: Record<string, any>;
}
export interface PasswordResetData {
email: string;
}
export interface PasswordChangeData {
old_password: string;
new_password: string;
}
// ============================================================================
// Statistics Types
// ============================================================================
export interface GlobalStats {
total_parks: number;
total_rides: number;
total_reviews: number;
total_photos: number;
total_users: number;
active_users_30d: number;
new_content_7d: number;
top_countries: Array<{
country: string;
park_count: number;
ride_count: number;
}>;
recent_activity: Array<{
type: string;
description: string;
timestamp: string;
}>;
}
export interface TrendingContent {
parks: Park[];
rides: Ride[];
reviews: RideReview[];
time_period: "24h" | "7d" | "30d";
generated_at: string;
}
// ============================================================================
// Search and Filter Types
// ============================================================================
export interface SearchFilters {
search?: string;
ordering?: string;
page?: number;
page_size?: number;
}
export interface ParkFilters extends SearchFilters {
country?: string;
state?: string;
city?: string;
status?: Park["status"];
park_type?: Park["park_type"];
has_rides?: boolean;
}
export interface RideFilters extends SearchFilters {
park?: string;
manufacturer?: string;
ride_type?: string;
status?: Ride["status"];
opened_after?: string;
opened_before?: string;
height_min?: number;
height_max?: number;
has_photos?: boolean;
}
export interface ModerationReportFilters extends SearchFilters {
status?: ModerationReport["status"];
priority?: ModerationReport["priority"];
report_type?: ModerationReport["report_type"];
reported_by?: number;
assigned_moderator?: number;
created_after?: string;
created_before?: string;
unassigned?: boolean;
overdue?: boolean;
has_resolution?: boolean;
}
export interface ModerationQueueFilters extends SearchFilters {
status?: ModerationQueue["status"];
priority?: ModerationQueue["priority"];
item_type?: ModerationQueue["item_type"];
assigned_to?: number;
unassigned?: boolean;
has_related_report?: boolean;
}
export interface ModerationActionFilters extends SearchFilters {
action_type?: ModerationAction["action_type"];
moderator?: number;
target_user?: number;
is_active?: boolean;
expired?: boolean;
expiring_soon?: boolean;
has_related_report?: boolean;
}
export interface BulkOperationFilters extends SearchFilters {
status?: BulkOperation["status"];
operation_type?: BulkOperation["operation_type"];
priority?: BulkOperation["priority"];
created_by?: number;
can_cancel?: boolean;
has_failures?: boolean;
in_progress?: boolean;
}
// ============================================================================
// WebSocket Types
// ============================================================================
export interface WebSocketMessage {
type: string;
data: any;
timestamp: string;
}
export interface ModerationUpdate extends WebSocketMessage {
type: "moderation_update";
data: {
event_type: "report_created" | "report_assigned" | "report_resolved" | "queue_updated" | "action_taken";
object_type: "report" | "queue_item" | "action";
object_id: number;
object_data: ModerationReport | ModerationQueue | ModerationAction;
user: UserBasic;
};
}
export interface BulkOperationUpdate extends WebSocketMessage {
type: "bulk_operation_update";
data: {
operation_id: string;
status: BulkOperation["status"];
progress_percentage: number;
processed_items: number;
failed_items: number;
estimated_completion: string | null;
};
}
// ============================================================================
// Form Validation Types
// ============================================================================
export interface ValidationError {
field: string;
message: string;
code: string;
}
export interface FormErrors {
[key: string]: string[] | string;
}
// ============================================================================
// Utility Types
// ============================================================================
export type SortOrder = "asc" | "desc";
export interface SortOption {
field: string;
label: string;
order: SortOrder;
}
export interface FilterOption {
value: string;
label: string;
count?: number;
}
export interface TabOption {
key: string;
label: string;
count?: number;
badge?: string;
}
// ============================================================================
// API Client Configuration Types
// ============================================================================
export interface ApiClientConfig {
baseURL: string;
timeout: number;
retries: number;
retryDelay: number;
headers: Record<string, string>;
}
export interface RequestConfig {
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
url: string;
data?: any;
params?: Record<string, any>;
headers?: Record<string, string>;
timeout?: number;
}
export interface CacheConfig {
enabled: boolean;
ttl: number;
maxSize: number;
keyPrefix: string;
}