mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 12:31:13 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
339
django-backend/api/v1/endpoints/timeline.py
Normal file
339
django-backend/api/v1/endpoints/timeline.py
Normal file
@@ -0,0 +1,339 @@
|
||||
"""
|
||||
Timeline API endpoints.
|
||||
|
||||
Handles entity timeline events for tracking significant lifecycle events
|
||||
like openings, closings, relocations, etc.
|
||||
"""
|
||||
from typing import List
|
||||
from uuid import UUID
|
||||
from ninja import Router, Query
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.db.models import Q, Count, Min, Max
|
||||
|
||||
from apps.timeline.models import EntityTimelineEvent
|
||||
from apps.entities.models import Park, Ride, Company, RideModel
|
||||
from apps.users.permissions import require_role
|
||||
from api.v1.schemas import (
|
||||
EntityTimelineEventOut,
|
||||
EntityTimelineEventCreate,
|
||||
EntityTimelineEventUpdate,
|
||||
EntityTimelineEventListOut,
|
||||
TimelineStatsOut,
|
||||
MessageSchema,
|
||||
ErrorResponse,
|
||||
)
|
||||
|
||||
|
||||
router = Router(tags=["Timeline"])
|
||||
|
||||
|
||||
def get_entity_model(entity_type: str):
|
||||
"""Get the Django model class for an entity type."""
|
||||
models = {
|
||||
'park': Park,
|
||||
'ride': Ride,
|
||||
'company': Company,
|
||||
'ridemodel': RideModel,
|
||||
}
|
||||
return models.get(entity_type.lower())
|
||||
|
||||
|
||||
def serialize_timeline_event(event: EntityTimelineEvent) -> dict:
|
||||
"""Serialize a timeline event to dict for output."""
|
||||
return {
|
||||
'id': event.id,
|
||||
'entity_id': event.entity_id,
|
||||
'entity_type': event.entity_type,
|
||||
'event_type': event.event_type,
|
||||
'event_date': event.event_date,
|
||||
'event_date_precision': event.event_date_precision,
|
||||
'title': event.title,
|
||||
'description': event.description,
|
||||
'from_entity_id': event.from_entity_id,
|
||||
'to_entity_id': event.to_entity_id,
|
||||
'from_location_id': event.from_location_id,
|
||||
'from_location_name': event.from_location.name if event.from_location else None,
|
||||
'to_location_id': event.to_location_id,
|
||||
'to_location_name': event.to_location.name if event.to_location else None,
|
||||
'from_value': event.from_value,
|
||||
'to_value': event.to_value,
|
||||
'is_public': event.is_public,
|
||||
'display_order': event.display_order,
|
||||
'created_by_id': event.created_by_id,
|
||||
'created_by_email': event.created_by.email if event.created_by else None,
|
||||
'approved_by_id': event.approved_by_id,
|
||||
'approved_by_email': event.approved_by.email if event.approved_by else None,
|
||||
'submission_id': event.submission_id,
|
||||
'created_at': event.created_at,
|
||||
'updated_at': event.updated_at,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{entity_type}/{entity_id}/", response={200: List[EntityTimelineEventOut], 404: ErrorResponse})
|
||||
def get_entity_timeline(
|
||||
request,
|
||||
entity_type: str,
|
||||
entity_id: UUID,
|
||||
event_type: str = Query(None, description="Filter by event type"),
|
||||
is_public: bool = Query(None, description="Filter by public/private"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=100),
|
||||
):
|
||||
"""
|
||||
Get timeline events for a specific entity.
|
||||
|
||||
Returns a paginated list of timeline events for the specified entity.
|
||||
Regular users only see public events; moderators see all events.
|
||||
"""
|
||||
# Validate entity type
|
||||
model = get_entity_model(entity_type)
|
||||
if not model:
|
||||
return 404, {'detail': f'Invalid entity type: {entity_type}'}
|
||||
|
||||
# Verify entity exists
|
||||
entity = get_object_or_404(model, id=entity_id)
|
||||
|
||||
# Build query
|
||||
queryset = EntityTimelineEvent.objects.filter(
|
||||
entity_type=entity_type.lower(),
|
||||
entity_id=entity_id
|
||||
).select_related('from_location', 'to_location', 'created_by', 'approved_by')
|
||||
|
||||
# Filter by public status (non-moderators only see public events)
|
||||
is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin']
|
||||
if not is_moderator:
|
||||
queryset = queryset.filter(is_public=True)
|
||||
|
||||
# Apply filters
|
||||
if event_type:
|
||||
queryset = queryset.filter(event_type=event_type)
|
||||
if is_public is not None:
|
||||
queryset = queryset.filter(is_public=is_public)
|
||||
|
||||
# Order by date (newest first) and display order
|
||||
queryset = queryset.order_by('-event_date', 'display_order', '-created_at')
|
||||
|
||||
# Pagination
|
||||
total = queryset.count()
|
||||
start = (page - 1) * page_size
|
||||
end = start + page_size
|
||||
events = queryset[start:end]
|
||||
|
||||
return 200, [serialize_timeline_event(event) for event in events]
|
||||
|
||||
|
||||
@router.get("/recent/", response={200: List[EntityTimelineEventOut]})
|
||||
def get_recent_timeline_events(
|
||||
request,
|
||||
entity_type: str = Query(None, description="Filter by entity type"),
|
||||
event_type: str = Query(None, description="Filter by event type"),
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
):
|
||||
"""
|
||||
Get recent timeline events across all entities.
|
||||
|
||||
Returns the most recent timeline events. Only public events are returned
|
||||
for regular users; moderators see all events.
|
||||
"""
|
||||
# Build query
|
||||
queryset = EntityTimelineEvent.objects.all().select_related(
|
||||
'from_location', 'to_location', 'created_by', 'approved_by'
|
||||
)
|
||||
|
||||
# Filter by public status
|
||||
is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin']
|
||||
if not is_moderator:
|
||||
queryset = queryset.filter(is_public=True)
|
||||
|
||||
# Apply filters
|
||||
if entity_type:
|
||||
queryset = queryset.filter(entity_type=entity_type.lower())
|
||||
if event_type:
|
||||
queryset = queryset.filter(event_type=event_type)
|
||||
|
||||
# Order by date and limit
|
||||
queryset = queryset.order_by('-event_date', '-created_at')[:limit]
|
||||
|
||||
return 200, [serialize_timeline_event(event) for event in queryset]
|
||||
|
||||
|
||||
@router.get("/stats/{entity_type}/{entity_id}/", response={200: TimelineStatsOut, 404: ErrorResponse})
|
||||
def get_timeline_stats(request, entity_type: str, entity_id: UUID):
|
||||
"""
|
||||
Get statistics about timeline events for an entity.
|
||||
"""
|
||||
# Validate entity type
|
||||
model = get_entity_model(entity_type)
|
||||
if not model:
|
||||
return 404, {'detail': f'Invalid entity type: {entity_type}'}
|
||||
|
||||
# Verify entity exists
|
||||
entity = get_object_or_404(model, id=entity_id)
|
||||
|
||||
# Build query
|
||||
queryset = EntityTimelineEvent.objects.filter(
|
||||
entity_type=entity_type.lower(),
|
||||
entity_id=entity_id
|
||||
)
|
||||
|
||||
# Filter by public status if not moderator
|
||||
is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin']
|
||||
if not is_moderator:
|
||||
queryset = queryset.filter(is_public=True)
|
||||
|
||||
# Get stats
|
||||
total_events = queryset.count()
|
||||
public_events = queryset.filter(is_public=True).count()
|
||||
|
||||
# Event type distribution
|
||||
event_types = dict(queryset.values('event_type').annotate(count=Count('id')).values_list('event_type', 'count'))
|
||||
|
||||
# Date range
|
||||
date_stats = queryset.aggregate(
|
||||
earliest=Min('event_date'),
|
||||
latest=Max('event_date')
|
||||
)
|
||||
|
||||
return 200, {
|
||||
'total_events': total_events,
|
||||
'public_events': public_events,
|
||||
'event_types': event_types,
|
||||
'earliest_event': date_stats['earliest'],
|
||||
'latest_event': date_stats['latest'],
|
||||
}
|
||||
|
||||
|
||||
@router.post("/", response={201: EntityTimelineEventOut, 400: ErrorResponse, 403: ErrorResponse})
|
||||
@require_role(['moderator', 'admin'])
|
||||
def create_timeline_event(request, data: EntityTimelineEventCreate):
|
||||
"""
|
||||
Create a new timeline event (moderators only).
|
||||
|
||||
Allows moderators to manually create timeline events for entities.
|
||||
"""
|
||||
# Validate entity exists
|
||||
model = get_entity_model(data.entity_type)
|
||||
if not model:
|
||||
return 400, {'detail': f'Invalid entity type: {data.entity_type}'}
|
||||
|
||||
entity = get_object_or_404(model, id=data.entity_id)
|
||||
|
||||
# Validate locations if provided
|
||||
if data.from_location_id:
|
||||
get_object_or_404(Park, id=data.from_location_id)
|
||||
if data.to_location_id:
|
||||
get_object_or_404(Park, id=data.to_location_id)
|
||||
|
||||
# Create event
|
||||
event = EntityTimelineEvent.objects.create(
|
||||
entity_id=data.entity_id,
|
||||
entity_type=data.entity_type.lower(),
|
||||
event_type=data.event_type,
|
||||
event_date=data.event_date,
|
||||
event_date_precision=data.event_date_precision or 'day',
|
||||
title=data.title,
|
||||
description=data.description,
|
||||
from_entity_id=data.from_entity_id,
|
||||
to_entity_id=data.to_entity_id,
|
||||
from_location_id=data.from_location_id,
|
||||
to_location_id=data.to_location_id,
|
||||
from_value=data.from_value,
|
||||
to_value=data.to_value,
|
||||
is_public=data.is_public,
|
||||
display_order=data.display_order,
|
||||
created_by=request.user,
|
||||
approved_by=request.user, # Moderator-created events are auto-approved
|
||||
)
|
||||
|
||||
return 201, serialize_timeline_event(event)
|
||||
|
||||
|
||||
@router.patch("/{event_id}/", response={200: EntityTimelineEventOut, 404: ErrorResponse, 403: ErrorResponse})
|
||||
@require_role(['moderator', 'admin'])
|
||||
def update_timeline_event(request, event_id: UUID, data: EntityTimelineEventUpdate):
|
||||
"""
|
||||
Update a timeline event (moderators only).
|
||||
"""
|
||||
event = get_object_or_404(EntityTimelineEvent, id=event_id)
|
||||
|
||||
# Update fields if provided
|
||||
update_fields = []
|
||||
|
||||
if data.event_type is not None:
|
||||
event.event_type = data.event_type
|
||||
update_fields.append('event_type')
|
||||
|
||||
if data.event_date is not None:
|
||||
event.event_date = data.event_date
|
||||
update_fields.append('event_date')
|
||||
|
||||
if data.event_date_precision is not None:
|
||||
event.event_date_precision = data.event_date_precision
|
||||
update_fields.append('event_date_precision')
|
||||
|
||||
if data.title is not None:
|
||||
event.title = data.title
|
||||
update_fields.append('title')
|
||||
|
||||
if data.description is not None:
|
||||
event.description = data.description
|
||||
update_fields.append('description')
|
||||
|
||||
if data.from_entity_id is not None:
|
||||
event.from_entity_id = data.from_entity_id
|
||||
update_fields.append('from_entity_id')
|
||||
|
||||
if data.to_entity_id is not None:
|
||||
event.to_entity_id = data.to_entity_id
|
||||
update_fields.append('to_entity_id')
|
||||
|
||||
if data.from_location_id is not None:
|
||||
# Validate park exists
|
||||
if data.from_location_id:
|
||||
get_object_or_404(Park, id=data.from_location_id)
|
||||
event.from_location_id = data.from_location_id
|
||||
update_fields.append('from_location_id')
|
||||
|
||||
if data.to_location_id is not None:
|
||||
# Validate park exists
|
||||
if data.to_location_id:
|
||||
get_object_or_404(Park, id=data.to_location_id)
|
||||
event.to_location_id = data.to_location_id
|
||||
update_fields.append('to_location_id')
|
||||
|
||||
if data.from_value is not None:
|
||||
event.from_value = data.from_value
|
||||
update_fields.append('from_value')
|
||||
|
||||
if data.to_value is not None:
|
||||
event.to_value = data.to_value
|
||||
update_fields.append('to_value')
|
||||
|
||||
if data.is_public is not None:
|
||||
event.is_public = data.is_public
|
||||
update_fields.append('is_public')
|
||||
|
||||
if data.display_order is not None:
|
||||
event.display_order = data.display_order
|
||||
update_fields.append('display_order')
|
||||
|
||||
if update_fields:
|
||||
update_fields.append('updated_at')
|
||||
event.save(update_fields=update_fields)
|
||||
|
||||
return 200, serialize_timeline_event(event)
|
||||
|
||||
|
||||
@router.delete("/{event_id}/", response={200: MessageSchema, 404: ErrorResponse, 403: ErrorResponse})
|
||||
@require_role(['moderator', 'admin'])
|
||||
def delete_timeline_event(request, event_id: UUID):
|
||||
"""
|
||||
Delete a timeline event (moderators only).
|
||||
"""
|
||||
event = get_object_or_404(EntityTimelineEvent, id=event_id)
|
||||
event.delete()
|
||||
|
||||
return 200, {
|
||||
'message': 'Timeline event deleted successfully',
|
||||
'success': True
|
||||
}
|
||||
Reference in New Issue
Block a user