mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 00:31:19 -05:00
264 lines
8.5 KiB
Python
264 lines
8.5 KiB
Python
"""
|
|
Reports API endpoints.
|
|
|
|
Handles user-submitted reports for content moderation.
|
|
"""
|
|
from typing import List
|
|
from uuid import UUID
|
|
from datetime import datetime
|
|
from ninja import Router, Query
|
|
from django.shortcuts import get_object_or_404
|
|
from django.db.models import Count, Avg, Q
|
|
from django.db.models.functions import Extract
|
|
from django.utils import timezone
|
|
|
|
from apps.reports.models import Report
|
|
from apps.users.permissions import require_role
|
|
from api.v1.schemas import (
|
|
ReportOut,
|
|
ReportCreate,
|
|
ReportUpdate,
|
|
ReportListOut,
|
|
ReportStatsOut,
|
|
MessageSchema,
|
|
ErrorResponse,
|
|
)
|
|
|
|
|
|
router = Router(tags=["Reports"])
|
|
|
|
|
|
def serialize_report(report: Report) -> dict:
|
|
"""Serialize a report to dict for output."""
|
|
return {
|
|
'id': report.id,
|
|
'entity_type': report.entity_type,
|
|
'entity_id': report.entity_id,
|
|
'report_type': report.report_type,
|
|
'description': report.description,
|
|
'status': report.status,
|
|
'reported_by_id': report.reported_by_id,
|
|
'reported_by_email': report.reported_by.email if report.reported_by else None,
|
|
'reviewed_by_id': report.reviewed_by_id,
|
|
'reviewed_by_email': report.reviewed_by.email if report.reviewed_by else None,
|
|
'reviewed_at': report.reviewed_at,
|
|
'resolution_notes': report.resolution_notes,
|
|
'created_at': report.created_at,
|
|
'updated_at': report.updated_at,
|
|
}
|
|
|
|
|
|
@router.post("/", response={201: ReportOut, 400: ErrorResponse, 401: ErrorResponse})
|
|
def create_report(request, data: ReportCreate):
|
|
"""
|
|
Submit a report (authenticated users only).
|
|
|
|
Allows authenticated users to report inappropriate or inaccurate content.
|
|
"""
|
|
# Require authentication
|
|
if not request.user or not request.user.is_authenticated:
|
|
return 401, {'detail': 'Authentication required'}
|
|
|
|
# Create report
|
|
report = Report.objects.create(
|
|
entity_type=data.entity_type,
|
|
entity_id=data.entity_id,
|
|
report_type=data.report_type,
|
|
description=data.description,
|
|
reported_by=request.user,
|
|
status='pending'
|
|
)
|
|
|
|
return 201, serialize_report(report)
|
|
|
|
|
|
@router.get("/", response={200: ReportListOut, 401: ErrorResponse})
|
|
def list_reports(
|
|
request,
|
|
status: str = Query(None, description="Filter by status"),
|
|
report_type: str = Query(None, description="Filter by report type"),
|
|
entity_type: str = Query(None, description="Filter by entity type"),
|
|
entity_id: UUID = Query(None, description="Filter by entity ID"),
|
|
page: int = Query(1, ge=1),
|
|
page_size: int = Query(50, ge=1, le=100),
|
|
):
|
|
"""
|
|
List reports.
|
|
|
|
Moderators see all reports. Regular users only see their own reports.
|
|
"""
|
|
# Require authentication
|
|
if not request.user or not request.user.is_authenticated:
|
|
return 401, {'detail': 'Authentication required'}
|
|
|
|
# Build queryset
|
|
queryset = Report.objects.all().select_related('reported_by', 'reviewed_by')
|
|
|
|
# Filter by user unless moderator
|
|
is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin']
|
|
if not is_moderator:
|
|
queryset = queryset.filter(reported_by=request.user)
|
|
|
|
# Apply filters
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
if report_type:
|
|
queryset = queryset.filter(report_type=report_type)
|
|
if entity_type:
|
|
queryset = queryset.filter(entity_type=entity_type)
|
|
if entity_id:
|
|
queryset = queryset.filter(entity_id=entity_id)
|
|
|
|
# Order by date (newest first)
|
|
queryset = queryset.order_by('-created_at')
|
|
|
|
# Pagination
|
|
total = queryset.count()
|
|
total_pages = (total + page_size - 1) // page_size
|
|
start = (page - 1) * page_size
|
|
end = start + page_size
|
|
reports = queryset[start:end]
|
|
|
|
return 200, {
|
|
'items': [serialize_report(report) for report in reports],
|
|
'total': total,
|
|
'page': page,
|
|
'page_size': page_size,
|
|
'total_pages': total_pages,
|
|
}
|
|
|
|
|
|
@router.get("/{report_id}/", response={200: ReportOut, 404: ErrorResponse, 403: ErrorResponse})
|
|
def get_report(request, report_id: UUID):
|
|
"""
|
|
Get a single report by ID.
|
|
|
|
Users can only view their own reports unless they are moderators.
|
|
"""
|
|
report = get_object_or_404(
|
|
Report.objects.select_related('reported_by', 'reviewed_by'),
|
|
id=report_id
|
|
)
|
|
|
|
# Permission check: must be reporter or moderator
|
|
is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin']
|
|
if not is_moderator and report.reported_by != request.user:
|
|
return 403, {'detail': 'You do not have permission to view this report'}
|
|
|
|
return 200, serialize_report(report)
|
|
|
|
|
|
@router.patch("/{report_id}/", response={200: ReportOut, 404: ErrorResponse, 403: ErrorResponse})
|
|
@require_role(['moderator', 'admin'])
|
|
def update_report(request, report_id: UUID, data: ReportUpdate):
|
|
"""
|
|
Update a report (moderators only).
|
|
|
|
Allows moderators to update report status and add resolution notes.
|
|
"""
|
|
report = get_object_or_404(Report, id=report_id)
|
|
|
|
# Update fields if provided
|
|
update_fields = []
|
|
|
|
if data.status is not None:
|
|
report.status = data.status
|
|
update_fields.append('status')
|
|
|
|
# If status is being changed to resolved/dismissed, set reviewed fields
|
|
if data.status in ['resolved', 'dismissed'] and not report.reviewed_by:
|
|
report.reviewed_by = request.user
|
|
report.reviewed_at = timezone.now()
|
|
update_fields.extend(['reviewed_by', 'reviewed_at'])
|
|
|
|
if data.resolution_notes is not None:
|
|
report.resolution_notes = data.resolution_notes
|
|
update_fields.append('resolution_notes')
|
|
|
|
if update_fields:
|
|
update_fields.append('updated_at')
|
|
report.save(update_fields=update_fields)
|
|
|
|
return 200, serialize_report(report)
|
|
|
|
|
|
@router.get("/stats/", response={200: ReportStatsOut, 403: ErrorResponse})
|
|
@require_role(['moderator', 'admin'])
|
|
def get_report_stats(request):
|
|
"""
|
|
Get report statistics (moderators only).
|
|
|
|
Returns various statistics about reports for moderation purposes.
|
|
"""
|
|
queryset = Report.objects.all()
|
|
|
|
# Count by status
|
|
total_reports = queryset.count()
|
|
pending_reports = queryset.filter(status='pending').count()
|
|
reviewing_reports = queryset.filter(status='reviewing').count()
|
|
resolved_reports = queryset.filter(status='resolved').count()
|
|
dismissed_reports = queryset.filter(status='dismissed').count()
|
|
|
|
# Count by report type
|
|
reports_by_type = dict(
|
|
queryset.values('report_type')
|
|
.annotate(count=Count('id'))
|
|
.values_list('report_type', 'count')
|
|
)
|
|
|
|
# Count by entity type
|
|
reports_by_entity_type = dict(
|
|
queryset.values('entity_type')
|
|
.annotate(count=Count('id'))
|
|
.values_list('entity_type', 'count')
|
|
)
|
|
|
|
# Calculate average resolution time for resolved/dismissed reports
|
|
resolved_queryset = queryset.filter(
|
|
status__in=['resolved', 'dismissed'],
|
|
reviewed_at__isnull=False
|
|
)
|
|
|
|
avg_resolution_time = None
|
|
if resolved_queryset.exists():
|
|
# Calculate time difference in hours
|
|
from django.db.models import F, ExpressionWrapper, DurationField
|
|
from datetime import timedelta
|
|
|
|
time_diffs = []
|
|
for report in resolved_queryset:
|
|
if report.reviewed_at and report.created_at:
|
|
diff = (report.reviewed_at - report.created_at).total_seconds() / 3600
|
|
time_diffs.append(diff)
|
|
|
|
if time_diffs:
|
|
avg_resolution_time = sum(time_diffs) / len(time_diffs)
|
|
|
|
return 200, {
|
|
'total_reports': total_reports,
|
|
'pending_reports': pending_reports,
|
|
'reviewing_reports': reviewing_reports,
|
|
'resolved_reports': resolved_reports,
|
|
'dismissed_reports': dismissed_reports,
|
|
'reports_by_type': reports_by_type,
|
|
'reports_by_entity_type': reports_by_entity_type,
|
|
'average_resolution_time_hours': avg_resolution_time,
|
|
}
|
|
|
|
|
|
@router.delete("/{report_id}/", response={200: MessageSchema, 404: ErrorResponse, 403: ErrorResponse})
|
|
@require_role(['moderator', 'admin'])
|
|
def delete_report(request, report_id: UUID):
|
|
"""
|
|
Delete a report (moderators only).
|
|
|
|
Permanently removes a report from the system.
|
|
"""
|
|
report = get_object_or_404(Report, id=report_id)
|
|
report.delete()
|
|
|
|
return 200, {
|
|
'message': 'Report deleted successfully',
|
|
'success': True
|
|
}
|