""" 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 rest_framework.views import APIView from rest_framework.request import Request from rest_framework.response import Response from rest_framework.permissions import AllowAny, IsAdminUser from rest_framework import status from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter 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=[ OpenApiParameter( name="limit", location=OpenApiParameter.QUERY, description="Number of trending items to return (default: 20, max: 100)", required=False, type=OpenApiTypes.INT, default=20, ), OpenApiParameter( name="timeframe", location=OpenApiParameter.QUERY, description="Timeframe for trending calculation (day, week, month) - default: week", required=False, type=OpenApiTypes.STR, 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.""" from apps.core.services.trending_service import trending_service # Parse parameters limit = min(int(request.query_params.get("limit", 20)), 100) # Get trending content using direct calculation service 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 [] # Latest reviews will be empty until review system is implemented latest_reviews = [] # Return in expected frontend format response_data = { "trending_rides": trending_rides, "trending_parks": trending_parks, "latest_reviews": latest_reviews, } return Response(response_data) @extend_schema_view( post=extend_schema( summary="Trigger trending content calculation", description="Manually trigger the calculation of trending content using Django management commands. Admin access required.", responses={ 202: { "type": "object", "properties": { "message": {"type": "string"}, "trending_completed": {"type": "boolean"}, "new_content_completed": {"type": "boolean"}, "completion_time": {"type": "string"}, }, }, 403: {"description": "Admin access required"}, }, tags=["Trending"], ), ) class TriggerTrendingCalculationAPIView(APIView): """API endpoint to manually trigger trending content calculation.""" permission_classes = [IsAdminUser] def post(self, request: Request) -> Response: """Trigger trending content calculation using management commands.""" try: from django.core.management import call_command import io from contextlib import redirect_stdout, redirect_stderr # Capture command output trending_output = io.StringIO() new_content_output = io.StringIO() trending_completed = False new_content_completed = False try: # Run trending calculation command with redirect_stdout(trending_output), redirect_stderr(trending_output): call_command( "calculate_trending", "--content-type=all", "--limit=50" ) trending_completed = True except Exception as e: trending_output.write(f"Error: {str(e)}") try: # Run new content calculation command with redirect_stdout(new_content_output), redirect_stderr( new_content_output ): call_command( "calculate_new_content", "--content-type=all", "--days-back=30", "--limit=50", ) new_content_completed = True except Exception as e: new_content_output.write(f"Error: {str(e)}") completion_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return Response( { "message": "Trending content calculation completed", "trending_completed": trending_completed, "new_content_completed": new_content_completed, "completion_time": completion_time, "trending_output": trending_output.getvalue(), "new_content_output": new_content_output.getvalue(), }, status=status.HTTP_202_ACCEPTED, ) except Exception as e: return Response( { "error": "Failed to trigger trending content calculation", "details": str(e), }, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) @extend_schema_view( get=extend_schema( summary="Get new content", description="Retrieve recently added parks and rides.", parameters=[ OpenApiParameter( name="limit", location=OpenApiParameter.QUERY, description="Number of new items to return (default: 20, max: 100)", required=False, type=OpenApiTypes.INT, default=20, ), OpenApiParameter( name="days", location=OpenApiParameter.QUERY, description="Number of days to look back for new content (default: 30, max: 365)", required=False, type=OpenApiTypes.INT, default=30, ), ], 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.""" from apps.core.services.trending_service import trending_service # Parse parameters limit = min(int(request.query_params.get("limit", 20)), 100) days_back = min(int(request.query_params.get("days", 30)), 365) # Get new content using direct calculation service all_new_content = trending_service.get_new_content( limit=limit * 2, days_back=days_back ) 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) # Upcoming items will be empty until future content system is implemented upcoming = [] # Limit each category recently_added = recently_added[: limit // 3] if recently_added else [] newly_opened = newly_opened[: limit // 3] if newly_opened else [] # Return in expected frontend format response_data = { "recently_added": recently_added, "newly_opened": newly_opened, "upcoming": upcoming, } return Response(response_data)