mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 08:51:09 -05:00
feat: major API restructure and Vue.js frontend integration
- Centralize API endpoints in dedicated api app with v1 versioning - Remove individual API modules from parks and rides apps - Add event tracking system with analytics functionality - Integrate Vue.js frontend with Tailwind CSS v4 and TypeScript - Add comprehensive database migrations for event tracking - Implement user authentication and social provider setup - Add API schema documentation and serializers - Configure development environment with shared scripts - Update project structure for monorepo with frontend/backend separation
This commit is contained in:
334
backend/apps/api/v1/schema.py
Normal file
334
backend/apps/api/v1/schema.py
Normal file
@@ -0,0 +1,334 @@
|
||||
"""
|
||||
Schema extensions and customizations for drf-spectacular.
|
||||
|
||||
This module provides custom extensions to improve OpenAPI schema generation
|
||||
for the ThrillWiki API, including better documentation and examples.
|
||||
"""
|
||||
|
||||
from drf_spectacular.openapi import AutoSchema
|
||||
from drf_spectacular.utils import OpenApiExample
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
|
||||
# Custom examples for common serializers
|
||||
|
||||
PARK_EXAMPLE = {
|
||||
"id": 1,
|
||||
"name": "Cedar Point",
|
||||
"slug": "cedar-point",
|
||||
"description": "The Roller Coaster Capital of the World",
|
||||
"status": "OPERATING",
|
||||
"opening_date": "1870-07-04",
|
||||
"closing_date": None,
|
||||
"location": {
|
||||
"latitude": 41.4793,
|
||||
"longitude": -82.6833,
|
||||
"city": "Sandusky",
|
||||
"state": "Ohio",
|
||||
"country": "United States",
|
||||
"formatted_address": "Sandusky, OH, United States",
|
||||
},
|
||||
"operator": {
|
||||
"id": 1,
|
||||
"name": "Cedar Fair",
|
||||
"slug": "cedar-fair",
|
||||
"roles": ["OPERATOR", "PROPERTY_OWNER"],
|
||||
},
|
||||
"property_owner": {
|
||||
"id": 1,
|
||||
"name": "Cedar Fair",
|
||||
"slug": "cedar-fair",
|
||||
"roles": ["OPERATOR", "PROPERTY_OWNER"],
|
||||
},
|
||||
"area_count": 15,
|
||||
"ride_count": 70,
|
||||
"operating_rides_count": 68,
|
||||
"roller_coaster_count": 17,
|
||||
}
|
||||
|
||||
RIDE_EXAMPLE = {
|
||||
"id": 1,
|
||||
"name": "Steel Vengeance",
|
||||
"slug": "steel-vengeance",
|
||||
"description": "A hybrid wooden/steel roller coaster",
|
||||
"category": "ROLLER_COASTER",
|
||||
"status": "OPERATING",
|
||||
"opening_date": "2018-05-05",
|
||||
"closing_date": None,
|
||||
"park": {"id": 1, "name": "Cedar Point", "slug": "cedar-point"},
|
||||
"manufacturer": {
|
||||
"id": 1,
|
||||
"name": "Rocky Mountain Construction",
|
||||
"slug": "rmc",
|
||||
"roles": ["MANUFACTURER"],
|
||||
},
|
||||
"designer": {
|
||||
"id": 1,
|
||||
"name": "Rocky Mountain Construction",
|
||||
"slug": "rmc",
|
||||
"roles": ["DESIGNER"],
|
||||
},
|
||||
"height_feet": 205,
|
||||
"length_feet": 5740,
|
||||
"speed_mph": 74,
|
||||
"inversions": 4,
|
||||
"duration_seconds": 150,
|
||||
"capacity_per_hour": 1200,
|
||||
"minimum_height_inches": 48,
|
||||
"maximum_height_inches": None,
|
||||
}
|
||||
|
||||
COMPANY_EXAMPLE = {
|
||||
"id": 1,
|
||||
"name": "Cedar Fair",
|
||||
"slug": "cedar-fair",
|
||||
"roles": ["OPERATOR", "PROPERTY_OWNER"],
|
||||
}
|
||||
|
||||
LOCATION_EXAMPLE = {
|
||||
"latitude": 41.4793,
|
||||
"longitude": -82.6833,
|
||||
"city": "Sandusky",
|
||||
"state": "Ohio",
|
||||
"country": "United States",
|
||||
"formatted_address": "Sandusky, OH, United States",
|
||||
}
|
||||
|
||||
HISTORY_EVENT_EXAMPLE = {
|
||||
"id": "12345678-1234-5678-9012-123456789012",
|
||||
"pgh_created_at": "2024-01-15T14:30:00Z",
|
||||
"pgh_label": "updated",
|
||||
"pgh_model": "parks.park",
|
||||
"pgh_obj_id": 1,
|
||||
"pgh_context": {
|
||||
"user_id": 42,
|
||||
"request_id": "req_abc123",
|
||||
"ip_address": "192.168.1.100",
|
||||
},
|
||||
"changed_fields": ["name", "description"],
|
||||
"field_changes": {
|
||||
"name": {"old_value": "Cedar Point Amusement Park", "new_value": "Cedar Point"},
|
||||
"description": {
|
||||
"old_value": "America's Roller Coast",
|
||||
"new_value": "The Roller Coaster Capital of the World",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
PARK_HISTORY_EXAMPLE = {
|
||||
"park": PARK_EXAMPLE,
|
||||
"current_state": PARK_EXAMPLE,
|
||||
"summary": {
|
||||
"total_events": 25,
|
||||
"first_recorded": "2023-01-01T00:00:00Z",
|
||||
"last_modified": "2024-01-15T14:30:00Z",
|
||||
"significant_changes": [
|
||||
{
|
||||
"date": "2024-01-15T14:30:00Z",
|
||||
"event_type": "updated",
|
||||
"description": "Name and description updated",
|
||||
},
|
||||
{
|
||||
"date": "2023-06-01T10:00:00Z",
|
||||
"event_type": "updated",
|
||||
"description": "Operating status changed",
|
||||
},
|
||||
],
|
||||
},
|
||||
"events": [HISTORY_EVENT_EXAMPLE],
|
||||
}
|
||||
|
||||
UNIFIED_HISTORY_TIMELINE_EXAMPLE = {
|
||||
"summary": {
|
||||
"total_events": 1250,
|
||||
"events_returned": 100,
|
||||
"event_type_breakdown": {"created": 45, "updated": 180, "deleted": 5},
|
||||
"model_type_breakdown": {
|
||||
"parks.park": 75,
|
||||
"rides.ride": 120,
|
||||
"companies.operator": 15,
|
||||
"companies.manufacturer": 25,
|
||||
"accounts.user": 30,
|
||||
},
|
||||
"time_range": {
|
||||
"earliest": "2023-01-01T00:00:00Z",
|
||||
"latest": "2024-01-15T14:30:00Z",
|
||||
},
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"id": "event_001",
|
||||
"pgh_created_at": "2024-01-15T14:30:00Z",
|
||||
"pgh_label": "updated",
|
||||
"pgh_model": "parks.park",
|
||||
"pgh_obj_id": 1,
|
||||
"entity_name": "Cedar Point",
|
||||
"entity_slug": "cedar-point",
|
||||
"change_significance": "minor",
|
||||
"change_summary": "Park description updated",
|
||||
},
|
||||
{
|
||||
"id": "event_002",
|
||||
"pgh_created_at": "2024-01-15T12:00:00Z",
|
||||
"pgh_label": "created",
|
||||
"pgh_model": "rides.ride",
|
||||
"pgh_obj_id": 100,
|
||||
"entity_name": "New Roller Coaster",
|
||||
"entity_slug": "new-roller-coaster",
|
||||
"change_significance": "major",
|
||||
"change_summary": "New ride added to park",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# OpenAPI schema customizations
|
||||
|
||||
|
||||
def custom_preprocessing_hook(endpoints):
|
||||
"""
|
||||
Custom preprocessing hook to modify endpoints before schema generation.
|
||||
|
||||
This can be used to filter out certain endpoints, modify their metadata,
|
||||
or add custom documentation.
|
||||
"""
|
||||
# Filter out any endpoints we don't want in the public API
|
||||
filtered = []
|
||||
for path, path_regex, method, callback in endpoints:
|
||||
# Skip internal or debug endpoints
|
||||
if "/debug/" not in path and "/internal/" not in path:
|
||||
filtered.append((path, path_regex, method, callback))
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
def custom_postprocessing_hook(result, generator, request, public):
|
||||
"""
|
||||
Custom postprocessing hook to modify the generated schema.
|
||||
|
||||
This can be used to add custom metadata, modify response schemas,
|
||||
or enhance the overall API documentation.
|
||||
"""
|
||||
# Add custom info to the schema
|
||||
if "info" in result:
|
||||
result["info"]["contact"] = {
|
||||
"name": "ThrillWiki API Support",
|
||||
"email": "api@thrillwiki.com",
|
||||
"url": "https://thrillwiki.com/support",
|
||||
}
|
||||
|
||||
result["info"]["license"] = {
|
||||
"name": "MIT",
|
||||
"url": "https://opensource.org/licenses/MIT",
|
||||
}
|
||||
|
||||
# Add custom tags with descriptions
|
||||
if "tags" not in result:
|
||||
result["tags"] = []
|
||||
|
||||
result["tags"].extend(
|
||||
[
|
||||
{
|
||||
"name": "Parks",
|
||||
"description": "Operations related to theme parks, including CRUD operations and statistics",
|
||||
},
|
||||
{
|
||||
"name": "Rides",
|
||||
"description": "Operations related to rides and attractions within theme parks",
|
||||
},
|
||||
{
|
||||
"name": "History",
|
||||
"description": "Historical change tracking for all entities, providing complete audit trails and version history",
|
||||
"externalDocs": {
|
||||
"description": "Learn more about pghistory",
|
||||
"url": "https://django-pghistory.readthedocs.io/",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Statistics",
|
||||
"description": "Statistical endpoints providing aggregated data and insights",
|
||||
},
|
||||
{
|
||||
"name": "Reviews",
|
||||
"description": "User reviews and ratings for parks and rides",
|
||||
},
|
||||
{
|
||||
"name": "Authentication",
|
||||
"description": "User authentication and account management endpoints",
|
||||
},
|
||||
{
|
||||
"name": "Health",
|
||||
"description": "System health checks and monitoring endpoints",
|
||||
},
|
||||
{
|
||||
"name": "Recent Changes",
|
||||
"description": "Endpoints for accessing recently changed entities by type and change category",
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
# Add custom servers if not present
|
||||
if "servers" not in result:
|
||||
result["servers"] = [
|
||||
{
|
||||
"url": "https://api.thrillwiki.com/v1",
|
||||
"description": "Production server",
|
||||
},
|
||||
{
|
||||
"url": "https://staging-api.thrillwiki.com/v1",
|
||||
"description": "Staging server",
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8000/api/v1",
|
||||
"description": "Development server",
|
||||
},
|
||||
]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# Custom AutoSchema class for enhanced documentation
|
||||
class ThrillWikiAutoSchema(AutoSchema):
|
||||
"""
|
||||
Custom AutoSchema class that provides enhanced documentation
|
||||
for ThrillWiki API endpoints.
|
||||
"""
|
||||
|
||||
def get_operation_id(self):
|
||||
"""Generate meaningful operation IDs."""
|
||||
if hasattr(self.view, "basename"):
|
||||
basename = self.view.basename
|
||||
else:
|
||||
basename = getattr(self.view, "__class__", self.view).__name__.lower()
|
||||
if basename.endswith("viewset"):
|
||||
basename = basename[:-7] # Remove 'viewset' suffix
|
||||
|
||||
action = self.method_mapping.get(self.method.lower(), self.method.lower())
|
||||
return f"{basename}_{action}"
|
||||
|
||||
def get_tags(self):
|
||||
"""Generate tags based on the viewset."""
|
||||
if hasattr(self.view, "basename"):
|
||||
return [self.view.basename.title()]
|
||||
return super().get_tags()
|
||||
|
||||
def get_summary(self):
|
||||
"""Generate summary from docstring or method name."""
|
||||
summary = super().get_summary()
|
||||
if summary:
|
||||
return summary
|
||||
|
||||
# Generate from method and model
|
||||
action = self.method_mapping.get(self.method.lower(), self.method.lower())
|
||||
model_name = getattr(self.view, "basename", "resource")
|
||||
|
||||
action_map = {
|
||||
"list": f"List {model_name}",
|
||||
"create": f"Create {model_name}",
|
||||
"retrieve": f"Get {model_name} details",
|
||||
"update": f"Update {model_name}",
|
||||
"partial_update": f"Partially update {model_name}",
|
||||
"destroy": f"Delete {model_name}",
|
||||
}
|
||||
|
||||
return action_map.get(action, f"{action.title()} {model_name}")
|
||||
Reference in New Issue
Block a user