""" Trending content API views for ThrillWiki API v1. This module contains endpoints for trending and new content discovery including trending parks, rides, and recently added content. """ from datetime import datetime, date from django.utils import timezone from rest_framework import status from rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response from rest_framework.permissions import AllowAny from drf_spectacular.utils import extend_schema, extend_schema_view from drf_spectacular.types import OpenApiTypes @extend_schema_view( get=extend_schema( summary="Get trending content", description="Retrieve trending parks and rides based on view counts, ratings, and recency.", parameters=[ { "name": "limit", "in": "query", "description": "Number of trending items to return (default: 20, max: 100)", "required": False, "schema": {"type": "integer", "default": 20, "maximum": 100}, }, { "name": "timeframe", "in": "query", "description": "Timeframe for trending calculation (day, week, month) - default: week", "required": False, "schema": { "type": "string", "enum": ["day", "week", "month"], "default": "week", }, }, ], responses={200: OpenApiTypes.OBJECT}, tags=["Trending"], ), ) class TrendingAPIView(APIView): """API endpoint for trending content.""" permission_classes = [AllowAny] def get(self, request: Request) -> Response: """Get trending parks and rides.""" try: from apps.core.services.trending_service import TrendingService except ImportError: # Fallback if trending service is not available return self._get_fallback_trending_content(request) # Parse parameters limit = min(int(request.query_params.get("limit", 20)), 100) # Get trending content trending_service = TrendingService() all_trending = trending_service.get_trending_content(limit=limit * 2) # Separate by content type trending_rides = [] trending_parks = [] for item in all_trending: if item.get("category") == "ride": trending_rides.append(item) elif item.get("category") == "park": trending_parks.append(item) # Limit each category trending_rides = trending_rides[: limit // 3] if trending_rides else [] trending_parks = trending_parks[: limit // 3] if trending_parks else [] # Create mock latest reviews (since not implemented yet) latest_reviews = [ { "id": 1, "name": "Steel Vengeance Review", "location": "Cedar Point", "category": "Roller Coaster", "rating": 5.0, "rank": 1, "views": 1234, "views_change": "+45%", "slug": "steel-vengeance-review", } ][: limit // 3] # Return in expected frontend format response_data = { "trending_rides": trending_rides, "trending_parks": trending_parks, "latest_reviews": latest_reviews, } return Response(response_data) def _get_fallback_trending_content(self, request: Request) -> Response: """Fallback method when trending service is not available.""" limit = min(int(request.query_params.get("limit", 20)), 100) # Mock trending data trending_rides = [ { "id": 1, "name": "Steel Vengeance", "location": "Cedar Point", "category": "Roller Coaster", "rating": 4.8, "rank": 1, "views": 15234, "views_change": "+25%", "slug": "steel-vengeance", }, { "id": 2, "name": "Lightning Rod", "location": "Dollywood", "category": "Roller Coaster", "rating": 4.7, "rank": 2, "views": 12456, "views_change": "+18%", "slug": "lightning-rod", }, ][: limit // 3] trending_parks = [ { "id": 1, "name": "Cedar Point", "location": "Sandusky, OH", "category": "Theme Park", "rating": 4.6, "rank": 1, "views": 45678, "views_change": "+12%", "slug": "cedar-point", }, { "id": 2, "name": "Magic Kingdom", "location": "Orlando, FL", "category": "Theme Park", "rating": 4.5, "rank": 2, "views": 67890, "views_change": "+8%", "slug": "magic-kingdom", }, ][: limit // 3] latest_reviews = [ { "id": 1, "name": "Steel Vengeance Review", "location": "Cedar Point", "category": "Roller Coaster", "rating": 5.0, "rank": 1, "views": 1234, "views_change": "+45%", "slug": "steel-vengeance-review", } ][: limit // 3] response_data = { "trending_rides": trending_rides, "trending_parks": trending_parks, "latest_reviews": latest_reviews, } return Response(response_data) @extend_schema_view( get=extend_schema( summary="Get new content", description="Retrieve recently added parks and rides.", parameters=[ { "name": "limit", "in": "query", "description": "Number of new items to return (default: 20, max: 100)", "required": False, "schema": {"type": "integer", "default": 20, "maximum": 100}, }, { "name": "days", "in": "query", "description": "Number of days to look back for new content (default: 30, max: 365)", "required": False, "schema": {"type": "integer", "default": 30, "maximum": 365}, }, ], responses={200: OpenApiTypes.OBJECT}, tags=["Trending"], ), ) class NewContentAPIView(APIView): """API endpoint for new content.""" permission_classes = [AllowAny] def get(self, request: Request) -> Response: """Get new parks and rides.""" try: from apps.core.services.trending_service import TrendingService except ImportError: # Fallback if trending service is not available return self._get_fallback_new_content(request) # Parse parameters limit = min(int(request.query_params.get("limit", 20)), 100) # Get new content with longer timeframe to get more data trending_service = TrendingService() all_new_content = trending_service.get_new_content( limit=limit * 2, days_back=60 ) recently_added = [] newly_opened = [] upcoming = [] # Categorize items based on date today = date.today() for item in all_new_content: date_added = item.get("date_added", "") if date_added: try: # Parse the date string if isinstance(date_added, str): item_date = datetime.fromisoformat(date_added).date() else: item_date = date_added # Calculate days difference days_diff = (today - item_date).days if days_diff <= 30: # Recently added (last 30 days) recently_added.append(item) elif days_diff <= 365: # Newly opened (last year) newly_opened.append(item) else: # Older items newly_opened.append(item) except (ValueError, TypeError): # If date parsing fails, add to recently added recently_added.append(item) else: recently_added.append(item) # Create mock upcoming items upcoming = [ { "id": 1, "name": "Epic Universe", "location": "Universal Orlando", "category": "Theme Park", "date_added": "Opening 2025", "slug": "epic-universe", }, { "id": 2, "name": "New Fantasyland Expansion", "location": "Magic Kingdom", "category": "Land Expansion", "date_added": "Opening 2026", "slug": "fantasyland-expansion", }, ] # Limit each category recently_added = recently_added[: limit // 3] if recently_added else [] newly_opened = newly_opened[: limit // 3] if newly_opened else [] upcoming = upcoming[: limit // 3] if upcoming else [] # Return in expected frontend format response_data = { "recently_added": recently_added, "newly_opened": newly_opened, "upcoming": upcoming, } return Response(response_data) def _get_fallback_new_content(self, request: Request) -> Response: """Fallback method when trending service is not available.""" limit = min(int(request.query_params.get("limit", 20)), 100) # Mock new content data recently_added = [ { "id": 1, "name": "Iron Gwazi", "location": "Busch Gardens Tampa", "category": "Roller Coaster", "date_added": "2024-12-01", "slug": "iron-gwazi", }, { "id": 2, "name": "VelociCoaster", "location": "Universal's Islands of Adventure", "category": "Roller Coaster", "date_added": "2024-11-15", "slug": "velocicoaster", }, ][: limit // 3] newly_opened = [ { "id": 3, "name": "Guardians of the Galaxy", "location": "EPCOT", "category": "Roller Coaster", "date_added": "2024-10-01", "slug": "guardians-galaxy", }, { "id": 4, "name": "TRON Lightcycle Run", "location": "Magic Kingdom", "category": "Roller Coaster", "date_added": "2024-09-15", "slug": "tron-lightcycle", }, ][: limit // 3] upcoming = [ { "id": 5, "name": "Epic Universe", "location": "Universal Orlando", "category": "Theme Park", "date_added": "Opening 2025", "slug": "epic-universe", }, { "id": 6, "name": "New Fantasyland Expansion", "location": "Magic Kingdom", "category": "Land Expansion", "date_added": "Opening 2026", "slug": "fantasyland-expansion", }, ][: limit // 3] response_data = { "recently_added": recently_added, "newly_opened": newly_opened, "upcoming": upcoming, } return Response(response_data)